Python CSV 导入校验:从“读得出来”到“数据可信”

举报
yd_239005675 发表于 2026/10/11 17:45:05 2026/10/11
【摘要】 本文以学习计划导入为例,讲解 Python csv 模块的编码处理、表头校验、字段校验和失败路径设计,帮助构建更可靠的 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 辅助编写,代码和测试数据为独立构造的技术示例。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。