这意味着为每周出现一到两次的所有类似(但过于具体的问题,而不是目标候选人)提供一个规范的问答。
我正在开发一个需要解析包含表格的网站的应用程序。由于派生XPath表达式用于抓取网页是一项枯燥且容易出错的工作,我想使用Firebug的XPath提取器功能(或其他浏览器中的类似工具)来实现这一点。
示例输入如下所示:
<!-- snip -->
<table id="example">
<tr>
<th>Example Cell</th>
<th>Another one</th>
</tr>
<tr>
<td>foobar</td>
<td>42</td>
</tr>
</table>
<!-- snip -->
我想提取第一个数据单元格("foobar")。Firebug提出了XPath表达式
//table[@id="example"]/tbody/tr[2]/td[1]
哪个在任何XPath测试器插件中都工作得很好,但我自己的应用程序(找不到结果)就不行。如果我将查询缩减为//table[@id]
,它将再次工作。
出什么问题了?
发布于 2013-08-15 03:53:01
问题: DOM需要<tbody/>
标记
Chrome的开发工具Firebug、JavaScript中的XPath函数和其他工具都可以在DOM上工作,而不是在基本的HTML源代码上工作。
HTML的DOM要求所有未包含在页脚(<thead/>
,<tfoot/>
)表头中的表行都包含在表体标记<tbody/>
中。因此,如果在解析(X)HTML时缺少此标记,浏览器会添加此标记。例如,Microsoft's DOM documentation说
即使表没有显式定义
tbody
元素,也会为所有表公开tbody
元素。
有一个in-depth explanation in another answer on stackoverflow。
另一方面,HTML does not necessarily require that tag to be used
TBODY
开始标记始终是必需的,除非表格只包含一个表体,并且没有表头或表尾部分。
大多数XPath处理器都在原始XML上工作。
除JavaScript外,大多数XPath处理程序都处理原始的XML,而不是DOM,因此不会添加<tbody/>
标记。此外,像tag-soup和htmltidy这样的HTML解析器库只输出XHTML,而不输出"DOM-HTML“。
这是Stackoverflow上发布的一个常见问题,适用于PHP、Ruby、Python、Java、C#、Google Docs (电子表格)和许多其他语言。Selenium在浏览器内部运行,并在DOM上工作--因此它不会受到影响!
重现问题
将Firebug (或Chrome的开发工具)显示的源代码与右键单击并选择“显示页面源代码”(或浏览器中的其他名称) --或通过在命令行上使用curl http://your.example.org
--获得的源代码进行比较。后者可能不包含任何<tbody/>
元素(它们很少使用),Firebug将始终显示它们。
解决方案1:删除/tbody
轴步骤
检查您当前所在的表是否真的不包含<tbody/>
元素(参见最后一段)。如果是这样的话,您可能遇到了另一种问题。
现在删除/tbody
轴步骤,这样您的查询将如下所示
//table[@id="example"]/tr[2]/td[1]
解决方案2:跳过<tbody/>
标记
这是一个相当糟糕的解决方案,对于嵌套表可能会失败(可以跳到内部表)。我只会在非常罕见的情况下推荐这样做。
将/tbody
轴步长替换为后代或自身步长:
//table[@id="example"]//tr[2]/td[1]
解决方案3:允许使用和不使用<tbody/>
标记的输入
如果您事先不确定您的表或在"HTML“和DOM上下文中都使用了查询,并且不想/不能使用解决方案2中的技巧,那么可以提供另一个查询(对于XPath 1.0)或使用”可选的“axis步骤(XPath 2.0及更高版本)。
//table[@id="example"]/tr[2]/td[1] | //table[@id="example"]/tbody/tr[2]/td[1]
//table[@id="example"]/(tbody, .)/tr[2]/td[1]
发布于 2015-01-31 02:33:42
只是遇到了同样的问题。我几乎写了一个递归函数来检查每个tbody标记是否存在,并以这种方式遍历dom,然后我记起我知道regex。:)
在解析之前,获取字符串形式的html。使用正则表达式插入缺少的<tbody>
和</tbody>
标记,然后将其加载回DOMDocument对象。
延斯·埃拉特给出了一个很好的解释,但这里是
解决方案4:确保HTML源始终具有带正则表达式的<tbody>
标记
JavaScript
var html = '<html><table><tr><td>foo</td><td>bar</td></tr></table></html>';
html.replace(/(<table([^>]+)?>([^<>]+)?)(?!<tbody([^>]+)?>)/g,"$1<tbody>").replace(/(<(?!(\/tbody))([^>]+)?>)(<\/table([^>]+)?>)/g,"$1</tbody>$4");
PHP
$html = $dom->saveHTML();
$html = preg_replace(array('/(<table([^>]+)?>([^<>]+)?)(?!<tbody([^>]+)?>)/','/(<(?!(\/tbody))([^>]+)?>)(<\/table([^>]+)?>)/'),array('$1<tbody>','$1</tbody>$4'),$html);
$dom->loadHTML($html);
只有正则表达式:
matches `<table>` tag with whatever else junk inside the tag and between this and the next tag if the next tag is NOT `<tbody>` also with stuff inside the tag
/(<table([^>]+)?>([^<>]+)?)(?!<tbody([^>]+)?>)/
replace with
$1<tbody>
the $1 referencing the captured `<table>` tag with contents.
Do the same for the closing tag like this:
/(<(?!(\/tbody))([^>]+)?>)(<\/table([^>]+)?>)/
replace with
$1</tbody>$4
这样,dom将在必要的地方始终具有<tbody>
标记。
https://stackoverflow.com/questions/18241029
复制相似问题