Php 如何检索相对于找到的特定HTML节点的父元素文本?

Php 如何检索相对于找到的特定HTML节点的父元素文本?,php,xpath,css-selectors,Php,Xpath,Css Selectors,我正在编写一个通用的HTML浏览器,它可以执行一系列操作,例如访问页面、查找表、查找行、存储数据等。它在内部使用Goutte/Guzzle,因此可以使用CSS和XPath选择器。我遇到了一个有趣的问题,我一直在考虑相对于现有结果集选择一组新的结果 考虑这个演示HTML: <h2>Burrowing</h2> <ul> <li> <a href="/jobs/junior-mole">

我正在编写一个通用的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选择器来实现这一点:

h2
然而,这得到了四个结果,所以我不知道哪个类别(h2)与哪个作业(链接)相匹配。我需要得到八个结果:第一类的三个批次,第二类的两个,第三类的两个,第四类的一个,所以每个类别对应于每个角色

我想知道我是否需要一个父选择器来执行此操作,所以我从CSS切换到XPath,并首先尝试了此操作,它使每个h2都有一个紧跟其后的列表项:

//h2[(following-sibling::ul)[1]/li/a]
这会发现h2s具有指定的父结构,但再次返回时会得到四个结果-不好

下一次尝试:

//ul/li[../preceding-sibling::h2[1]]
它获取正确数量的结果(基于获取前一个标题的列表项),但获取链接文本,而不是类别文本

我考虑过做一个循环——我知道我有八个结果,所以我可以做这个(X是一个从1到8循环的注入变量)。这是可行的,但我认为这里添加的手动循环相当不雅观-我正在尽可能保持我的规则的通用性:

//li[X]/../preceding-sibling::h2[1]
是否存在可以返回所需结果的XPath操作?为免生疑问,我正在寻找以下内容(或者只需要文本元素即可):

挖洞
挖洞
挖洞
树木外科
树木外科
草地养护
草地养护
空中监督
CSS也可以,但我认为这是不可能的,因为CSS没有父操作符(在任何情况下,Goutte只是将CSS选择器转换为XPath选择器)


由于我使用的是PHP(5.5),我相信我必须坚持使用XPath 1.0。

因此我不确定您是如何尝试使用它的,但我会尝试以下方法:

$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
}
请注意,这是未经测试的,我是通过直接查看得到的,但我认为它应该基于此工作(除非我的XPath错误-但如果我这样做了,您应该很容易解决)


当然,您也可以使用
Symfony\Component\DomCrawler::each
并在闭包中执行此操作,而不是执行foreach…

不,没有一个XPath 1.0表达式返回您想要的内容。首先是因为XPath 1.0不允许对中间结果进行迭代,其次是因为项目序列是-其中不能有重复项

对于你的问题,我有两种可能的解决办法。要么编写PHP代码

  • 首先检索所有相关的
    a
    节点,例如使用类似
    //a
  • 将第二个XPath表达式依次应用于它们中的每一个:
    preference::h2[1]
你必须自己写PHP代码,因为我的技能很差。但我可以提供另一种选择:您也可以在PHP中使用XSLT1.0转换

样式表

<?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>

离题。。。你的销售代表怎么了?也许你是另一个用户?我过去经常看到的那只哈弗犬有几万只。。。我很困惑…@prodigitalson:不是我,仍然没有突破10公里线!快到了…谢谢你的建议!然而,我试图尽可能地概括我的处理步骤,
ulli a
的“抓取行”很好,而您的第二个表达式实际上是“抓取行数据”操作。然而,
parents()
这件事让它不那么通用,理想情况下我希望它能在没有这种情况下工作(也就是说,在解析一个新页面时,我只添加各种预定义的步骤类型,根本不必编写任何PHP)。我想
parents()
本身可能是一个步骤,因此过程将是“获取这些行[xpath],遍历到父级,获取这些列[xpath]”。有趣的是,我刚刚发现xpath 2.0,所以我想在该版本中这将是微不足道的!然而,我还是停留在1.0上,除非我能抽出时间让2.0解析器在控制台上运行,并将其破解成Goutte(在我看来,这并不值得费心)。啊,两个好的新想法,非常感谢。
for
XPath最令人沮丧,因为它非常完美,不需要在我的应用程序中更改设计,但语法不可用!呸。XSLT值得考虑:根据我对prodigitalson的评论,我正在制作一个通用解析器,这样我就可以在不编写任何新PHP的情况下扫描任何结构,而通用转换步骤将是一个有用的补充。(我可能有一条鱼在附近,看看是否有人已经用XPath 2.0以某种方式使用PHP,也许有任何可以接受的黑客。如果我找到一些东西,我会在这页上记下。它看起来确实不错)。
$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
}
<?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>
for $a in //a return $a/preceding::h2[1]