用 xml.etree.ElementTree 合并 XML 文件应逐个追加子元素而非拼接字符串,需用 deepcopy 避免引用问题,显式处理命名空间,并用 ET.indent 美化输出;大文件须用 iterparse 流式处理防 OOM。

用 Python 的 xml.etree.ElementTree 合并多个 XML 文件
直接用标准库最稳妥,不用额外装包,也避免 lxml 在某些环境(如 Alpine 容器、旧 CentOS)的编译问题。关键不是“拼接字符串”,而是把源文件的根子元素逐个追加到目标树中。
常见错误是调用 tree.getroot().append(child_root) 后发现子元素的命名空间丢失或属性错乱——这是因为 ElementTree 默认不保留原始命名空间前缀,且 append() 不自动处理父级 nsmap。
- 先用
ET.parse(filename)读取每个文件,拿到root - 对每个
root,用copy.deepcopy(root)避免后续修改影响原树(尤其当多个文件有同名节点需重命名时) - 若源文件含命名空间(如
xmlns="http://example.com/ns"),需在目标根节点初始化时显式传入nsmap,否则子节点的xmlns属性会被忽略 - 合并后调用
ET.indent(tree, space=" ", level=0)(Python 3.9+)美化输出;旧版本可用第三方xml.dom.minidom回写
import xml.etree.ElementTree as ET from copy import deepcopydef merge_xml_files(file_list, output_path): if not file_list: return
以第一个文件为基准构建目标树
base_tree = ET.parse(file_list[0]) base_root = base_tree.getroot() # 复制其余文件的根下所有子元素(跳过根本身) for fpath in file_list[1:]: tree = ET.parse(fpath) root = tree.getroot() for child in root: base_root.append(deepcopy(child)) # 写出 base_tree.write(output_path, encoding="utf-8", xml_declaration=True)XML 到字典/JSON 的映射:处理重复标签与属性冲突
很多工具(比如
xmltodict)默认把同名子节点转成 list,但若某节点只出现一次,它就变成 dict——这种不一致会让后续代码频繁判空或用isinstance(..., list),容易漏 case。更麻烦的是属性和文本内容共存:
映射成什么结构?不同库策略不同,29.99 xmltodict默认塞进@currency和#text,而dictor可能扁平化成price_currency和price_text。
- 统一用
xmltodict.parse(xml_str, force_list=("item", "entry"))强制指定哪些标签必须为 list,哪怕只出现一次 - 用
process_namespaces=True保留命名空间信息,否则{http://...}title会变成难以匹配的键名 - 若需自定义映射逻辑(例如把所有
@unit属性转为小写后缀),别依赖库的自动转换,改用ElementTree+ 手写遍历,在iter()过程中按需构造 dict
用 XSLT 实现带条件的数据映射(比如字段重命名、值转换)
当映射规则复杂(如 “把 转成 ,其他值转 0”),硬编码解析易出错且难维护。xslt 是专为此设计的,且主流语言都支持(Python 用 lxml,Java 用 javax.xml.transform)。
注意:XSLT 1.0(最广泛兼容)不支持正则,字符串处理能力弱;XSLT 2.0+(需 saxon 或 lxml 启用 EXSLT)才有 replace() 和 tokenize()。生产环境优先选 1.0,除非明确需要高级文本函数。
- XSLT 文件里用
实现标签重命名 - 用
做条件映射,比在 Python 里写一堆if/elif更清晰可测 - 执行时传入外部参数(如
base_url)用,避免把路径硬编码进 XSLT
大 XML 文件合并时的内存与性能陷阱
单个 500MB 的 XML 文件用 ElementTree.parse() 会直接 OOM;即使拆成多个 50MB 文件,全 load 进内存再合并也不现实。这时候必须流式处理。
iterparse() 是唯一靠谱选择,但它不保证事件顺序,且对嵌套层级深的结构容易漏节点——比如你监听 start 事件来捕获 ,但没等收到对应 end 就提前清理了上下文,会导致数据截断。
- 用
iterparse(filename, events=("start", "end")),只在end事件时处理完整节点,避免中间状态干扰 - 对每个
end事件,检查elem.tag == "record"且elem.getparent() is not None(防顶层节点误判) - 处理完一个
record后立即调用elem.clear()并删除其所有子节点引用,否则内存不会释放 - 不要试图用
iterparse构建完整新树——只提取你需要的字段,转成 CSV 行或插入数据库,绕过“合并 XML”这个动作本身
真正难的不是语法,是搞清你到底要“合并 XML”还是“合并 XML 里的数据”。前者是格式操作,后者才是实际需求。多数时候,后者更合理,也更容易避开各种解析器的边界 case。










