XML深度和广度是什么 如何遍历XML树

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

什么是XML深度优先和广度优先遍历

深度优先(DFS)是“一路到底再回头”:从根节点出发,选一个子节点钻进去,直到没子节点了才退一层,换另一个分支;广度优先(BFS)是“一层一层扫”:先处理所有一级子节点,再统一处理所有二级子节点,依此类推。这两种不是XML专属概念,而是树结构通用遍历策略——ElementTree 的 iter() 是 DFS,而用队列手动实现的是 BFS。

root.iter() 做深度优先遍历(最常用)

Python 标准库 xml.etree.ElementTreeiter() 方法就是开箱即用的 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 ET

def 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.textelem.tail 的分工:前者是标签内开头文本,后者是标签闭合后的文本——混在一起取会漏内容,分开处理又容易重复。需要纯文本时,别硬拼,老实用 etree.tostring(elem, method='text', encoding='unicode').strip()(需 lxml)或写个安全递归提取函数。