在XPath中基于Uncle过滤

时间:2012-10-23 15:50:39

标签: xpath scrapy

假设我有一个包含以下行的HTML表,

...
<tr>
  <th title="Library of Quintessential Memes">LQM:</th>
  <td>
    <a href="docs/lqm.html"><b>Intro</b></a>
    <a href="P/P79/">79</a>
    <a href="P/P80/">80</a>
    <a href="P/P81/">81</a>
    <a href="P/P82/">82</a>
  </td>
</tr>
<tr>
  <th title="Library of Boring Books">LBB:</th>
  <td>
    <a href="docs/lbb.html"><b>Intro</b></a>
    <a href="R/R80/">80</a>
    <a href="R/R81/">81</a>
    <a href="R/R82/">82</a>
    <a href="R/R83/">83</a>
    <a href="R/R84/">84</a>
  </td>
</tr>
...

我想选择<a>元素中的所有<td>元素,其关联的<th>文本位于一小组固定标题中(例如LQM,LBR和RTT) 。如何将其表示为XPath查询?

编辑:我正在使用Scrapy,一个Python抓取工具包,所以如果将这个查询作为一组较小的查询更容易,我会非常乐意使用它。例如,如果我可以选择其第一个<tr>子项与正则表达式匹配的所有<th>元素,则选择其余<a>个元素的所有<tr>个后代,这将是出色的。

1 个答案:

答案 0 :(得分:2)

以下XPath将起作用:

//a[contains(',LQM:,LBR:,RTT:,',
             concat(',', ancestor::td/preceding-sibling::th, ','))]

理论上,这可能会产生一些误报(如果您的代码包含逗号)。

更严格的说法是:

//a[ancestor::td/preceding-sibling::th[.='LQM:']]
|//a[ancestor::td/preceding-sibling::th[.='LBR:']]
|//a[ancestor::td/preceding-sibling::th[.='RTT:']]

我通过在输入周围添加<table>标记并应用以下XSL转换来测试此内容:

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/">
        <xsl:for-each select="//a[ancestor::td/preceding-sibling::th[.='LQM:']]
                                  |//a[ancestor::td/preceding-sibling::th[.='LBR:']]
                                  |//a[ancestor::td/preceding-sibling::th[.='RTT:']]">
            <xsl:text>
</xsl:text>
            <xsl:copy-of select="."/>
        </xsl:for-each>
    </xsl:template>

</xsl:transform>

它产生以下输出:

<a href="docs/lqm.html"><b>Intro</b></a>
<a href="P/P79/">79</a>
<a href="P/P80/">80</a>
<a href="P/P81/">81</a>
<a href="P/P82/">82</a>

当然,如果您使用的是XSL,那么您可能会发现这种结构更具可读性:

<xsl:for-each select="//a">
    <xsl:variable name="header" select="ancestor::td/preceding-sibling::th"/>

    <xsl:if test="$header='LQM:' or $header = 'LBR:' or $header = 'RTT:'">
        <xsl:text>
        </xsl:text>
        <xsl:copy-of select="."/>

    </xsl:if>
</xsl:for-each>