应使用 xml.etree.ElementTree.iterparse 进行流式解析,避免 ET.parse() 导致内存暴增;需指定 events=("start", "end"),在 end 事件处理目标标签并立即 elem.clear();lxml.etree.iterparse 更快但需 huge_tree=True 处理大文本。

用 xml.etree.ElementTree.iterparse 做流式解析,别碰 ET.parse()
直接调 ET.parse() 或 ET.fromstring() 解析 GB 级文件,基本等于主动触发 MemoryError。它会把整个 XML 构建成内存中的树结构,实际内存占用通常是文件体积的 5–10 倍。而 iterparse() 是事件驱动的生成器,只在读到标签边界时产出 (event, elem),内存占用几乎恒定。
关键操作有三步:
- 必须显式传
events=("start", "end"),否则默认只返回"end"事件,丢失属性读取时机 - 在
event == "end"且elem.tag == "目标标签"时处理数据——这时元素已完整加载,子节点全可用 -
立刻执行
elem.clear(),否则已处理节点的子树引用会持续累积,内存照涨不误
为什么 lxml.etree.iterparse 比标准库快,但要注意 huge_tree=True
lxml 的 iterparse 底层是 C 实现,解析速度通常比标准库快 2–5 倍,尤其在含大量文本或嵌套深的 XML 中优势明显。它还支持 XPath、命名空间和更细粒度的事件控制(比如只监听特定 tag)。
但遇到超长文本节点(如内嵌 Base64 数据或大段 CDATA),默认会报错 XMLSyntaxError: internal error: Huge input lookup。必须加参数:parser=lxml.etree.XMLParser(huge_tree=True) 才能绕过安全限制。
立即学习“Python免费学习笔记(深入)”;
其他实用点:
- 用
elem.drop_tree()替代clear(),能更彻底地释放内存(包括父引用) - 若需按路径筛选(比如只取
//record/author/name),先编译xpath = lxml.etree.XPath("..."),再对每个elem调用xpath(elem),避免重复解析 - 不要在循环里反复调
elem.find(),改用elem.xpath(".//field")[0]更快(前提是已用lxml)
xmltodict 流式回调真能省内存?看清楚 item_depth 和 item_callback
xmltodict 的卖点是“像 JSON 一样操作 XML”,但它默认的 parse() 仍是全量加载。真正流式靠的是 parse(..., item_depth=2, item_callback=handler) 这组参数。
其中 item_depth 指从根往下数第几层开始触发回调,item_callback 是你写的函数,接收 (path, item_dict)。注意:
- 这个
item_dict是字典,不是 Element 对象,无法再查子节点或调 XPath - 如果目标节点嵌套太深或路径不固定,
item_depth很难设准,容易漏数据或误触发 - 它内部仍用
expat做底层解析,性能不如lxml.iterparse,但开发速度快
别忽略:真正吃内存的不是标签,是没被清理的引用链
很多同学写了 iterparse 循环,也调了 clear(),但内存还是涨。常见原因有三个:
- 没清空根节点的子元素列表:处理完一个
item后,root里还留着它的引用,得手动del root[0]或定期root.clear() - 把
elem存进了全局 list 或 dict(比如results.append(elem)),导致整个子树无法 GC - 在
process_single_item()里用了ET.tostring(elem)或转成 dict,这会复制全部文本内容,瞬间放大内存压力
最稳妥的做法:所有业务逻辑只取 elem.text、elem.attrib、elem.find("xxx").text 这类原始值,处理完立刻 clear(),全程不保留 elem 引用。


















