本地文件用ElementTree.parse(),网络XML字符串用fromstring();命名空间需显式声明或用".//tag"模糊匹配;text为空时应strip()判断,含子标签用itertext()拼接。

ElementTree.parse() 和 ElementTree.fromstring() 用哪个?
数据来源决定了一切。本地磁盘上的文件,直接调用 parse() 就行,它只认文件路径或类文件对象。但如果你是从网络请求拿到的 XML 字符串——比如 response.text——就得用 fromstring()。搞混了?那等着 ParseError: not well-formed (invalid token) 吧,因为 parse() 根本不接受纯字符串。
parse("data.xml"):直接读磁盘,返回ElementTree实例fromstring(response.text):处理网络响应体,返回根Element- 如果手滑用了
requests.get().content(bytes 类型),先 decode 成 str 再传给fromstring(),否则TypeError: expected str, bytes or os.PathLike object会立刻教你做人
查不到节点?小心命名空间(namespace)
很多 XML 都带着命名空间,尤其 RSS、SOAP、Atom 这些老面孔。比如 ,这时候你直接写 root.find("item") 肯定返回 None——因为默认 namespace 是空字符串,而实际节点藏在另一个 namespace 下。这不是 bug,是标准行为,但确实容易让人抓狂。
- 先提取 namespace:用
root.tag.split("}", 1)[0][1:]或者正则r'xmlns="(.*?)"'粗略抓一下(不严谨,但多数场景够用) - 查节点时必须带上前缀:
ns = {"rss": "http://purl.org/rss/1.0/"}; root.find("rss:item", ns) - 想省事?暴力法:
root.find(".//item")直接跳过命名空间——但注意,这是 XPath 的模糊匹配,性能略低,而且可能抓到深层嵌套的同名节点,小心误伤
text 属性为空?可能是换行或空白字符
XML 里换行和缩进会被解析成文本节点,所以 elem.text 经常是 "\n " 而不是你期望的内容。这真不是 bug,是标准行为——但坑得人不少。
- 别直接写
if elem.text:,改成if elem.text and elem.text.strip()才靠谱 - 想安全取值?写个辅助函数:
safe_text(elem): return elem.text.strip() if elem is not None and elem.text else "" - 如果节点内容里还嵌着子标签(比如
),Hello world text只覆盖第一个文本片段,后面的得用elem.itertext()拼接起来
用 findall() 还是 iter()?性能和语义差别明显
findall() 只查直接子节点,iter() 则深度遍历整棵树——用错场景很容易出事,尤其当结构不确定的时候。
- 明确层级关系:比如所有
item都在channel下,那就用root.find("channel").findall("item"),清晰又高效 - 不确定嵌套深度:比如要抓任意位置的
link,用list(root.iter("link")),但注意它比findall()慢 2–3 倍——小 XML 无所谓,但到了万级节点就得留个心眼 - 避免
root.findall(".//item"):写法虽然短,但底层也是递归扫描,性能跟iter()差不多,可读性反而更差,没必要
解析 XML 最容易卡住的从来不是语法,而是命名空间和空白文本这些隐式行为。一旦遇到 None 或空字符串,先盯住这两点,比重写整个解析逻辑快得多。