解析XML文件疑似内存泄漏?lxml.iterparse顺序处理大文件内存暴涨
问题根因
这不是传统意义的内存泄漏,是lxml iterparse的使用逻辑缺陷导致的非目标节点内存累积:
- 你为
iterparse指定了tag参数后,迭代器只会返回匹配该标签的start/end事件,其余所有非目标标签的节点不会进入你的循环逻辑,你也就没有机会对这些节点执行清理操作。 - lxml的
iterparse默认会在内存中构建完整的DOM树,哪怕你不处理非目标标签,这些节点依然会被加载到内存中持续累积。 - 第一个函数运行正常是因为
Elements1标签在XML中出现的位置早、分布频率高,你很快就能触发清理逻辑释放已处理的节点内存;如果Elements2标签在XML中出现的位置非常靠后,或者分布非常稀疏,那么在第一次匹配到Elements2的end事件之前,XML前面几GB的所有节点都会被加载到内存中没有释放,最终导致内存溢出崩溃。
另外你代码中存在拼写错误:if len(elments) > 0 少了一个字母e,应为elements,这个问题会导致最后一批未满chunk size的数据无法正常入库。
解决方案
方案1:优化单标签解析的清理逻辑(适合必须分开执行函数的场景)
不要为iterparse指定tag参数,手动判断标签是否匹配,保证所有节点的end事件都能被捕获,无论是否是目标标签都执行清理:
def __get_elements(self, target_tag): # 不指定tag参数,捕获所有节点的end事件 context = ET.iterparse(tmp_folder + file_path, events=("end",)) elements = [] try: for event, element in context: # 先判断是否是目标标签,是则处理数据 if element.tag == target_tag: elements_result = tuple(child.text for child in element) elements.append(elements_result) if len(elements) == self.chunk_size: # 插入数据库 elements.clear() # 无论是不是目标标签,都执行清理操作 element.clear() # 清理父节点中已处理的前置节点 parent = element.getparent() if parent is not None: while parent.getprevious() is not None: del parent.getparent()[0] # 插入剩余数据 if elements: # 插入数据库 pass except Exception as e: logging.error(e)
两个标签的解析可以直接调用同一个方法传不同的target_tag即可,不用重复写两份代码,也不需要手动调用gc.collect(),Python自动回收机制足够处理临时对象。
方案2:单次遍历处理所有标签(推荐,性能最优)
你现在顺序执行多个函数等于要多次读取9GB的XML文件,IO开销极大,更合理的做法是一次遍历同时处理所有目标标签:
def parse_all_elements(self): target_tags = {"Elements1": [], "Elements2": []} context = ET.iterparse(tmp_folder + file_path, events=("end",)) try: for event, element in context: tag = element.tag if tag in target_tags: elements_result = tuple(child.text for child in element) target_tags[tag].append(elements_result) # 对应标签的数据达到chunk size就入库 if len(target_tags[tag]) == self.chunk_size: # 将target_tags[tag]的数据插入对应数据库表 target_tags[tag].clear() # 通用清理逻辑 element.clear() parent = element.getparent() if parent is not None: while parent.getprevious() is not None: del parent.getparent()[0] # 插入各标签剩余的未满块数据 for tag, data in target_tags.items(): if data: # 插入对应数据库表 pass except Exception as e: logging.error(e)
这个方案只需要读取一次XML文件就能完成所有标签的解析,IO开销减少数倍,也彻底避免了多次遍历时的内存累积问题。
内容的提问来源于stack exchange,提问作者Minura Punchihewa
相关产品推荐
相关产品推荐

