21

这旨在为每周出现一两次的所有类似(但过于具体的问题而不是密切的目标候选人)提供规范的问答。

我正在开发一个需要解析包含表格的网站的应用程序。由于为抓取网页而派生 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],它会再次起作用。

怎么了?

4

2 回答 2

45

问题:DOM 需要<tbody/>标签

Firebug、Chrome 的开发者工具、JavaScript 中的 XPath 函数和其他在DOM上工作,而不是在基本的HTML 源代码上工作。

HTML 的 DOM 要求所有不包含在页脚 ( <thead/>, <tfoot/>) 的表头中的表行都包含在表体标记<tbody/>中。因此,如果在解析 (X)HTML 时缺少此标记,浏览器会添加此标记。例如,微软的 DOM 文档

tbody元素对所有表都公开,即使该表没有显式定义tbody元素。

在 stackoverflow 的另一个答案中有深入的解释

另一方面,HTML 不一定要求使用该标签

TBODY除非表格仅包含一个表格主体且没有表格头部或底部部分,否则始终需要开始标记。

大多数 XPath 处理器处理原始 XML

除了 JavaScript,大多数 XPath 处理器都处理原始 XML,而不是 DOM,因此不添加<tbody/>标签。HTML 解析器库(如只输出 XHTML,而不是“DOM-HTML”。

这是在 Stackoverflow 上发布的 PHP、Ruby、Python、Java、C#、Google 文档(电子表格)和许多其他问题的常见问题。Selenium 在浏览器中运行并在 DOM 上运行——所以它不受影响!

重现问题

将 Firebug(或 Chrome 的开发工具)显示的源代码与通过右键单击并选择“显示页面源代码”(或在浏览器中调用的任何名称)或curl http://your.example.org在命令行中使用的源代码进行比较。后者可能不包含任何<tbody/>元素(它们很少使用),Firebug 将始终显示它们。


解决方案 1:删除/tbodyAxis Step

检查您遇到的表格是否真的不包含<tbody/>元素(请参阅最后一段)。如果是这样,您可能遇到了另一种问题。

现在删除/tbody轴步骤,所以您的查询看起来像

//table[@id="example"]/tr[2]/td[1]

解决方案 2:跳过<tbody/>标签

这是一个相当肮脏的解决方案,并且对于嵌套表可能会失败(可以跳转到内部表)。我只会在极少数情况下建议这样做。

/tbody用后代或自我步骤替换轴步骤:

//table[@id="example"]//tr[2]/td[1]

解决方案 3:允许带标签和不带<tbody/>标签的输入

如果您事先不确定您的表格或在“HTML 源”和 DOM 上下文中使用查询;并且不想/不能使用解决方案 2 中的 hack,提供替代查询(对于 XPath 1.0)或使用“可选”轴步骤(XPath 2.0 和更高版本)。

  • XPath 1.0
    //table[@id="example"]/tr[2]/td[1] | //table[@id="example"]/tbody/tr[2]/td[1]
  • XPath 2.0//table[@id="example"]/(tbody, .)/tr[2]/td[1]
于 2013-08-14T19:53:01.410 回答
2

刚遇到同样的问题。我几乎写了一个递归函数来检查每个 tbody 标签是否存在并以这种方式遍历 dom,然后我记得我知道正则表达式。:)

在解析之前,将 html 作为字符串获取。使用正则表达式插入缺失<tbody></tbody>标签,然后将其加载回您的 DOMDocument 对象。

Jens Erat 给出了一个很好的解释,但这里是

解决方案 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>在必要时拥有标签。

于 2015-01-30T18:33:42.050 回答