python,lxml或etree获取包含某些文本的节点的父节点

时间:2013-06-18 13:18:01

标签: python xpath lxml

如何继续获取包含一段文本的节点的父节点?

此外,我可以使用一些正则表达式mecanism作为搜索/过滤的匹配元素,例如在re.compile("th[ei]s? .ne")搜索之后吗?

this one

html = '''<html>
<head><title></title></head>
<body>
<table>
<tr><td>1a</td><td>2a</td><td>3a</td><td>4a</td><td>5a</td><td>6a</td></tr>
<tr><td>1b</td><td>2b</td><td>3b</td><td>4b</td><td>5b</td><td>6b</td></tr>
<tr><td>1c</td><td>2c</td><td>3c</td><td>4c</td><td>5c</td><td>6c this one</td></tr>
</table>
<div><div>
<table>
<tr><td>1A</td><td>2A</td><td>3A</td><td>4A</td><td>5A</td><td>6A</td></tr>
<tr><td>1B</td><td>2B</td><td>3B</td><td>4B</td><td>5B</td><td>6B</td></tr>
<tr><td>1C</td><td>2C</td><td>3C</td><td>4C</td><td>5C</td><td>6C</td></tr>
</table>this one
</div></div>
</body>
</html>'''

我想要一个返回的迭代器:

<td>6c this one</td>

然后:

<div>
<table>
<tr><td>1A</td><td>2A</td><td>3A</td><td>4A</td><td>5A</td><td>6A</td></tr>
<tr><td>1B</td><td>2B</td><td>3B</td><td>4B</td><td>5B</td><td>6B</td></tr>
<tr><td>1C</td><td>2C</td><td>3C</td><td>4C</td><td>5C</td><td>6C</td></tr>
</table>this one
</div>

我试过了:

import lxml.html
root = lxml.html.document_fromstring(html)
root.xpath("//text()[contains(., one)]")

import xml.etree.ElementTree as ET
for e in ET.fromstring(html).getiterator():
    if e.text and e.text.find('one') != -1:
        print "Found string %r, element = %r" % (e.text, e)

但我能拥有的最好的是包含this one本身的节点...而我正在寻找包含此文本的父节点。注意div或表只是例如,我真的需要在找到“this one”之后回到父级而不是过滤包含this one的xml元素,因为我不知道这是div,表还是在找到它包含的内容之前的任何事情。

(另请注意,它是html并且没有很好的格式化xml,因为我认为第二个this one应该包装在xml标记中)

编辑:

>>> root.xpath("//*[contains(child::*/text(), 'one')]") # why empty parent?
[]
>>> root.xpath("//*[contains(text(), 'one')]") # i expected to have a list with two elements td and div
[<Element td at 0x280b600>]
>>> root.xpath("//*[child::*[contains(text(), 'one')]]") # if parent: expected tr and div, if not parent expected table or div, still missing one
[<Element tr at 0x2821f30>]

BTW,使用最后一个是好的:

import xml.etree.ElementTree as ET
import lxml.html
#[... here add html = """...]
root = lxml.html.document_fromstring(html)
for i, x in enumerate(root.xpath("//text()[contains(., 'one')]/parent::*")):
    print "%s => \n\t" % i, ET.tostring(x).replace("\n", "\n\t")

产生

0 => 
    <td>6c this one</td>
1 => 
    <div>
    <table>
    <tr><td>1A</td><td>2A</td><td>3A</td><td>4A</td><td>5A</td><td>6A</td></tr>
    <tr><td>1B</td><td>2B</td><td>3B</td><td>4B</td><td>5B</td><td>6B</td></tr>
    <tr><td>1C</td><td>2C</td><td>3C</td><td>4C</td><td>5C</td><td>6C</td></tr>
    </table>this one
    </div>

2 个答案:

答案 0 :(得分:3)

根据您的示例输出,您似乎想要获取包含指定文本one的元素。您的描述表明您想要此节点的父节点。

基于此假设,您可以使用以下XPath获取所需的节点:

//*[contains(text(), 'one')]

如果您真的想要此节点的父节点,则可以执行

//*[child::*[contains(text(), 'one')]]

顺便说一句,正如您所看到的,我使用谓词来获取节点,因此我过滤了XML节点。在我看来,这是更合乎逻辑和可读的方法,因为它基本上说给我所有满足给定条件的节点而不是说给我输出我的条件并从中指向搜索实际所需的输出。但您也可以执行以下操作,这样可以更好地匹配您提出的解决方案:

//text()[contains(., 'one')]/parent::*

答案 1 :(得分:1)

>>> root.xpath("//*[contains(child::*/text(), 'one')]") # why empty parent?
[]

此XPath表达式选择第一个孙子文本节点包含的每个元素&#39; one&#39;。 contains()的第一个参数应该是一个字符串,因此XPath在child::*/text()的结果中取第一个节点并获取其字符串值。由于没有元素的文本节点包含&#34;一个&#34;作为它的第一个孙子,答案是一个空的节点列表。

>>> root.xpath("//*[contains(text(), 'one')]")
# i expected to have a list with two elements td and div
[<Element td at 0x280b600>]

出于同样的原因,此XPath表达式选择第一个文本节点子节点包含的所有元素&#39; one&#39;。这就是选择<td>的原因,但<div>不是:div的子文本节点包含&#39; one&#39;不是第一个子文本节点。

>>> root.xpath("//*[child::*[contains(text(), 'one')]]")
# if parent: expected tr and div,
# if not parent expected table or div, still missing one
[<Element tr at 0x2821f30>]

这面临与前一个表达式相同的限制。

你有没有尝试过@dirkk建议的最后一个解决方案,

//text()[contains(., 'one')]/parent::*

这应该避免将多个节点作为contains()的第一个参数传递出来的问题。