【问题标题】:Extract text based on previous and next sibling根据上一个和下一个兄弟姐妹提取文本
【发布时间】:2012-07-30 10:23:51
【问题描述】:

我正在尝试从以下结构中提取数据:

<span>Heading</span>
<br />
<br />
<span>Heading1</span>
<br />
data#1
<br />
<br />
<span>Heading4</span><br />
&acirc;&euro;&cent; data#4.1
<br />
&acirc;&euro;&cent; data#4.2
<br />
&acirc;&euro;&cent; data#4.3
<br />
&acirc;&euro;&cent; data#4.4
<br />
<br />
<span>Heading5</span>
<br />
&acirc;&euro;&cent; data#5.1
<br />
&acirc;&euro;&cent; data#5.2
<br />
&acirc;&euro;&cent; data#5.3
<br />
<br />

我可以使用以下方法提取数据#1:

span[text()='Heading1']/following-sibling::br[1]/following::text()[1]

但我不知道如何提取 Heading4 下的数据。我需要提取data#4.1data#4.2data#4.3data#4.4。 点数不是固定的,可以变化。

【问题讨论】:

    标签: xpath web-scraping


    【解决方案1】:

    这个 XPath 1.0 表达式准确地选择了想要的节点

      /*/span[.='Heading4']
            /following-sibling::text()
               [count(.|/*/span[.='Heading5']/preceding-sibling::text())
               =
                count(/*/span[.='Heading5']/preceding-sibling::text())
                ]
                      [normalize-space()]
    

    它是由著名的 Kayessian 方法产生的,用于两个节点集 $ns1$ns2 的交集:

    $ns1[count(.|$ns2) = count($ns2)]
    

    如果在 Kayessian 公式中我们将$ns1 替换为:

      /*/span[.='Heading4']/following-sibling::text()
    

    $ns2 与:

      /*/span[.='Heading5']/preceding-sibling::text()
    

    最后的谓词[normalize-space()] 从这个交集过滤掉只有空白的文本节点。

    基于 XSLT 的验证

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:template match="/">
         <xsl:copy-of select=
          "/*/span[.='Heading4']
                /following-sibling::text()
                   [count(.|/*/span[.='Heading5']/preceding-sibling::text())
                   =
                    count(/*/span[.='Heading5']/preceding-sibling::text())
                    ]
                    [normalize-space()]
          "/>
     </xsl:template>
    </xsl:stylesheet>
    

    在提供的 XML 文档上应用此转换时(替换实体——因为我们没有定义它们的 DTD,这在这里不是必需的):

    <html>
        <span>Heading</span>
        <br />
        <br />
        <span>Heading1</span>
        <br /> data#1 
        <br />
        <br />
        <span>Heading4</span>
        <br /> #acirc;#euro;#cent; data#4.1 
        <br /> #acirc;#euro;#cent; data#4.2 
        <br /> #acirc;#euro;#cent; data#4.3 
        <br /> #acirc;#euro;#cent; data#4.4 
        <br />
        <br />
        <span>Heading5</span>
        <br /> #acirc;#euro;#cent; data#5.1 
        <br /> #acirc;#euro;#cent; data#5.2 
        <br /> #acirc;#euro;#cent; data#5.3 
        <br />
        <br />
    </html>
    

    计算 Xpath 表达式并将计算结果复制到输出:

     #acirc;#euro;#cent; data#4.1 
         #acirc;#euro;#cent; data#4.2 
         #acirc;#euro;#cent; data#4.3 
         #acirc;#euro;#cent; data#4.4 
    

    【讨论】:

      【解决方案2】:

      你可以使用

      span[text()='Heading4']/following-sibling::text()[. != ""] 
      

      获取Heading4之后的所有文字然后使用。

      span[text()='Heading5']/following-sibling::text()[. != ""]
      

      获取 Heading5 之后不需要的文本,然后在主程序中从第一个结果集中减去第二个结果集。

      如果您有 XPath 2,则可以直接使用 except 运算符排除它们:

      span[text()='Heading4']/following-sibling::text()[. != ""] except span[text()='Heading5']/following::text()[. != ""]
      

      在使用substring(.,5) 函数之前,您只能得到data 而没有&amp;acirc;&amp;euro;&amp;cent;,因此最终的XPath 2 表达式变为:

      (span[text()='Heading4']/following-sibling::text()[. != ""] except span[text()='Heading5']/following::text()[. != ""])/substring(., 5)
      

      而且由于您没有明确说明您的语言要求,您可能还想查看我的pascal based query language,因为它更好:

       <span>Heading4</span><br />
       <t:loop>
          {filter(text(), "data.*")}<br/>
       </t:loop>
       <br/>
       <span>Heading5</span><br />
      

      【讨论】:

      • 感谢您的详细回答。会试一试。我实际上在scrapy中使用它,这是一个基于python的网络抓取框架。
      • 刚刚检查过,scrapy 不支持 XPath 2.0。还有其他想法吗?
      • 好吧,我已经实现了所有的 XPath 2.0...(我真的应该在 python 中完成它,但我认为解释器内存开销太大:()您可能可以模拟 except 运算符使用 position()/last()/count() 函数来排除一定数量的文本节点。像这样:(/span[text()='Heading4']/following::text()[position() &lt; last() - count(/span[text()='Heading5']/following::text())])[.!=""](按回车键保存评论?这很烦人。以及消失的换行符)
      【解决方案3】:

      在答案here的帮助下,我终于使用了它

      //text()[preceding-sibling::span[1] = 'Heading4']

      【讨论】:

        【解决方案4】:

        我会使用

        span[text()='Heading4']/following-sibling::text()
        

        然后分别解析结果文本。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-07-23
          • 2014-07-12
          • 2021-10-08
          • 1970-01-01
          • 2020-05-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多