xml.etree.ElementTree解析发票XML最稳妥:用ET.parse()读文件并处理编码,命名空间需显式声明字典,金额日期须清洗转换,字段映射用字典硬对齐,批量入库用executemany()。

用 xml.etree.ElementTree 解析发票XML最稳妥
Python 标准库的 xml.etree.ElementTree 是处理结构清晰、无命名空间或命名空间可控的发票 XML 的首选。它内存占用低、无需额外安装、对常见嵌套结构(如 Invoice → LineItem → Amount)解析直接。别一上来就用 lxml——除非你明确需要 XPath 2.0、XSLT 或处理带复杂前缀的命名空间。
常见错误是直接调用 ET.fromstring() 却忽略编码声明或 BOM 头,导致解析报错 UnicodeDecodeError 或节点取空。务必先用 open(..., encoding='utf-8-sig') 读文件,或对字符串做 .encode('utf-8').decode('utf-8-sig') 清理。
实操建议:
- 用
ET.parse()代替ET.fromstring()读文件,自动处理编码声明 - 对可能缺失的字段(如
Discount),统一用elem.find('Discount').text if elem.find('Discount') is not None else '0'防止AttributeError - 避免用
.iter()全局遍历——发票结构固定,应精准定位根节点下的.findall('LineItem')
把解析结果映射到数据库表要先对齐字段类型
发票 XML 中的 Amount 看似是数字,但常含千分位逗号或货币符号(如 "¥1,234.56"),直接塞进 DECIMAL(10,2) 字段会报错。同理,InvoiceDate 可能是 "2024-03-15T09:22:13" 或 "15/03/2024",不标准化就无法入库。
立即学习“Python免费学习笔记(深入)”;
关键不是“存进去”,而是“存得准”。建议在解析后、插入前加一层清洗函数:
-
parse_currency(text):用re.sub(r'[^\d.-]', '', text)去除非数字字符,再转float或Decimal -
parse_date(text):优先匹配 ISO 格式(%Y-%m-%dT%H:%M:%S), fallback 到%d/%m/%Y,失败则设为None - 数据库字段名和 XML 标签名不一致时,用字典硬映射,例如
{'InvoiceNo': 'invoice_number', 'TotalAmount': 'total'},别依赖动态反射
批量插入时用 executemany() 而非循环 execute()
一张发票常含 5–50 行明细,若对每行都调一次 cursor.execute(),I/O 开销会指数级上升。PostgreSQL 和 SQLite 都支持 executemany(),MySQL 需确认驱动(pymysql 支持,mysql-connector-python 默认关闭批处理)。
注意两个坑:
- 传入的参数必须是
list[tuple],不是list[dict];XML 解析出的字典得用[tuple(d.values()) for d in line_items]转换 - SQLite 不支持命名占位符(
:name),必须用?,且顺序严格对应元组字段顺序 - 如果某行数据触发唯一约束冲突(如重复
invoice_number),整个executemany()会中断;需拆成小批次(如每 100 行)并捕获IntegrityError
遇到带命名空间的 XML 别硬写 tag 名
不少电子发票标准(如 UBL、CII)强制使用命名空间,比如 <id>INV-2024-001</id>。此时直接写 root.find('cbc:ID') 会返回 None——因为冒号不是合法 XML tag 分隔符,而是命名空间前缀。
正确做法是定义命名空间字典:ns = {'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'},然后所有查找都带这个参数:root.find('cbc:ID', ns)。
容易被忽略的是:同一个 XML 文件里可能混用多个命名空间(如 cbc:、cac:、ext:),必须全部声明,漏一个就会查不到节点。调试时可用 print([elem.tag for elem in root.iter()][:5]) 看实际解析出的带命名空间 tag 全名(通常是 {uri}LocalName 格式),再反推前缀映射。


















