我正在编写一个可以执行操作列表的通用HTML资源管理器,例如访问页面,查找表,查找行,存储数据等。它在内部使用Goutte / Guzzle,因此可以使用CSS和XPath选择。我有一个有趣的问题,我坚持选择一组相对于现有结果的新结果。
考虑这个演示HTML:
<h2>Burrowing</h2>
<ul>
<li>
<a href="/jobs/junior-mole">Junior Mole</a>
</li>
<li>
<a href="/jobs/head-of-badger-partnerships">Head of Badger Partnerships</a>
</li>
<li>
<a href="/jobs/trainee-worm">Trainee Worm</a>
</li>
</ul>
<h2>Tree Surgery</h2>
<ul>
<li>
<a href="/jobs/senior-woodpecker">Senior Woodpecker</a>
</li>
<li>
<a href="/jobs/owl-supervisor">Owl Supervisor</a>
</li>
</ul>
<h2>Grass maintenance</h2>
<ul>
<li>
<a href="/jobs/trainee-sheep">Trainee sheep</a>
</li>
<li>
<a href="/jobs/sheep-shearer">Sheep shearer</a>
</li>
</ul>
<h2>Aerial supervision</h2>
<ul>
<li>
<a href="/jobs/head-magpie-ops">Head of Magpie Operations</a>
</li>
</ul>
我运行此CSS查询以获取链接中的角色(这正确地获取了八个项目):
ul li a
对于每一个,我想得到一个类别,即每个案例中<h2>
之前的<ul>
。现在我可以用绝对的CSS选择器来做到这一点:
h2
然而,得到四个结果,所以我不知道哪个类别(h2)与哪个作业(链接)有关。我需要获得8个结果:第一个类别中的三个,第二个中的两个,第三个中的两个,以及第四个中的一个,因此每个类别都映射到每个角色。
我想知道我是否需要一个父选择器,所以我从CSS切换到XPath,并首先尝试了这个,这使得每个h2都有一个紧随其后的列表项:
//h2[(following-sibling::ul)[1]/li/a]
找到具有指定父结构的h2s,但又返回了四个结果 - 没有好处。
下一次尝试:
//ul/li[../preceding-sibling::h2[1]]
获得正确数量的结果(基于获取具有前一个标题的列表项)但获取链接文本,而不是类别文本。
我想过做一个循环 - 我知道我有8个结果,所以我可以做到这一点(X是一个从1到8循环的注入变量)。这是有效的,但我认为在这里增加一个手动循环相当不优雅 - 我试图让我的规则尽可能通用:
//li[X]/../preceding-sibling::h2[1]
是否有可以返回所需结果的XPath操作?为避免疑问,我正在寻找以下内容(或者只是文本元素会没问题):
<h2>Burrowing</h2>
<h2>Burrowing</h2>
<h2>Burrowing</h2>
<h2>Tree Surgery</h2>
<h2>Tree Surgery</h2>
<h2>Grass maintenance</h2>
<h2>Grass maintenance</h2>
<h2>Aerial supervision</h2>
CSS也可以,但我认为这是不可能的,因为CSS没有父运算符(在任何情况下,Goutte只是将CSS选择器转换为XPath选择器)。
由于我使用的是PHP(5.5),我相信我必须坚持使用XPath 1.0。
答案 0 :(得分:2)
不,没有单个XPath 1.0表达式可以返回您想要的内容。首先是因为XPath 1.0不允许迭代中间结果,其次是因为一系列项目是defined as a node-set - 其中没有重复项。
我可以看到两个可能的问题解决方案。编写
的PHP代码a
个节点,例如使用类似//a
preceding::h2[1]
由于我的技能很差,你必须自己编写PHP代码。但我可以提供一个替代方案:你也可以在PHP中使用XSLT 1.0转换,there are XSLT 1.0 processors。
<强>样式表强>
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" />
<xsl:template match="/">
<xsl:for-each select="//a">
<xsl:copy-of select="preceding::h2[1]"/>
</xsl:for-each>
</xsl:template>
</xsl:transform>
应用于您的输入(添加根元素后),结果为
<h2>Burrowing</h2>
<h2>Burrowing</h2>
<h2>Burrowing</h2>
<h2>Tree Surgery</h2>
<h2>Tree Surgery</h2>
<h2>Grass maintenance</h2>
<h2>Grass maintenance</h2>
<h2>Aerial supervision</h2>
在线试用here。顺便说一句,如果您对如何使用for
使用XPath 2.0感兴趣,正如您在评论中提到的那样,请参阅this version instead:
for $a in //a return $a/preceding::h2[1]
答案 1 :(得分:1)
所以我不确定你是如何尝试使用它的,但我会尝试类似的东西:
$links = $cralwer->filter('ul li a');
foreach ($links as $link) {
// do stuff with the link
// ...
// get the H2
$header = $link->parents()->filter('ul[../preceding-sibling::h2]');
// do stuff with the header
}
注意这是未经测试的,我从直接查看Symfony\Component\DomCrawler
API得出它,但我认为它应该基于此工作(除非我有XPath错误 - 但如果我这样做应该很容易为了你的工作)。
你当然也可以使用Symfony\Component\DomCrawler::each
并在封闭内执行此操作而不是做foreach ...