最可控的CSV转XML方式是用csv.DictReader逐行读取并手动构建ElementTree树结构,需预先定义层级逻辑、处理字段映射与特殊字符,避免依赖XSLT或自动工具。

用 Python 的 csv + xml.etree.ElementTree 手动构建 XML
这是最可控、最常被需要的方式,尤其当 CSV 字段和 XML 结构不一一对应,或需条件生成节点、重命名字段、嵌套层级时。别指望 csv.DictReader 读出来直接塞进 ElementTree 就能成形——XML 的树状结构和 CSV 的扁平表结构天然不匹配。
关键点:
- 先定义好目标 XML 的层级逻辑(比如哪列是
,哪几列属于它的子节点/) - 用
csv.DictReader逐行读取,每行生成一个顶层元素(如),再按需创建子元素并设.text或.attrib - 注意特殊字符:XML 不允许直接写
、&等,ElementTree会自动转义,但手动拼字符串则必须用xml.sax.saxutils.escape()
import csv
import xml.etree.ElementTree as ET
root = ET.Element("data")
with open("input.csv", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
record = ET.SubElement(root, "record")
ET.SubElement(record, "id").text = row["id"]
name = ET.SubElement(record, "name")
name.text = row["full_name"]
contact = ET.SubElement(record, "contact")
ET.SubElement(contact, "email").text = row["email"]
ET.SubElement(contact, "phone").text = row["phone"]
tree = ET.ElementTree(root)
tree.write("output.xml", encoding="utf-8", xml_declaration=True)
遇到 CSV 头含空格或特殊符号时怎么处理字段映射
csv.DictReader 默认把第一行当字段名,如果原始 CSV 是 "User ID","Full Name","E-mail",那 row["User ID"] 会报 KeyError——因为键名带空格,但你代码里写的是 row["id"]。
解决方法只有两个:
- 用
fieldnames参数显式指定干净的字段名:csv.DictReader(f, fieldnames=["id", "name", "email"], skipinitialspace=True),同时加skipinitialspace=True忽略字段值前导空格 - 或在读取后做一次键名映射:
mapped = {"id": row["User ID"], "name": row["Full Name"], "email": row["E-mail"]}
别依赖 Excel 自动保存 CSV 后“看起来整齐”——它可能悄悄加了 BOM、用了非标准换行符,或把数字当文本导出导致前后空格,这些都会让 row.get("email") 返回 None 或带空格字符串。
用 XSLT + CSV 转 XML?不现实
有人想走捷径:先把 CSV 转成简易 XML(如用 csvtoxml 工具生成扁平 ),再用 XSLT 变换。这条路在小文件上看似可行,但实际踩坑密集:
- XSLT 本身不原生支持 CSV 解析,所谓“CSV 转 XML”工具往往只是按逗号硬切,遇带逗号的字段(如
"Smith, John","123 Main St")就崩 - XSLT 2.0+ 虽有
tokenize(),但处理引号包裹、换行嵌入等 RFC 4180 规范几乎不可能 - 调试困难:XSLT 错误信息极不友好,
AttributeError: 'NoneType' object has no attribute 'text'这类 Python 错误反而更容易定位
真要用 XSLT,前提是 CSV 已被严格清洗为无引号、无换行、无逗号的纯分隔格式,且字段顺序固定——这种数据现实中极少。
性能瓶颈通常不在解析,而在 XML 构建方式
处理 10 万行 CSV 时,慢的从来不是 csv.reader,而是反复调用 ET.SubElement() 创建大量小对象。如果你的目标 XML 允许,可考虑:
- 用字符串模板批量拼接(需手动处理转义):
f"{escape(row['id'])} " - 对超大文件,改用
xml.sax或lxml的iterparse流式写入,避免把整个树载入内存 - 确认是否真需要完整 XML 文档:有时下游只要符合某 DTD 片段,用
print()直接输出格式化字符串更轻量
最容易被忽略的一点:中文 Windows 下默认编码是 gbk,但 CSV 文件可能是 utf-8-sig(带 BOM)。不显式指定 encoding="utf-8-sig",csv.DictReader 会把 BOM 当作第一个字段名,导致所有 row.get("id") 都返回 None。










