应使用 xml.etree.ElementTree.iterparse() 流式解析超大 XML,及时调用 elem.clear() 和 parent.remove(elem) 释放内存,禁用 DTD/实体解析,并避免 findall/XPath、保留引用及全量树操作。

用 xml.etree.ElementTree.iterparse() 流式解析,别用 ET.parse()
直接加载整个超大 XML(比如几个 GB)进内存,ET.parse() 会瞬间 OOM。必须放弃“全量树结构”思路,改用事件驱动的流式解析。iterparse() 是标准库里唯一能边读边处理、及时释放内存的方案——它只把当前正在处理的节点及其子节点保留在内存中,父节点处理完即可被 clear() 掉。
常见错误是只调用 iterparse() 却不手动清理:
- 没调
elem.clear()→ 子元素持续堆积,内存照涨不误 - 在循环里反复
root.append(elem)或保留对已处理元素的引用 → 树结构隐式重建,等于白做 - 忽略命名空间前缀,导致
elem.tag包含{http://...}tag,用字符串匹配失败
解析时主动 clear() 已处理节点,尤其注意父节点
iterparse() 返回 (event, elem) 元组,但 elem 的父引用默认一直存在。即使你只关心 end 事件,也要在处理完一个完整业务单元(比如一个 <record></record>)后,显式清除其所有子节点,并手动解除父引用。
典型安全写法:
立即学习“Python免费学习笔记(深入)”;
for event, elem in ET.iterparse(file_path, events=("start", "end")):
if event == "start" and elem.tag == "record":
# 开始一个新 record,先清空上一个(如果存在)
if current_record is not None:
current_record.clear()
current_record = elem
elif event == "end" and elem.tag == "record":
# 处理完这个 record,提取数据、写入 DB 或 CSV
process_record(elem)
# 关键:清空 elem 的全部子节点,再移除其在父节点中的引用
elem.clear()
# 如果 elem 不是根节点,还需从父节点中 detach(Python 3.9+ 可用 elem.remove())
if elem.getparent() is not None:
elem.getparent().remove(elem)
避免 findall() 和 XPath,改用标签名和属性条件判断
在流式解析中调用 elem.findall("child") 或 elem.find(".//deep/nested") 会强制加载整棵子树,破坏内存控制。XPath 更是灾难——它需要完整 DOM 上下文。
正确做法是靠状态机 + 标签名 + 属性过滤:
- 用栈记录当前路径(如
["root", "section", "item"]),根据start/end事件 push/pop - 检查
elem.tag和elem.attrib.get("type") == "valid"这类轻量判断 - 对文本内容,优先用
elem.text.strip(),而非elem.xpath("text()") - 如果必须多层嵌套提取,用嵌套的
if elem.tag == "A": ... elif elem.tag == "B": ...分支,而不是递归遍历
性能关键:关掉 DTD 和外部实体解析,预编译正则(如需清洗)
默认情况下,xml.etree.ElementTree 会尝试解析 DTD 和外部实体(即使文件里没有),这会触发网络请求或本地文件读取,不仅慢还危险。必须显式禁用:
parser = ET.XMLParser(resolve_entities=False, dtd_validation=False)
for event, elem in ET.iterparse(file_path, events=("start", "end"), parser=parser):
...
另外,如果解析后要对文本做清洗(比如去 HTML 实体、删空白),别在每次循环里调用 re.sub(r"\s+", " ", text) —— 正则编译开销大。提前定义:CLEAN_SPACES = re.compile(r"\s+"),然后用 CLEAN_SPACES.sub(" ", text)。
真正卡住的往往不是解析本身,而是后续的数据转换。比如把百万级 <price>123.45</price> 转成 float 再存 Pandas DataFrame,不如先写入临时 CSV,最后用 pd.read_csv() 批量加载。


















