应选lxml而非xml.etree.ElementTree,因其底层C实现支持增量解析、XPath 2.0子集和自动命名空间处理,吞吐量达标准库3–5倍;但需注意系统依赖及iterparse()内存管理。

为什么不用xml.etree.ElementTree而选lxml
因为 xml.etree.ElementTree 在解析几百MB以上XML时会明显变慢,且内存占用高;lxml 底层用C实现,支持增量解析、XPath 2.0子集、命名空间自动处理,实际吞吐量通常是标准库的3–5倍。但要注意:它不是纯Python模块,需系统级依赖(libxml2/libxslt),Windows用户装 pip install lxml 通常能直接拉预编译轮子,Linux/macOS可能需先装 libxml2-dev 和 libxslt-dev。
用iterparse()边读边处理,避免OOM
加载整个大型XML到内存是最大陷阱。别用 etree.parse() 或 etree.fromstring(),改用 etree.iterparse() 配合事件驱动清理:
from lxml import etree
<p>context = etree.iterparse('huge.xml', events=('start', 'end'))
context = iter(context)
event, root = next(context) # 获取根节点,但不保留全部子树</p><p>for event, elem in context:
if event == 'end' and elem.tag == 'record':</p><h1>处理单条record</h1><pre class="brush:php;toolbar:false;"> process_record(elem)
elem.clear() # 关键!释放已处理节点的内存
while elem.getparent() is not None:
elem.getparent().remove(elem)
elem = elem.getparent()
-
iterparse()默认只缓存当前路径上的节点,配合clear()和显式remove()才能真正控住内存 - 别在
start事件里做重逻辑——此时子元素还没读入,容易误判结构 - 如果XML有默认命名空间,需提前用
etree.register_namespace('', 'http://...'),否则elem.tag会带乱码前缀
用XPath快速定位,但别滥用 //
// 是全树扫描,对GB级XML等于重头遍历一遍,性能灾难。优先用相对路径或带条件过滤:
# ❌ 慢:从根开始无脑搜
records = root.xpath('//record[@status="active"]')
<h1>✅ 快:假设record都在data下,且已知层级</h1><p>records = root.xpath('./data/record[@status="active"]')</p><h1>✅ 更快:用iterfind限制范围</h1><p>for record in root.iterfind('./data/record'):
if record.get('status') == 'active':
process(record)
</p>
-
iterfind()返回迭代器,比xpath()返回列表更省内存 - 属性过滤用
@attr比用.get('attr')稍快,但差异小;结构过滤层级越浅越好 - 如果要多次查同一文档,可预编译XPath:
compiled = etree.XPath('./data/record[@status=$stat]'),再调用compiled(root, stat='active')
写入大XML时用ElementMaker + 文件流
生成大型XML别拼字符串,也别反复 append() 后 write() 整棵树——这会导致中间树驻留内存。用 etree.ElementMaker 构建片段,配合 etree.XMLFile 流式写入:
from lxml import etree
<p>E = etree.ElementMaker()
with open('output.xml', 'wb') as f:
writer = etree.XMLFile(f)
with writer.element('root'):
for i in range(100000):
record = E.record(
E.id(str(i)),
E.value('data_' + str(i))
)
writer.write(record) # 直接刷到文件,不存内存
</p>
-
XMLFile是lxml 4.2+引入的API,替代旧式tostring()+write()组合 - 如果目标XML需要声明编码(如UTF-8),在
XMLFile初始化时加encoding='utf-8' - 注意:写入时若节点含非法字符(如\x00),
XMLFile.write()会静默跳过,建议提前用re.sub(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]', '', text)清洗
实际跑GB级XML时,最常被忽略的是命名空间清理和 iterparse() 中的双重释放逻辑——漏掉 elem.clear() 或没向上 remove(),内存只会涨不会降。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











