elementtree默认解析带命名空间xml时会丢数据,因未声明命名空间映射导致find()/findall()找不到节点;必须显式传入namespaces字典并用前缀查询,如root.findall('.//ns:item', namespaces),且xpath不支持//顶层写法和复杂函数。

遇到嵌套深、命名空间多的XML时,ElementTree默认解析会丢数据
默认用 ET.parse() 或 ET.fromstring() 加载带命名空间的XML,所有带前缀的标签(如 <item></item>)会被当作普通标签名处理,导致 find()、findall() 找不到节点。这不是bug,是命名空间未声明的必然结果。
解决方法不是硬编码前缀,而是显式传入命名空间映射:
namespaces = {'ns': 'http://example.com/schema'}
root = ET.parse('data.xml').getroot()
items = root.findall('.//ns:Item', namespaces)
注意:findall() 的路径里必须用前缀(如 ns:Item),不能写完整URI;namespaces 字典键是前缀,值是URI,二者必须严格匹配XML中声明的 xmlns:ns="..."。
find() 和 xpath 表达式能力差异大,别混用
ElementTree 支持的XPath是子集,不支持 // 跨层级任意匹配(除非用 .//),也不支持函数如 contains()、starts-with()。常见误用是写 root.find('//Item')——这根本不会生效,因为 find() 只查直接子节点,且不支持顶层 //。
-
find('Child'):只找一级子节点叫Child的第一个 -
find('.//Child'):从当前节点向下递归找任意深度的Child -
find('ns:Child', ns_map):带命名空间时必须传namespaces参数 - 真要复杂查询(比如属性含某字符串、多个条件and/or),要么用
iter()+ Python过滤,要么换lxml
文本内容和子元素混在一起时,text 和 tail 容易取错
XML里像 <p>开头文本<b>加粗</b>结尾文本</p> 这种结构,<p></p> 的 text 是“开头文本”,<b></b> 的 tail 是“结尾文本”。很多人只读 text,漏掉 tail,导致内容拼接不全。
安全提取全部文本的方法是:
def get_all_text(elem):
texts = [elem.text or '']
for child in elem:
texts.append(child.tail or '')
texts.append(get_all_text(child))
return ''.join(texts)
更轻量的做法是用 elem.itertext(),它返回生成器,自动按文档顺序产出所有文本节点(包括 text 和 tail):
''.join(elem.itertext()).strip()
修改XML后写回文件,编码和换行容易出乱码或格式崩塌
直接 tree.write('out.xml') 默认用ASCII编码,中文会报错;且不保留原有缩进,所有标签挤成一行。这两个问题必须手动干预。
- 写入时指定编码:
tree.write('out.xml', encoding='utf-8', xml_declaration=True) - 若需保留可读格式,
ElementTree本身不提供美化功能,得自己加换行和缩进。最简方案是用xml.dom.minidom中转:
rough_string = ET.tostring(root, encoding='utf-8')
reparsed = minidom.parseString(rough_string)
with open('out.xml', 'w', encoding='utf-8') as f:
f.write(reparsed.toprettyxml(indent=' '))
但注意:minidom 会在每行末尾加空格,且可能插入多余空行——生产环境若对格式敏感,建议用 lxml.etree 的 pretty_print=True。
命名空间声明位置、空元素闭合方式(<tag></tag> vs <tag></tag>)、属性顺序这些细节,ElementTree 不保证与原文一致,改写后做diff要心里有数。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











