最直接方法是用xml.etree.ElementTree遍历目标节点,提取指定子元素文本或属性组合为元组存入set查重,收集待删节点再统一删除,写入时指定encoding='utf-8'和xml_declaration=True。

用 xml.etree.ElementTree 遍历并去重节点最直接
Python 标准库的 xml.etree.ElementTree 足够应付大多数 XML 去重需求,不用额外装包,也避免了 lxml 在某些环境(比如旧版 CentOS 或无编译工具的容器)里安装失败的问题。
关键不是“删重复”,而是“保留第一次出现的,跳过后续相同结构”。XML 节点是否重复,得看你怎么定义“相同”——常见做法是比对一组指定子元素或属性值的组合,而不是整个节点字符串(因为空白、顺序、命名空间可能干扰)。
- 用
for elem in root.iter('target_tag')遍历目标节点,别用findall后再循环索引,容易漏删或索引错位 - 把判断依据(比如
elem.findtext('id')或tuple(elem.get(attr) for attr in ['type', 'name']))转成不可变类型存进set,用于查重 - 不要边遍历边调用
root.remove(elem)—— 这会改变迭代器状态,导致跳过节点;改用收集待删节点列表,遍历完再统一删
按子元素文本内容去重时,注意 findtext() 和 find().text 的区别
findtext('field') 返回字符串或 None,而 find('field').text 在 find 失败时会抛 AttributeError。线上 XML 数据常有缺失字段,用 findtext 更稳。
如果字段含空格或换行,findtext 默认不 strip,可能导致看似相同的值被当成不同项。这时候得手动处理:
立即学习“Python免费学习笔记(深入)”;
- 统一用
elem.findtext('name') and elem.findtext('name').strip() - 或者提前清洗:对所有待比对字段做
.strip() or '',避免None和空串混在一起出问题 - 大小写敏感?比如
'User'和'user'算不算重复?得明确加.lower()或按业务约定处理
用属性组合当唯一键时,elem.attrib 顺序不保证,别直接用 str(elem.attrib)
elem.attrib 是字典,Python 3.7+ 虽保持插入序,但 XML 解析器(如 ElementTree)不保证属性读入顺序一致。用 str(elem.attrib) 当哈希键,同一份 XML 在不同机器上可能生成不同字符串,去重结果不稳定。
- 正确做法:选固定字段名列表,比如
['category', 'version'],然后构造元组:tuple(elem.get(k, '').strip() for k in keys) - 如果某个属性必存在,可简化为
(elem.get('id'), elem.get('scope')),元组天然可哈希且顺序确定 - 避免用
elem.items(),它返回的顺序不可靠,也不排除命名空间前缀干扰
写回文件前务必用 etree.write(..., encoding='utf-8', xml_declaration=True)
默认 write() 不带 XML 声明,且可能用系统默认编码(比如 cp1252),中文会乱码或解析失败。尤其当原始 XML 有 <?xml version="1.0" encoding="UTF-8"?> 时,输出不带声明 + 错编码 = 下游工具直接报错 UnicodeDecodeError 或 ParseError: not well-formed。
- 显式传
encoding='utf-8'和xml_declaration=True - 如果原 XML 有缩进,
ElementTree默认不保留;需要格式化就得手动加换行和空格,或改用lxml.etree的pretty_print=True(但这就绕开标准库了) - 写入前建议先
etree.tostring(root, encoding='unicode')检查逻辑是否符合预期,比直接写文件调试快
去重逻辑本身不难,难的是“怎么定义重复”——字段缺失、空格、大小写、属性顺序、编码这些细节,一个没兜住,脚本就只在测试数据上跑通。










