应使用 xml.etree.elementtree.iterparse() 替代 et.parse() 实现流式解析,避免内存溢出;需手动调用 elem.clear() 和 root.clear() 释放引用,并注意命名空间处理、避免循环内重复 find()、不适用场景及时切换工具。

用 xml.etree.ElementTree.iterparse() 替代 ET.parse()
直接调用 ET.parse() 会把整个 XML 加载进内存构建树结构,文件超过几百 MB 就极易触发 MemoryError。而 iterparse() 是流式解析,边读边处理,内存占用基本只取决于单个元素的大小。
关键点:必须手动清理已处理的元素,否则引用残留仍会导致内存持续增长:
- 在循环中对每个处理完的
elem调用elem.clear() - 对父节点(如根)也要定期
root.clear(),尤其当子元素深度大时 - 用
events=("start", "end")控制触发时机,通常只在"end"时提取数据
示例片段:
for event, elem in ET.iterparse("huge.xml", events=("start", "end")):
if event == "end" and elem.tag == "record":
# 处理该 record
process_record(elem)
elem.clear() # 立即释放子元素引用
遇到命名空间时,elem.tag 会带前缀导致匹配失败
如果 XML 声明了命名空间(如 <rss xmlns="http://purl.org/rss/1.0/"></rss>),elem.tag 实际是 {http://purl.org/rss/1.0/}item,直接写 elem.tag == "item" 永远为假。
解决方式有两种:
- 预定义命名空间字典:
ns = {"rss": "http://purl.org/rss/1.0/"},然后用elem.tag == "{http://purl.org/rss/1.0/}item"或elem.tag.endswith("}") and elem.tag.split("}")[-1] == "item" - 更稳妥的是用
ET.register_namespace()配合find(),但iterparse()中推荐直接字符串后缀匹配,避免额外开销
解析速度慢?检查是否在循环里反复调用 elem.find()
elem.find() 每次都做 XPath 查找,若在每条记录中重复查找相同子字段(比如 elem.find("title")),性能损耗明显,尤其当 elem 子节点多时。
优化建议:
- 改用属性访问(如果结构固定):
elem.findtext("title") or ""比elem.find("title").text if elem.find("title") else ""更简洁安全 - 或提前获取子元素引用:
title_elem = elem.find("title"),再判断title_elem is not None后取值 - 避免在循环内构造正则或新建对象(如
datetime.strptime()),可提至外部或用time.strptime()缓存
需要随机访问或多次遍历?别硬扛,换专用工具
iterparse() 是单向、一次性的。如果你后续还要按 ID 查记录、排序、去重或关联其他数据,硬靠缓存到列表(records.append(...))会再次引爆内存。
此时应切换技术栈:
- 用
lxml的iterparse()(比标准库快 2–5 倍,且支持remove_blank_text=True减少文本节点) - 导出为 CSV / SQLite:边解析边写入磁盘,之后用 SQL 查询替代内存遍历
- 真正超大规模(GB+)考虑
xmltodict+ 流式 JSON 解析,或迁移到 Spark +spark-xml
最常被忽略的一点:即使用了 iterparse(),如果在回调中把整条记录深拷贝、转成 dict 再塞进大列表,和直接 parse() 没本质区别——流式解析的收益全被后续操作抵消了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











