最稳方法是先用 findall('.//old_name') 收集目标节点再统一改 tag,避免边遍历边修改;处理命名空间需 register_namespace 并用完整 uri 格式更新 tag;批量处理时用 pathlib 遍历并异常隔离。

用 xml.etree.ElementTree 批量改节点名最稳
Python 标准库的 xml.etree.ElementTree 足够胜任,不用装第三方包。它不解析 DTD 或命名空间,默认忽略注释和空白文本,对多数配置类 XML 安全、轻量、够快。
关键不是“怎么改”,而是“改完别破坏结构”——比如改了 tag 但漏掉子节点的 tail 文本,或误改了属性里的字符串值。
- 只改
Element.tag,不动Element.text和Element.tail - 递归遍历时用
iter()或iterparse(),避免手动写多层for child in root:嵌套 - 如果 XML 有命名空间(如
{http://example.com/ns}node),必须提前处理前缀,否则find('oldname')找不到
递归修改所有匹配节点:先找再改,别边遍历边改
直接在 for elem in root.iter(): 循环里改 elem.tag 是安全的,但如果你用 root.findall() + for,得注意:返回的是实时视图,不影响遍历本身;但若用 getchildren()(已弃用)或手动索引,可能因树结构变化出错。
更稳妥的做法是先收集所有目标节点,再统一改名:
立即学习“Python免费学习笔记(深入)”;
import xml.etree.ElementTree as ET
<p>tree = ET.parse('config.xml')
root = tree.getroot()</p><h1>收集所有 tag 为 'old_name' 的节点</h1><p>targets = root.findall('.//old_name') # .// 表示任意深度
for elem in targets:
elem.tag = 'new_name'</p><p>tree.write('config_new.xml', encoding='utf-8', xml_declaration=True)
-
.//old_name比//old_name更可靠(后者在某些旧版本中不支持) -
tree.write()默认不写 XML 声明,加xml_declaration=True才有<?xml version='1.0' encoding='utf-8'?> - 中文路径或含中文内容时,务必指定
encoding='utf-8',否则 Windows 下容易乱码
遇到命名空间怎么办:别硬匹配,用 register_namespace + 带前缀查找
如果原始 XML 有类似 <root xmlns:ns="http://example.com/ns"><item>...</item></root> 的结构,直接查 'item' 会失败——因为实际 tag 是 {http://example.com/ns}item。
正确做法是注册前缀,然后用带前缀的 XPath:
ET.register_namespace('ns', 'http://example.com/ns')
# 然后查找时用
targets = root.findall('.//ns:old_name', namespaces={'ns': 'http://example.com/ns'})
for elem in targets:
elem.tag = '{http://example.com/ns}new_name'
- 改
tag时必须用完整 URI 形式({uri}new_name),不能只写'new_name',否则命名空间丢失 -
namespaces参数只影响查找,不影响写入;写入时靠register_namespace()控制输出前缀 - 如果不确定有没有命名空间,先打印
elem.tag看一眼,常见坑是看到{...}xxx还以为是字符串字面量
批量处理多个文件:用 pathlib 遍历 + 异常隔离
别用 os.walk 拼路径,pathlib 更直观;更重要的是,单个文件解析失败不该中断整个流程。
from pathlib import Path
import xml.etree.ElementTree as ET
<p>xml_dir = Path('data/xmls')
for xml_file in xml_dir.rglob('*.xml'):
try:
tree = ET.parse(xml_file)
root = tree.getroot()
for elem in root.findall('.//legacy_tag'):
elem.tag = 'modern_tag'
tree.write(xml_file.with_suffix('.new.xml'), encoding='utf-8', xml_declaration=True)
except ET.ParseError as e:
print(f"Parse failed {xml_file}: {e}")
except Exception as e:
print(f"Other error {xml_file}: {e}")
-
rglob('*.xml')自动递归,比glob('**/*.xml')更明确 - 每个文件单独 try/catch,防止一个坏文件导致全部跳过
- 写新文件用
with_suffix(),避免手拼字符串出错(比如.xml.xml)
改名本身很简单,难的是判断哪些该改、哪些不该动——比如 value 是标签名还是属性值?<name>John</name> 里的 name 是要改的节点,但 <user name="John"></user> 里的 name 是属性,不能碰。动手前先用 print([elem.tag for elem in root.iter()][:10]) 快速扫一遍结构。










