使用pd.read_xml高效解析6000+ XML Spreadsheet 2003文件为Pandas DataFrame
使用pd.read_xml高效解析6000+ XML Spreadsheet 2003文件为Pandas DataFrame
我太懂你这种要处理一大堆特殊XML格式Excel文件的头疼了——6000+文件,还要高效转成Pandas DataFrame,还指定要用pd.read_xml不想直接碰lxml,完全是实际生产环境的刚需,性能绝对是核心。
先给你拆解下问题:这些是微软的XML Spreadsheet 2003格式,自带命名空间,这也是pd.read_xml最容易踩坑的地方,搞定命名空间和正确的XPath,剩下的就顺理成章了。
第一步:搞定命名空间与XPath定位
首先,这个XML里的核心命名空间是urn:schemas-microsoft-com:office:spreadsheet,我们先把它定义成字典,方便pd.read_xml识别:
namespaces = {'ss': 'urn:schemas-microsoft-com:office:spreadsheet'}
然后,数据都在名为Details的工作表下的<Table>标签里,每个<Row>对应一行数据,每行的4个<Cell>分别对应ShortName、FieldCode、Date、Value四列。我们用XPath精准定位到这些行:
- 定位目标工作表的所有行:
//ss:Worksheet[@ss:Name="Details"]/ss:Table/ss:Row - 再给每行的4个单元格分别指定提取路径,映射到表头列名。
第二步:单个文件解析代码
先写单个文件的解析逻辑,验证正确后再批量处理:
import pandas as pd def parse_single_xml(file_path): # 定义命名空间 ns = {'ss': 'urn:schemas-microsoft-com:office:spreadsheet'} # 读取XML并提取数据 df = pd.read_xml( file_path, namespaces=ns, # 定位到Details工作表下的所有行 xpath='//ss:Worksheet[@ss:Name="Details"]/ss:Table/ss:Row', # 为每列指定从行内提取的XPath elems=[ './ss:Cell[1]/ss:Data', # 第一列:ShortName './ss:Cell[2]/ss:Data', # 第二列:FieldCode './ss:Cell[3]/ss:Data', # 第三列:Date './ss:Cell[4]/ss:Data' # 第四列:Value ], # 对应列名 names=['ShortName', 'FieldCode', 'Date', 'Value'] ) # 跳过第一行(表头行),重置索引 df = df.iloc[1:].reset_index(drop=True) # 处理空值:把None转为空字符串(可选,根据你的需求调整) df = df.fillna('') return df # 测试单个文件 test_df = parse_single_xml('your_sample_file.xml') print(test_df.head())
运行后就能得到你期望的四列DataFrame,每个数据行完美对应原XML里的内容。
第三步:6000+文件的高效批量处理
批量处理的核心是减少内存开销+避免重复操作,这里用pathlib遍历文件,列表推导式收集所有DataFrame,最后一次性合并:
from pathlib import Path # 所有XML文件的目录 xml_dir = Path('path/to/your/xml/files') # 收集所有文件的DataFrame(用列表推导式比循环append快很多) all_dfs = [parse_single_xml(file) for file in xml_dir.glob('*.xml') if file.is_file()] # 一次性合并所有DataFrame(比循环concat高效N倍) final_df = pd.concat(all_dfs, ignore_index=True) # 保存结果(可选,比如存为Parquet格式,比CSV快很多,适合大数据量) final_df.to_parquet('all_combined_data.parquet', index=False)
性能优化小贴士
针对6000+文件的规模,这几个细节能帮你省不少时间:
- 预加载依赖:确保你的Pandas是用C扩展的lxml编译的(默认安装的都是),这是解析速度的基础。
- 避免循环concat:列表推导式收集所有DataFrame后一次性concat,避免每次concat都复制整个DataFrame。
- 分块处理(内存不足时用):如果内存放不下所有DataFrame,每1000个文件合并一次,保存为中间Parquet文件,最后再合并中间文件:
chunk_size = 1000 files = list(xml_dir.glob('*.xml')) for i in range(0, len(files), chunk_size): chunk_files = files[i:i+chunk_size] chunk_df = pd.concat([parse_single_xml(f) for f in chunk_files], ignore_index=True) chunk_df.to_parquet(f'chunk_{i//chunk_size}.parquet', index=False) # 合并所有chunk final_df = pd.concat([pd.read_parquet(f) for f in xml_dir.glob('chunk_*.parquet')], ignore_index=True) - 跳过不必要的解析:XPath里精准定位到
Details工作表,避免解析其他无关内容,减少解析时间。
验证结果
你可以对比解析后的DataFrame和原XML数据,比如第一行数据应该是:
| ShortName | FieldCode | Date | Value |
|---|---|---|---|
| CHF vs EUR | Calculate | yes |
完全符合你的期望。
备注:内容来源于stack exchange,提问作者JM Nel




