Python CSV 导入校验:从“读得出来”到“数据可信”
CSV 常用于报表导出、学习计划备份和批量配置。文件能被成功打开,并不代表里面的数据可以直接进入应用:表头可能重复,某条记录可能少一列,字符串 false 也可能被错误地当成真值。本文用一个学习计划导入示例,把读取、结构校验和业务转换分开处理。
1. 先定义输入契约
示例约定文件使用 UTF-8 编码,允许文件开头带 UTF-8 BOM;分隔符为英文逗号;第一条记录必须是 name,minutes,completed,顺序固定。名称不能为空,分钟数必须能转换成大于零的整数,完成状态只能填写 true 或 false,忽略大小写和首尾空白。
示例数据:
name,minutes,completed
“阅读论文,记录结论”,45,false
完成练习,30,true
第一条名称本身含有逗号,所以被引号包围。直接调用 split(‘,’) 会把它拆成额外字段,应让 CSV 解析器处理引号和分隔符,再单独检查业务要求。
2. 明确编码和换行行为
打开输入文件时使用 encoding=“utf-8-sig”,可以在解码时去掉文件开头的 UTF-8 BOM,也可以读取普通 UTF-8 文件。它不是通用编码检测器;如果来源明确使用 GBK,应按来源协议选择对应编码。Python 官方 codecs 文档说明了 utf-8-sig 的 BOM 行为。
打开 CSV 文件时还应指定 newline=“”,让 CSV 模块处理换行细节;这对带引号字段内包含换行的情况尤其有用。不要使用 errors=“ignore” 丢弃无法解码的字节,否则名称或配置值可能被静默修改。
3. 一个可复用的导入函数
下面的函数只使用标准库,先验证表头,再逐条完成结构和业务校验:
import csv
from pathlib import Path
FIELDS = ["name", "minutes", "completed"]
def load_plan(path: Path) -> list[dict[str, object]]:
records = []
with path.open("r", encoding="utf-8-sig", newline="") as stream:
reader = csv.DictReader(stream, strict=True)
if reader.fieldnames != FIELDS:
raise ValueError("表头必须依次为 name,minutes,completed")
for number, row in enumerate(reader, start=1):
prefix = f"第 {number} 条数据记录"
if None in row or any(value is None for value in row.values()):
raise ValueError(f"{prefix}:字段数量与表头不一致")
name = row["name"].strip()
if not name:
raise ValueError(f"{prefix}:name 不能为空")
try:
minutes = int(row["minutes"].strip())
except ValueError as exc:
raise ValueError(f"{prefix}:minutes 必须是整数") from exc
if minutes <= 0:
raise ValueError(f"{prefix}:minutes 必须大于零")
completed = row["completed"].strip().lower()
if completed not in {"true", "false"}:
raise ValueError(f"{prefix}:completed 必须是 true 或 false")
records.append({"name": name, "minutes": minutes, "completed": completed == "true"})
return records
DictReader 在默认配置下,会把多出来的字段放到 None 键下,把缺失字段的值设为 None,因此代码同时检查这两种情况。strict=True 用于让解析器报告部分格式错误,但它不会替代字段数量和业务规则校验。
完成状态也不能直接使用 bool(row[“completed”]):非空字符串 false 的布尔值仍然是真,必须按约定显式转换。
4. 验证失败路径,再接入应用
应覆盖普通 UTF-8、带 BOM、CRLF 换行、引号内逗号和换行,以及重复表头、多列、少列、空名称、非整数、非正数、非法完成状态和未闭合引号。把分钟数改成 0,应得到“必须大于零”的错误;把完成状态改成 yes,应得到具体字段提示。
错误信息中的“第几条数据记录”不等于文件物理行号,因为一个带引号的字段可以跨行。需要定位原文件时,可以另外记录 reader.line_num;它表示解析器已经读取的行数,Python csv 官方文档对两者做了区分。
函数先在内存中收集全部记录,验证完成后才返回。对于小型学习计划,调用方可以等函数成功返回后再替换现有数据,避免第五条记录失败时前四条已经写入。大文件则应结合分批校验、暂存区或数据库事务设计导入过程。
输入契约、解析规则和业务规则各自承担明确职责,才能让导入失败可定位、成功可解释。只处理“文件能读”,往往会把更难发现的数据错误留给后续页面或数据库。
写作说明:本文由 AI 辅助编写,代码和测试数据为独立构造的技术示例。
- 点赞
- 收藏
- 关注作者
评论(0)