前言

这里只是是一本lxml的简单修炼手册


一、lxml 概述

1.1 什么是 lxml

lxml 是 Python 中一个高效、功能丰富的 XML 和 HTML 处理库,结合了:

  • libxml2 的高性能
  • ElementTree 的简单 API
  • 强大的 XPath 和 XSLT 支持

1.2 核心特性

  • 支持 XML 和 HTML 解析
  • 完整的 XPath 1.0 实现
  • XSLT 1.0 转换支持
  • 基于 ElementTree 的 API
  • 高性能解析(比内置 xml 模块快 10-100 倍)
  • 优雅的错误恢复机制

二、安装与基本使用

2.1 安装

1
pip install lxml

2.2 基础解析示例

1
2
3
4
5
6
7
8
9
10
from lxml import etree

# XML 解析
xml_data = "<root><child>Text</child></root>"
root = etree.fromstring(xml_data)

# HTML 解析
from lxml import html
html_data = "<html><body><div>Content</div></body></html>"
tree = html.fromstring(html_data)

三、XML 处理

3.1 元素操作

1
2
3
4
5
6
7
8
9
10
11
12
# 创建元素
root = etree.Element("root")
child = etree.SubElement(root, "child")
child.text = "Text content"

# 添加属性
child.set("id", "123")

# 获取元素
print(root.tag) # "root"
print(child.text) # "Text content"
print(child.get("id")) # "123"

3.2 元素遍历

1
2
3
4
5
6
7
8
9
10
11
# 遍历子元素
for element in root:
print(element.tag)

# 遍历所有后代元素
for element in root.iter():
print(element.tag)

# 按标签名查找
for child in root.iter("child"):
print(child.text)

四、HTML 处理

4.1 HTML 解析与修复

1
2
3
4
5
6
7
broken_html = "<div><p>Paragraph</div>"
tree = html.fromstring(broken_html)

# 获取修复后的 HTML
fixed_html = etree.tostring(tree, pretty_print=True).decode()
print(fixed_html)
# 输出: <div><p>Paragraph</p></div>

4.2 提取链接与文本

1
2
3
4
5
6
7
8
9
10
11
12
html_content = """
<a href="/page1">Link 1</a>
<a href="/page2">Link 2</a>
"""

tree = html.fromstring(html_content)

# 提取所有链接
links = tree.xpath('//a/@href') # ['/page1', '/page2']

# 提取链接文本
texts = [a.text for a in tree.xpath('//a')] # ['Link 1', 'Link 2']

五、XPath 表达式

5.1 基本语法

表达式 说明 示例
nodename 选择节点 //div
/ 从根节点选择 /root/child
// 选择任意位置 //div
. 当前节点 ./child
.. 父节点 ../parent
@ 选择属性 //@id
* 通配符 //*

5.2 实用示例

1
2
3
4
5
6
7
8
9
10
11
# 选择所有带class属性的div
divs = tree.xpath('//div[@class]')

# 选择第一个p元素
first_p = tree.xpath('//p[1]')

# 选择包含特定文本的元素
elements = tree.xpath('//*[contains(text(), "Search")]')

# 选择属性值开头的元素
elements = tree.xpath('//a[starts-with(@href, "/category")]')

六、XSLT 转换

6.1 基本转换流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- input.xml -->
<data>
<item>Apple</item>
<item>Banana</item>
</data>

<!-- transform.xsl -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<fruits>
<xsl:apply-templates/>
</fruits>
</xsl:template>

<xsl:template match="item">
<fruit><xsl:value-of select="."/></fruit>
</xsl:template>
</xsl:stylesheet>
1
2
3
4
5
6
7
8
# Python 转换代码
dom = etree.parse("input.xml")
xslt = etree.parse("transform.xsl")
transform = etree.XSLT(xslt)
result = transform(dom)

print(str(result))
# 输出: <fruits><fruit>Apple</fruit><fruit>Banana</fruit></fruits>

七、高级功能

7.1 解析器选项

1
2
3
4
5
6
7
8
9
# 创建解析器
parser = etree.XMLParser(
remove_blank_text=True, # 删除空白文本节点
resolve_entities=False, # 不解析实体
huge_tree=True # 允许大型文档
)

# 使用自定义解析器
tree = etree.parse("large.xml", parser)

7.2 增量解析(处理大型文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
context = etree.iterparse("large.xml", events=("start", "end"))

for event, elem in context:
if event == "start" and elem.tag == "item":
# 处理开始标签
pass
elif event == "end" and elem.tag == "item":
# 处理元素内容
print(elem.text)
# 清理已处理元素
elem.clear()
while elem.getprevious() is not None:
del elem.getparent()[0]

7.3 HTML 表单处理

1
2
3
4
5
6
7
8
9
10
11
tree = html.fromstring("<form><input name='user' value='admin'></form>")
form = tree.forms[0] # 获取第一个表单

# 获取表单值
print(form.fields['user']) # 'admin'

# 修改表单值
form.fields['user'] = 'newuser'

# 序列化表单数据
print(form.form_values()) # [('user', 'newuser')]

八、性能优化

8.1 高效处理技巧

  1. 使用迭代解析:处理大型文件时避免内存溢出

    1
    2
    3
    for _, element in etree.iterparse("large.xml", tag="item"):
    process(element)
    element.clear()
  2. 编译XPath表达式:重复使用时提升性能

    1
    2
    find_items = etree.XPath("//items/item")
    items = find_items(tree)
  3. 选择性解析:只加载需要的部分

    1
    2
    # 仅解析目标元素
    items = etree.parse("data.xml").xpath("//target-element")

8.2 与内置库对比

特性 lxml xml.etree.ElementTree
性能 ⭐⭐⭐⭐ ⭐⭐
XPath支持 完整1.0 有限子集
HTML处理 优秀 不支持
错误恢复 强大 有限
内存占用 较低 较高
依赖 C扩展 纯Python

九、常见问题解决

9.1 命名空间处理

1
2
3
4
5
6
7
8
9
xml = """<root xmlns:ns="http://example.com">
<ns:item>Value</ns:item>
</root>"""

tree = etree.fromstring(xml)
ns = {"ns": "http://example.com"}

# 带命名空间的XPath
items = tree.xpath("//ns:item", namespaces=ns)

9.2 编码问题

1
2
3
4
5
6
# 指定编码解析
parser = etree.XMLParser(encoding="iso-8859-1")
tree = etree.parse("data.xml", parser)

# 输出指定编码
result = etree.tostring(root, encoding="utf-8", xml_declaration=True)

9.3 错误处理

1
2
3
4
5
try:
tree = etree.parse("invalid.xml")
except etree.XMLSyntaxError as err:
print(f"解析错误: {err.message}")
print(f"位置: 行 {err.lineno}, 列 {err.offset}")

十、最佳实践

10.1 安全建议

  1. 禁用实体解析(避免XXE攻击)

    1
    parser = etree.XMLParser(resolve_entities=False)
  2. 谨慎处理用户提供的XSLT/XPath

  3. 验证输入数据格式

10.2 开发实践

  1. 优先使用XPath:比手动遍历更简洁高效

  2. 利用HTML修复功能:处理不规范的网页内容

  3. 使用CSS选择器(需要lxml.cssselect)

    1
    2
    3
    from lxml.cssselect import CSSSelector
    sel = CSSSelector('div.content > p')
    paragraphs = sel(tree)
  4. 组合使用工具

    1
    2
    3
    # BeautifulSoup + lxml
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, "lxml")