【发布时间】:2012-02-22 14:57:27
【问题描述】:
这个问题已经改写了。我正在使用 CPAN Perl 模块 WWW::Mechanize 导航网站,使用 HTML::TreeBuilder-XPath 捕获内容并使用 xacobeo 在 HTML/XML 上测试我的 XPath 代码。目标是从基于 PHP 的网站调用这个 Perl 脚本,并将抓取的内容上传到数据库中。因此,如果内容“缺失”,仍然需要考虑。
下面是一个经过测试的简化示例代码,描述了我的挑战。注意:
- 这个页面是动态填充的,包含了针对不同店铺输出的各种
ITEMS;每个商店将存在不同数量的Products*。这些产品列表下方可能有也可能没有明细表。 - 捕获的数据必须在数组中,并且必须维护任何逐项列表(如果存在)与产品列表的关联。
下面,每个商店的示例 xml 更改(如上所述),但为简洁起见,我只显示一种“类型”的输出。我意识到可以将所有数据捕获到一个数组中,然后使用正则表达式来破译内容,以便将其上传到数据库中。我正在寻求更好的 XPath 知识,以帮助简化这个(和未来的)解决方案。
<!DOCTYPE XHTML>
<table id="8jd9c_ITEMS">
<tr><th style="color:red">The Products we have in stock!</th></tr>
<tr><td><span id="Product_NUTS">We have nuts!</span></td></tr>
<tr><td>
<!--Table may or may not exist -->
<table>
<tr><td style="color:blue;text-indent:10px">Almonds</td></tr>
<tr><td style="color:blue;text-indent:10px">Cashews</td></tr>
<tr></tr>
</table>
</td></tr>
<tr><td><span id="Product_VEGGIES">We have veggies!</span></td></tr>
<tr><td>
<!--Table may or may not exist -->
<table>
<tr><td style="color:blue;text-indent:10px">Carrots</td></tr>
<tr><td style="color:blue;text-indent:10px">Celery</td></tr>
<tr></tr>
</table>
</td></tr>
<tr><td><span id="Product_ALCOHOL">We have booze!</span></td></tr>
<!--In this case, the table does not exist -->
</table>
XPath 语句:
'//table[contains(@id, "ITEMS")]/tr[position() >1]/td/span/text()'
会找到:
We have nuts!
we have veggies!
We have booze!
还有一个 XPath 语句:
'//table[contains(@id, "ITEMS")]/tr[position() >1]/td/table/tr/td/text()'
会找到:
Almonds
Cashews
Carrots
Celery
这两个 XPath 语句可以合并:
'//table[contains(@id, "ITEMS")]/tr[position() >1]/td/span/text() | //table[contains(@id, "ITEMS")]/tr[position() >1]/table/tr/td/text()'
要查找:
We have nuts!
Almonds
Cashews
We have veggies!
Carrots
Celery
We have booze!
再一次,上面的数组可以被破译(在实际代码中),因为它是使用正则表达式的产品到列表的关联。 但是可以使用 XPath 以保持这种关联的方式构建数组吗?
例如(假说,这个不行):
'//table[contains(@id, "ITEMS")]/tr[position()>1]/td/span/text() |
if exists('//table[contains(@id, "ITEMS")]/tr[position() >1]/table))
then ("NoTable") else ("TableRef") |
Save this result into @TableRef ('//table[contains(@id, "ITEMS")]/tr[position() >1]/table/tr/td/text()')'
在 Perl 中构建多维数组(传统意义上的)是不可能的,请参阅 perldoc perlref 但希望类似于上述的解决方案可以创建类似的东西:
@ITEMS[0] => We have nuts!
@ITEMS[1] => nutsREF <-- say, the last word of the span value + REF
@ITEMS[2] => We have veggies!
@ITEMS[3] => veggiesREF <-- say, the last word of the span value + REF
@ITEMS[4] => We have booze!
@ITEMS[5] => NoTable <-- value accounts for the missing info
@nutsREF[0] => Almonds
@nutsREF[1] => Cashews
@veggiesREF[0] => Carrots
@veggiesREF[1] => Celery
在实际代码中,产品是已知的,因此可以在预期 XPath 输出时定义 my @veggiesREF 和 my @nutsREF。
我意识到 XPath if/else/then 功能在 XPath 2.0 版本中。我在 ubuntu 系统上并在本地工作,但我仍然不清楚我的 apache2 服务器是使用它还是 1.0 版本。我该如何检查?
最后,如果您可以展示如何从 PHP 表单提交调用 Perl 脚本,以及如何将 Perl 数组传回调用 PHP 函数,那么这将有助于获得赏金。 :)
谢谢!
最终编辑:
该帖子下方的评论直接指向了一个过于模糊的初始帖子。随后的重新发布(和赏金)得到了 ikegami 的回应,它非常有创意地使用它解决了伪问题,但事实证明我很难在我的实际应用程序中掌握和重用 - 这需要在各种 html 页面上进行多次使用。在我们对话的第 18 条评论中,我终于发现了他对 ($cat) 的含义和用法——他使用的一种未记录的 Perl 语法。对于新读者来说,理解这种语法可以理解(和重新格式化)他对问题的智能解决方案。他的帖子当然符合 OP 中寻求的基本要求,但没有使用 HTML::TreeBuilder::XPath 来完成。
jpalecek 使用 HTML::TreeBuilder::XPath,但不将捕获的数据放入数组中,以便传回 PHP 函数并上传到数据库。
我从两位响应者那里学到了东西,并希望这篇文章可以帮助其他刚接触 Perl 的人,比如我自己。任何最终贡献将不胜感激。
【问题讨论】:
-
无响应。 :(我的问题是不是太罗嗦了?有些人更喜欢额外的信息。这真的是一个常见的要求吗?
-
td[3 and 4 and 6]不返回第三个、第四个和第六个成员。括号中的表达式返回 true,因此返回所有td。使用td[position()=3 or position()=4 or position()=6]。 -
您忘记提供源 XML 文档(请尽可能小)。如果没有显示源 XML,那么请求选择某些节点的 XPath 表达式几乎毫无意义——这很好地解释了您得到的响应(缺少)。
-
@DimitreNovaatchev 我并不是真的在寻找一个确切的句法答案并发布多个文档,而且他们的所有代码都太多了 - 帖子已经太长了。这是一个架构问题:Choroba 解决了明显的语法错误。问题 1 更复杂,但从我读过的内容来看,XPath 没有为节点提供逻辑 if/else 方法。尽管 XPath 可以在格式正确的谓词中提供“if”选择,但我的解决方案更可能是在运行表达式的 Perl 代码中 - 可能处理返回的谓词值(?)
-
@choroba 感谢您对问题 1 的简短回答。 RE: 问题 2,您是否曾经需要检查节点 in XPATH 是否存在(if/else)并相应地更改您的代码?我倾向于使用 XPath 表达式检查存在,然后更改运行它的 Perl 代码。