不正确的父元素 lxml

Incorrect parent element lxml

我正在 Python.

中实施网络抓取程序

考虑我下面的 HTML 片段。

<div>
  <b> 
    <i>
      HelloWorld
   </i>
   HiThere
  </b>
</div>

如果我想使用 lxml 仅提取粗体或斜体文本,我使用以下命令

tree = etree.fromstring(myhtmlstr, htmlparser)
opp1 = tree.xpath(".//text()[ancestor::b or ancestor::i or ancestor::strong]")

这给了我正确的结果,即我的 opp1 的结果是:

['HelloWorld', 'HiThere']

到目前为止,一切都很完美。但是,如果我尝试查询标签的父级,就会出现真正的问题。正如预期的那样,opp1[0].getparent().tagopp1[0].getparent().getparent().tag 的输出是 ib.

然而,真正的问题在第二个标签中。理想情况下,opp[1] 的父标签应该是 b 标签。然而,opp1[1].getparent().tagopp1[1].getparent().getparent().tag的输出又是ib

您可以在以下代码中进行验证:

from lxml import etree
htmlstr = """<div><b><i>HelloWorld</i>HiThere</b></div>"""
htmlparser = etree.HTMLParser()
tree = etree.fromstring(htmlstr, htmlparser)
opp1 = tree.xpath(".//text()[ancestor::b or ancestor::i or ancestor::strong]")
print(opp1)
print(opp1[0].getparent(), opp1[0].getparent().getparent())
print(opp1[1].getparent(), opp1[1].getparent().getparent())

有人能指出为什么会这样吗?我该怎么做才能纠正它?我计划只为我的程序使用 lxml,并且不想要任何使用 bs4 的解决方案。

这个问题似乎源于 LXML(和 ElementTree)的数据模型,其中一个元素大致是 "tag, attributes, text, children, tail"; DOM 数据模型也有实际的文本节点。

如果你改变你的程序来做

for x in tree.xpath(".//text()[ancestor::b or ancestor::i or ancestor::strong]"):
    print(x, x.getparent(), "text?", x.is_text, "tail?", x.is_tail)

它将打印

HelloWorld <Element i at 0x10aa0ccd0> text? True tail? False
HiThere <Element i at 0x10aa0ccd0> text? False tail? True

"HiThere" 是第 i 个元素的尾部,因为这是 Etree 数据模型表示混合文本和标签的方式。

此处的要点(可能适用于您的用例)是将 .getparent().getparent() 视为具有 is_tail=True.

的文本结果的有效父级