为啥我的 XPath 查询(抓取 HTML 表)只能在 Firebug 中工作,而不能在我正在开发的应用程序中工作?
Posted
技术标签:
【中文标题】为啥我的 XPath 查询(抓取 HTML 表)只能在 Firebug 中工作,而不能在我正在开发的应用程序中工作?【英文标题】:Why does my XPath query (scraping HTML tables) only work in Firebug, but not the application I'm developing?为什么我的 XPath 查询(抓取 HTML 表)只能在 Firebug 中工作,而不能在我正在开发的应用程序中工作? 【发布时间】:2013-08-16 22:54:16 【问题描述】:这旨在为每周出现一两次的所有类似(但过于具体的问题而不是密切的目标候选人)提供规范的问答。
我正在开发一个需要解析包含表格的网站的应用程序。由于为抓取网页而导出 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]
,它会再次起作用。
怎么了?
【问题讨论】:
也许值得一提的是,在<script> </script>
标记内测试这些 xpath 查询并不是一个好主意,它们被插入到 <body>
后面,因为它会失败(元素还不存在) . ***.com/questions/14028959/…
今天我还根据***.com/a/25949484/367456 进行了一些讨论,这与Table 无关,而是与浏览器Xpath 相关:似乎Firefox 接受大写的元素和属性名称。 DOMDocument xpath 需要那些小写的(不是这个参考问题中的问题,但我想交叉链接它,因为我第一次看到它,这是一个伟大的倡议!)。
【参考方案1】:
问题:DOM 需要 <tbody/>
标签
Firebug、Chrome 的开发者工具、javascript 中的 XPath 函数和其他函数在 DOM 上工作,而不是基本的 HTML 源代码。
html 的 DOM 要求所有不包含在表头页脚(<thead/>
、<tfoot/>
)中的表行都包含在表体标签 <tbody/>
中。因此,如果在解析 (X)HTML 时缺少此标记,浏览器会添加此标记。例如,Microsoft's DOM documentation 表示
tbody
元素对所有表都公开,即使该表没有明确定义tbody
元素。
有一个in-depth explanation in another answer on ***。
另一方面,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”。
这是 *** 上发布的常见问题,适用于 php、Ruby、Python、Java、C#、Google Docs(电子表格)和许多其他问题。 Selenium 在浏览器中运行并在 DOM 上运行——因此不受影响!
重现问题
将 Firebug(或 Chrome 的开发工具)显示的源代码与您通过右键单击并选择“显示页面源代码”(或在浏览器中调用的任何名称)或在命令行。后者可能不包含任何 <tbody/>
元素(它们很少使用),Firebug 将始终显示它们。
解决方案1:删除/tbody
Axis 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]
【讨论】:
除了上面所说的,对于我在这些场景中的刮板,我有一个“skipFirstRow”标志,它实际上工作得很好(对于我正在刮的页面)。 我已经搜索了 4 个小时的解决方案,因为我想要从某个站点获得的数据不想成为我的。所有值都可以通过它们的 xpaths 轻松获得,但是其中一个表返回了 error,解决方案是删除tbody
并用额外的 /
替换它.【参考方案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>
标签。
【讨论】:
不知怎的,这个答案立刻让我想起了***.com/a/1732454/5114081 @Alex 这是一个很棒的帖子。但它真的适用于这个案例吗?正则表达式用于在字符串被实际的 xml 解析器解析之前插入可能丢失的部分。可以通过搜索、检查、插入、移动和迭代对它解析成的对象执行相同的操作,但是这似乎更快,因为计算机不关心位代表什么。请注意,我在这里可能完全错了,如果有人可以展示这种方法导致安全问题、意外行为等,请提供解释或示例,以便我们都可以从中学习。 就个人而言,我想不出在这里使用正则表达式可能导致意外行为的示例,否则我会提到它。这并不是为了不必要地批评你的帖子,我只是想提醒一下,使用正则表达式可以(至少在理论上)带来一些后备。以上是关于为啥我的 XPath 查询(抓取 HTML 表)只能在 Firebug 中工作,而不能在我正在开发的应用程序中工作?的主要内容,如果未能解决你的问题,请参考以下文章
如果不包含某些字符串,则替换某些子值?还是重写 XPATH 查询?网站抓取
Python - for循环,它产生的抓取数据每页只循环一次