DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
晚安云;香港 CN2 ¥15/月
雨云;给项目一台云服务器

pandas iterparse 读取重名 XML 节点时丢值、错列

read_xml 的 iterparse 同时读取两个同名子节点时,大文件中可能出现缺值或错列。按 row 结束事件读取可避开提前占列;最终 DataFrame 仍需容纳整表。

使用 pd.read_xml() 的 iterparse 读取几 MB 的 XML,绝大多数行正常,少数行却出现第一列为空、第一项跑到第二列,或第二项直接缺失。如果一行有两个同名子节点,并用 names 分配为不同列,应先检查重复节点的解析路径。小文件正常,不能排除这个问题。

已出现该现象的环境是 pandas 3.0.6,lxml 与 etree 都可能受到影响。pandas 3.1.0 的重复节点分支仍同时处理 start 和 end 事件,因此不能把升级到 3.1.0 当作这条路径已经修好的保证。错误行数与行宽、解析缓冲边界有关,不是固定比例。

两个同名节点为何会占错列

下面是输入结构与对应调用,rows.xml 是应用已有的本地、未压缩 XML 文件:

<data>
  <row><a>first</a><a>second</a></row>
</data>
import pandas as pd

frame = pd.read_xml(
    "rows.xml",
    iterparse={"row": ["a", "a"]},
    names=["a1", "a2"],
)

预期每行的两个 a 分别进入 a1、a2。但解析器发出元素开始事件时,文本未必已经读取完整。重复名称分支会把此时的 None 写入尚未使用的列名;等到结束事件得到真正文本,前一个列名已经被占用,数据便可能进入下一列。这个分支还有按值排除重复的判断,也不适合把相同文本的两个位置视为两个独立字段。

因此,给结果 fillna() 或向前填充会掩盖数据丢失;把 lxml 换成 etree 也不能从机制上解决。先保留原文件与有问题的行,避免错误结果覆盖唯一的数据副本。

固定结构可以在 row 结束时按位置取值

如果 XML 来自可信输入,且协议明确每个 row 恰有两个直接子节点 a,可以单独按行结束事件读取。下面的函数针对无命名空间的这个结构;发现节点数量不符就停止,不猜测字段如何补齐:

import xml.etree.ElementTree as ET
import pandas as pd

def records(path):
    with open(path, "rb") as source:
        context = ET.iterparse(source, events=("start", "end"))
        _, root = next(context)
        for event, elem in context:
            if event == "end" and elem.tag == "row":
                values = [child.text for child in elem if child.tag == "a"]
                if len(values) != 2:
                    raise ValueError("each row must contain exactly two a elements")
                yield {"a1": values[0], "a2": values[1]}
                elem.clear()
                root.clear()

frame = pd.DataFrame.from_records(records("rows.xml"))

这里在整行结束后取两个位置,不按文本是否相同去重。root.clear() 以 row 是根的直接子节点、根上没有待保留业务信息为前提;嵌套行、命名空间、混合文本或需要保留根属性的格式,应按自己的 XML 协议调整。不要把这段读取器用于未经评估的不可信 XML。

这种写法减少了已处理 XML 节点的保留,但最终 DataFrame 仍装下全部行。数据本身放不进内存时,应把记录按批交给下游,而不是认为用了生成器就没有整表内存开销。

如果文件连同解析树能安全放进内存,也可改用 read_xml 的 xpath 路径读取固定子节点,并单独核对同名元素的顺序。那会改变内存占用,不能直接拿来替换大文件的流式任务。

用跨缓冲边界的数据验收

验收要包含足够多、宽度不同的行,并让两列携带可区分的值;同时加入两项文本恰好相同的行。逐行比较两个字段、行数与缺失位置,比只统计空值更有效。合法空节点也需要单独约定,不能把所有空值都判为解析故障。

已经生成的错列表格应从 XML 原件重读。修正读取代码不会恢复先前丢掉的第二个值。