应使用xml.etree.elementtree.iterparse()流式解析,禁用et.parse();需指定events=("start","end"),在"end"事件处理目标标签并立即调用elem.clear()和elem.getparent().remove(elem)释放内存,配合tag参数过滤、命名空间显式声明及分块写入防oom。

用 xml.etree.ElementTree.iterparse() 流式解析,别用 ET.parse()
直接加载 10G XML 会 OOM——ET.parse() 把整个树塞进内存,哪怕你只取三个字段。必须用 iterparse() 边读边处理,配合 clear() 主动释放已处理节点。
关键点:
-
iterparse()返回 (event, elem) 元组,常用event="start"捕获开始标签,event="end"捕获闭合标签(推荐用"end",避免手动跟踪嵌套) - 每次处理完一个完整元素(比如一个
<record>...</record>),立刻调用elem.clear(),并删除对它的所有引用(包括从父节点 detach) - 用
namespaces参数显式传入命名空间映射,否则带 ns 的标签名会变成{http://...}tag,匹配失败 - 不要在循环里反复调用
root.find()或elem.findall()——它们会重建子树引用,抵消clear()效果
提前过滤:用 tag 参数缩小解析范围
iterparse() 默认吐出所有标签事件,但你往往只关心某几类元素(如 <item></item> 或 <entry></entry>)。加 tag 参数能跳过无关节点,减少 Python 层回调开销。
示例:
for event, elem in ET.iterparse(file_path, events=("start", "end"), tag="item"):
if event == "end":
# 处理 item 元素
process_item(elem)
elem.clear() # 清空子节点
注意:tag 只支持单个标签名,不支持 XPath;若需多级路径(如 doc/items/item),得靠状态机跟踪父标签栈,或改用 lxml 的 iterparse(..., events=("start-ns", "start", "end")) 配合深度计数。
用 lxml 替代标准库,获得真正可控的流式能力
标准库 iterparse() 对命名空间、CDATA、实体引用支持弱,且无法暂停/恢复解析。10G 文件大概率含这些特性,建议切到 lxml。
优势明显:
-
lxml.etree.iterparse()支持huge_tree=True,绕过默认 5M 节点限制 - 可捕获
"start-ns"事件,动态注册命名空间,避免硬编码{ns}tag - 支持
dtd_validation=False和load_dtd=False关闭 DTD 加载,防止远程实体攻击或卡死 - 用
elem.getparent().remove(elem)彻底断开引用,比clear()更彻底
常见坑:lxml 默认会解析外部 DTD,遇到 .. SYSTEM "...dtd"> 可能去拉网路文件或本地路径——务必显式关掉。
内存监控与分块写入:别让解析器“假跑”
即使用了 iterparse(),如果下游逻辑(比如拼 SQL、建 Pandas DataFrame)把数据全攒在内存里,一样爆掉。
实操建议:
- 用
psutil.Process().memory_info().rss在循环中定期检查内存,超阈值(如 2GB)就强制 flush 输出(写 CSV / 插 DB / 存 Parquet) - 避免用
list.append()累积万条记录再处理;改用固定 batch_size(如 5000 条)触发一次批量写入 - 写数据库时禁用 autocommit,每 batch 手动
commit();写文件用open(..., "a")追加,别反复打开关闭 - XML 中文本节点可能含大量空白或换行,用
elem.text.strip() if elem.text else ""预处理,防止字符串对象意外持有多余内存
最易被忽略的是:Python 的垃圾回收不保证立即释放 elem 占用的 C 层内存,lxml 元素尤其顽固——必须显式 remove() + clear() + 确保无变量引用,否则 RSS 会持续上涨直到崩溃。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











