xml.etree.elementtree是解析配置类xml最轻量够用的选择,适合无命名空间、结构扁平的config.xml等文件;需注意bom、编码、重复标签、文本混合、命名空间等问题,并合理转为嵌套字典和json。

用 xml.etree.ElementTree 解析 XML 是最轻量且够用的选择
Python 标准库的 xml.etree.ElementTree(常简写为 ET)能处理绝大多数配置类 XML:结构扁平、无命名空间、不依赖 DTD 或 Schema。它不校验合法性,但速度快、无额外依赖,适合读取 config.xml、web.xml、AndroidManifest.xml 这类人工编写的配置文件。
别一上来就选 lxml——除非你明确需要 XPath 2.0、XSLT 或处理带命名空间的复杂 XML;否则多一个 C 依赖、多一层安装失败风险,纯属加戏。
常见错误现象:ParseError: not well-formed (invalid token),通常是因为 XML 中混入了 UTF-8 BOM、控制字符,或用了中文全角标点(如“”代替"")。先用 file -i config.xml 确认编码,再用 open(... , encoding='utf-8-sig') 读取。
把 Element 递归转成嵌套字典时,注意重复标签和文本混合问题
XML 没有“数组”原语,但配置文件里常出现多个同名节点(如多个 <server></server>),直接转字典会覆盖。也不能简单把 .text 当值——比如 <port>8080</port> 的值是 "8080",但 <desc>API <b>v2</b> endpoint</desc> 的 .text 只是 "API ",.tail 和子元素会丢失。
实操建议:
- 对同级同名标签,统一转为 list(哪怕只有一个也要包成
[...]),后续 JSON 序列化才不会歧义 - 忽略
.tail和注释(Comment类型),配置文件里极少靠它们存关键信息 - 只保留
.text非空且无子元素的叶子节点作为字符串值;否则留空字典{}或跳过 - 属性(
elem.attrib)默认挂到对应节点下"@attributes"键里,避免和子节点名冲突
示例片段:
使用ydata-profiling(前身为pandas-profiling)生成全面的数据质量报告,包含相关性分析、缺失值模式和基数检测。导出交互式HTML仪表板和JSON摘要。
def elem_to_dict(elem):
result = {}
if elem.attrib:
result["@attributes"] = elem.attrib
children = list(elem)
if not children:
if elem.text and elem.text.strip():
return elem.text.strip()
return result
for child in children:
child_data = elem_to_dict(child)
if child.tag not in result:
result[child.tag] = child_data
else:
# 已存在同名键 → 转为 list
if not isinstance(result[child.tag], list):
result[child.tag] = [result[child.tag]]
result[child.tag].append(child_data)
return result
用 json.dumps(..., indent=2, ensure_ascii=False) 输出可读 JSON
配置文件常含中文、路径、正则等非 ASCII 内容。ensure_ascii=False 是刚需,否则输出 "\u4f60\u597d" 这种没法直接看的玩意儿;indent=2 让结果可读,方便人工核对或提交进 Git。
注意点:
- 不要用
sort_keys=True——XML 顺序有意义(如<bean></bean>加载顺序),JSON 键序不保证,强行排序反而掩盖问题 - 如果原始 XML 有数字型内容(如
<timeout>30</timeout>),elem.text仍是字符串;是否转 int/float 得按 schema 判断,不能全局int()——否则<version>2.1.0</version>就崩了 - 输出前用
json.loads(json.dumps(...))反序列化一次,能提前暴露不可 JSON 化的对象(比如误塞了datetime实例)
遇到带命名空间的 XML(如 Spring Boot 的 beans)要主动处理 find 和 iter
像 <beans xmlns="http://www.springframework.org/schema/beans"></beans> 这种,默认 root.find("bean") 会返回 None,因为实际全名是 {http://www.springframework.org/schema/beans}bean。
解决方法只有两个:
- 解析时传
parser=ET.XMLParser(resolve_entities=False)并在查找时显式带上命名空间字典:ns = {"x": "http://www.springframework.org/schema/beans"},然后用root.find("x:bean", ns) - 更省事的是预处理:用正则去掉所有
xmlns.*?=属性(仅限你确认不依赖命名空间语义的配置场景)
别试图用 ET.register_namespace()——它只影响输出,不影响查找逻辑。
真正容易被忽略的是:很多企业内部 XML 配置看似没声明命名空间,但用 root.tag 打印出来可能是 {http://xxx}config,说明解析器已自动识别并注入。务必先打印 root.tag 和 list(root)[:2] 看一眼真实结构,再动手写转换逻辑。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










