在Python的数据处理和爬虫开发领域,解析HTML和XML文档是一项绕不开的基础工作。标准库虽然提供了html.parser和xml.etree.ElementTree,但面对结构复杂或写法不规范的文档时,它们往往力不从心。lxml基于C语言实现的libxml2库构建,不仅解析速度极快,还自带容错能力,能自动修复缺失闭合标签的HTML片段,是处理真实网页数据的首选工具。这篇文章将从安装配置、基础解析、XPath定位到进阶技巧,完整梳理lxml的核心用法。

一、安装lxml与核心模块介绍
lxml的安装非常简单,直接使用pip即可完成。Windows用户如果遇到编译报错,可以尝试升级pip后再安装,新版本的pip会自动下载预编译的wheel包,无需本地编译C代码。
pip install lxml # 指定国内镜像源加速安装 pip install lxml -i https://pypi.tuna.tsinghua.edu.cn/simple
安装完成后,主要会用到两个子模块:lxml.etree负责XML解析和XPath支持,lxml.html专门针对HTML文档做了优化,内置更强的容错能力。两者的API设计风格接近,但处理目标不同。简单来说,解析严格规范的XML数据(比如接口返回的SOAP报文、RSS订阅源)用etree,解析来源复杂的网页HTML用lxml.html更稳妥。
导入时还需要注意一个细节:老代码中常见from lxml import etree后在PyCharm里报红线的情况,这只是IDE的静态检查问题,不影响实际运行,可以忽略或者改用from lxml import etree as et的方式规避。
二、用etree解析XML文档
解析XML字符串是etree最基础的用法。etree.XML()接收字节串或字符串,返回一个Element对象,也就是文档的根节点。拿到根节点后,就可以通过tag属性查看标签名,通过attrib属性查看全部属性。
from lxml import etree
xml_data = '''<bookstore>
<book category="技术">
<title lang="zh">Python进阶</title>
<price>59.00</price>
</book>
<book category="小说">
<title lang="zh">三体</title>
<price>45.00</price>
</book>
</bookstore>'''
root = etree.XML(xml_data)
print(root.tag) # 输出: bookstore
print(root.attrib) # 输出: {'category'相关属性为空时不会显示}
# 遍历所有book节点
for book in root.iter('book'):
title = book.find('title').text
price = book.find('price').text
print(f'书名: {title}, 价格: {price}')如果XML内容存放在文件里,用etree.parse()更方便,它直接接收文件路径,返回一个ElementTree对象,再通过getroot()拿到根节点。对于特别大的XML文件,etree提供了增量解析的方式,通过etree.iterparse逐个事件处理节点,避免一次性把整个文档加载进内存,处理几百兆的日志文件也不会爆内存。
# 文件解析
tree = etree.parse('books.xml')
root = tree.getroot()
# 大文件增量解析,只处理end事件
for event, element in etree.iterparse('big_file.xml', events=('end',), tag='record'):
print(element.findtext('name'))
element.clear() # 及时释放已处理的节点内存需要注意的一点是,etree对XML格式要求严格,如果字符串里存在未定义的命名空间或者非法字符,etree.XML()会直接抛出XMLSyntaxError异常。生产环境解析外部数据时,建议用try-except包裹,保证程序不会因为一段脏数据直接崩溃。
三、解析容错性强的HTML文档
真实世界中的网页HTML很少是完全规范的,缺闭合标签、属性没加引号、标签嵌套错误这些问题到处都是。如果用严格的XML解析器去处理,基本都会报错。lxml的解决方案是使用lxml.html模块,它底层调用libxml2的HTML解析器,会自动推断并修复这些结构问题。
from lxml import html
html_str = '''<html>
<body>
<div class="list">
<p>第一段内容
<p>第二段内容
</div>
</body>
</html>'''
doc = html.fromstring(html_str)
# 自动补全了未闭合的p标签
for p in doc.cssselect('.list p'):
print(p.text)上面例子中的两个<p>标签都没有闭合,但解析器自动处理了,遍历时能正常取出两个元素。另外lxml.html还提供parse()函数,可以直接传入URL远程抓取并解析网页,不过它的抓取能力比较简陋,不支持复杂的请求头设置,实际爬虫项目中还是建议用requests拿到响应后再交给lxml解析,分工更清晰。
有时候网页编码不是UTF-8,比如老的GBK编码页面,直接解析可能出现乱码。这时可以先让requests猜测编码,或者手动指定:doc = html.fromstring(resp.content.decode('gbk')),把字符串统一转成Unicode再交给lxml,是最稳妥的做法。
四、XPath精准定位节点数据
lxml相比标准库最大的优势就是对XPath的完整支持。XPath是一门在文档中查找信息的语言,表达能力远强于单纯的find方法。Element对象直接支持xpath()调用,返回值永远是列表。
from lxml import etree
html_data = '''<ul id="menu">
<li><a href="/news">新闻</a></li>
<li><a href="/sports">体育</a></li>
<li class="hot"><a href="/tech">科技</a></li>
</ul>'''
root = etree.HTML(html_data)
# 选取所有a标签的href属性
links = root.xpath('//ul[@id="menu"]/li/a/@href')
print(links) # ['/news', '/sports', '/tech']
# 选取class为hot的li下的a标签文本
text = root.xpath('//li[@class="hot"]/a/text()')
print(text) # ['科技']
# 使用last()函数取最后一个li
last_item = root.xpath('//li[last()]/a/text()')
print(last_item)XPath中几个常用表达式值得记牢:双斜杠//表示从全文档递归查找,单斜杠/表示从当前节点的直接子级查找,@用于取属性,text()用于取文本。组合使用contains()函数可以模糊匹配属性,比如//div[contains(@class, "item")]能匹配class中包含item的所有div,处理那种class里拼了多个类名的页面特别好用。
还有一个容易踩的坑:当XPath表达式匹配不到任何节点时,xpath()返回空列表而不是报错,所以取值时一定要做非空判断,否则直接对[0]取下标会抛出IndexError。建议封装一个安全的取值函数,列表为空时返回默认值。
五、命名空间处理与序列化输出
解析XML时经常遇到带命名空间的文档,比如站点地图sitemap。直接用普通标签名去XPath查找会一无所获,必须注册命名空间前缀。etree提供了nsmap属性查看文档中定义的命名空间映射,查找时在标签前加上前缀和冒号即可。
xml_ns = '''<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.ipipp.com/a</loc></url>
<url><loc>https://example.ipipp.com/b</loc></url>
</urlset>'''
root = etree.XML(xml_ns)
ns = {'sm': 'http://www.sitemaps.org/schemas/sitemap/0.9'}
locs = root.xpath('//sm:url/sm:loc', namespaces=ns)
for loc in locs:
print(loc.text)解析完数据后,经常还需要把结果重新输出成字符串。etree.tostring()可以把Element对象序列化,参数encoding='unicode'输出字符串,不指定则输出字节串。参数pretty_print=True会自动格式化缩进,方便人工检查生成的文档结构。
result = etree.tostring(root, encoding='unicode', pretty_print=True) print(result)
六、lxml与其他解析方案的选择建议
和标准库的ElementTree相比,lxml的API几乎完全兼容,但多了XPath、CSS选择器和大文件增量解析能力,速度也快得多,没有任何理由不优先选它。和BeautifulSoup相比,两者定位不同:BeautifulSoup的API更加人性化和宽容,学习曲线平缓;lxml则在性能上占据绝对优势,处理海量数据时差距非常明显。
一个常见的折中方案是把BeautifulSoup和lxml结合起来,创建解析器时指定lxml作为底层引擎,既保留了BeautifulSoup的易用接口,又获得了lxml的速度。如果对性能要求极致,直接使用lxml配合XPath是最优解。日常写爬虫,推荐掌握etree.HTML()加xpath()这套组合,配合浏览器的开发者工具直接复制XPath表达式,开发效率会提升一大截。
最后提醒一点,XPath表达式从浏览器复制过来时有时会带上/html/body这样的绝对路径,一旦页面结构稍有调整就会失效。尽量手写相对路径,从稳定的id或class属性入手定位,写出的解析规则才足够健壮。
Python lxmlHTML解析XML解析修改时间:2026-09-04 00:53:12