使用 lxml xpath 时容易遇到一些常见问题
/ / 阅读数:1683摘要 :新手刚接触 lxml 时,总会遇到一些奇奇怪怪的问题,解决这些问题需要花费不少时间。本文整理了一些常见的问题,希望能对你有所帮助。
XPath( XML Path Language,XML 路径语言)是一门在 XML 文档中查找信息的语言。XPath 最初设计是用来搜寻 XML 文档,但是它同样适用于 HTML 文档的搜索。在 Python 标准库提供了HTMLParser
和SGMLParser
两个模块用于解析 HTML,然而这两个模块的实现复杂,用得很不顺手,而第三方库 lxml 则简单许多,而且它基于 Cython 实现,性能出众。
我们可以使用pip install lxml
命令来安装它,具体使用教程可以参考 lxml 官方文档 或 w3school 中文教程 ,以下我只列举一些新手容易遇到的常见问题:
一、lxml.html 与 lxml.etree 的区别
lxml.html
用来解析 HTML 文档,而lxml.etree
用来解析 XML 文档,etree 也可以解析正规的 HTML,但是如果 HTML 文档不完整,则会抛错。
二、lxml.html 中 fromstring, soupparser, html5lib 的区别
from lxml.html import fromstring, soupparser, html5parser content = '<html><a>link</a></html>' tree1 = fromstring(content) tree2 = soupparser.fromstring(content) tree3 = html5parser.fromstring(content) |
主要区别:
- 第一种方式
lxml.html.fromstring
使用的 parser 为etree.HTMLParser
的子类,我们称其为 lxml 解析器。 - 第二种方式 soupparser 模块底层是用 BeautifulSoup 来处理 html,指定的 parser 为 Python 自带的
html.parser
,BeautifulSoup 对于声明编码与实际编码不一致的情况处理的更好。 - 第三种方式 html5lib 模块实现了与浏览器类似的对 HTML5 网页的解析算法,损失一些性能换取更好的兼容性。
>>> from lxml.html import tostring, html5parser >>> tostring(html5parser.fromstring("<table><td>foo")) '<table><tbody><tr><td>foo</td></tr></tbody></table>'
结论是按自己的需求来选择,1) 如果需要编码识别能力好,用 soupparser,2) 如果要更好的兼容性,用 html5lib,3) 否则用默认的fromstring
性能更佳。
三、如何将 lxml HtmlElement 对象转换为 HTML 文本
可以使用lxml.html.tostring
:
import lxml.html as lh tree = lh.fromstring('<div><p><a>link</a></p></div>') elems = tree.xpath('//a') # HtmlElement print(lh.tostring(elems[0])) # Output: b'<a>link</a>' |
四、如何获取指定位置的节点
import lxml.html as lh tree = lh.fromstring('<div><p><a>link1</a><a>link2</a></p></div>') print(tree.xpath('//a[2]/text()') # Output: link2 |
五、contains 有什么需要注意的地方
注意contains
是纯字符串匹配(只要包含特定字符即可),有时候其行为并不是我们想要的。比如,有三个链接,前两个 class 包含link
,第三个包含link3
。
当我们想要获取所有包含link
的链接时,首先想到的是使用=
和contains
:
from lxml.html import fromstring tr = fromstring('<a class="link r">l1</a><a class="g link">l2</a><a class="link3">l3</a>') print(tr.xpath('//*[@class="link")]/text()') # Wrong Output: [] print(tr.xpath('//*[contains(@class, "link")]/text()') # Wrong Output: [l1, l2, l3] print(tr.xpath('//*[contains(@class, "link ")]/text()') # Wrong Output: [l1, l3] print(tr.xpath('//*[contains(@class, " link")]/text()') # Wrong Output: [l2, l3] print(tr.xpath('//*[contains(@class, " link ")]/text()') # Wrong Output: [] |
如以上代码所示,我们期望的是获取 class 名为link
的元素,而不是 class 名中包含link
的元素。
无论怎么样使用contains
或=
都无法得到正确的结果。解决方案是使用下文介绍的正则表达式(RegExp)来匹配。
六、如何使用正则表达式
lxml 支持 xpath 1.0 标准,不支持 xpath 2.0,这就导致有些功能不能使用,比如正则匹配(matches
),第一个元素(first()
)等。
不过 lxml 提供了扩展的方式来解决部分遗憾,比如我们可以使用 exslt 来 扩展正则表达式功能 :
from lxml.html import fromstring tr = fromstring('<a class="link r">l1</a><a class="g link">l2</a><a class="link3">l3</a>') ns = {"re": "http://exslt.org/regular-expressions"} print(tr.xpath('//*[contains(@class, "link")]/text()') # Wrong Output: l1, l2, l3 print(tr.xpath(r'//*[re:match(@class, "(^|\s)link($|\s)")]/text()', namespaces=ns) # Right Output: l1, l2 |