XML遍历中,深度优先(DFS)是“一路到底再回头”,广度优先(BFS)是“一层一层扫”;ElementTree的iter()是DFS实现,手动用deque实现BFS。

什么是XML深度优先和广度优先遍历
深度优先(DFS)是“一路到底再回头”:从根节点出发,选一个子节点钻进去,直到没子节点了才退一层,换另一个分支;广度优先(BFS)是“一层一层扫”:先处理所有一级子节点,再统一处理所有二级子节点,依此类推。这两种不是XML专属概念,而是树结构通用遍历策略——ElementTree 的 iter() 是 DFS,而用队列手动实现的是 BFS。
用 root.iter() 做深度优先遍历(最常用)
Python 标准库 xml.etree.ElementTree 的 iter() 方法就是开箱即用的 DFS 实现,按深度优先顺序返回所有后代元素(含自身),无需递归写法,也自动处理任意嵌套层级。
- 它返回的是迭代器,内存友好,适合大文件
-
root.iter()遍历全部节点;root.iter('book')只遍历指定标签,过滤更干净 - 注意
elem.text可能为None,务必判空再.strip(),否则报AttributeError - 属性通过
elem.attrib获取,是普通字典,可直接遍历键值对
import xml.etree.ElementTree as ET
tree = ET.parse('books.xml')
root = tree.getroot()
for elem in root.iter():
tag = elem.tag
text = elem.text.strip() if elem.text else ''
attrs = elem.attrib
print(f"{tag}: {text} | attrs={attrs}")
手动实现广度优先遍历(需队列)
ElementTree 本身不提供 BFS 接口,但用 Python 内置 collections.deque 很容易手写。BFS 对“按层级批量处理”场景更自然,比如导出为表格时想先取所有 ,再统一提取各列字段。
- 别用 list 模拟队列(
.pop(0)是 O(n)),必须用deque保证 O(1) 出队 - 遍历时要跳过非元素节点(如文本、注释),只处理
Element类型 - BFS 不天然保留父子路径信息,如需定位,得自己维护层级或路径字符串
from collections import deque import xml.etree.ElementTree as ETdef bfs_traverse(root): queue = deque([root]) while queue: elem = queue.popleft() print(f"Level-{len(elem.tag.split('/'))}: {elem.tag}")
只把 Element 子节点入队(跳过文本、注释等)
for child in elem: if hasattr(child, 'tag') and child.tag is not None: queue.append(child)tree = ET.parse('data.xml') bfs_traverse(tree.getroot())
深度 vs 广度:选哪个?关键看你要什么
多数日常解析(提取所有
、收集全部id属性)直接用iter()就够了——它快、短、稳。只有当你明确需要“同一层级的节点一起处理”,或者要做层级校验(比如要求所有下必须有且仅有 3 个),才值得上 BFS。
- DFS 天然支持路径回溯(递归调用栈隐含路径),适合构建 JSON-like 嵌套结构
- BFS 更利于并行化或分批处理,但 ElementTree 没内置支持,得自己搭轮子
- 遇到命名空间(
{http://...}tag)时,DFS 和 BFS 都一样要先处理前缀映射,别指望遍历方式能绕过这个问题
真正容易被忽略的,不是选 DFS 还是 BFS,而是 elem.text 和 elem.tail 的分工:前者是标签内开头文本,后者是标签闭合后的文本——混在一起取会漏内容,分开处理又容易重复。需要纯文本时,别硬拼,老实用 etree.tostring(elem, method='text', encoding='unicode').strip()(需 lxml)或写个安全递归提取函数。









