用 Python 标准库检查 CSV:重复表头、错列行与错误汇总

举报
yd_210400787 发表于 2026/11/01 08:22:15 2026/11/01
【摘要】 使用 Python 标准库 csv 检查 CSV 的 UTF-8 BOM、重复表头、错列、必填字段和正整数时长,汇总错误并给出可运行脚本与样例输出。

CSV 看起来像“用逗号分隔的文本”,但合法字段也可以包含逗号、换行和引号。例如 "北京,上海" 是一个字段,直接调用 line.split(",") 会把它拆成两列。更稳妥的做法是交给 Python 标准库 csv 解析,再单独验证业务规则。本文用一个任务清单演示:先检查表头,再逐行报告错列、空值和无效数字。

示例数据

把下面内容保存为 tasks.csv:

id,title,minutes
1,备份,30
2,检查,abc
3,发布
4,复盘,40,多余
5,总结,45

这里第 2 条记录的时长不是整数,第 3 条少一列,第 4 条多一列。我们希望继续检查后续行,最后一起列出问题,而不是在遇到第一个错误时就丢失其余诊断信息。

完整脚本

将下面代码保存为 inspect_csv.py,用 Python 3 运行,无需安装第三方包:

#!/usr/bin/env python3
"""Validate a small task CSV with Python's standard library."""

import csv
import sys
from collections import Counter
from pathlib import Path


REQUIRED = ("id", "title", "minutes")


def inspect(path: Path) -> tuple[int, list[str]]:
    with path.open(encoding="utf-8-sig", newline="") as handle:
        reader = csv.reader(handle, strict=True)
        header = next(reader, None)
        if header is None:
            raise ValueError("CSV 文件为空")

        duplicates = [name for name, count in Counter(header).items() if count > 1]
        if duplicates:
            raise ValueError(f"重复表头:{', '.join(duplicates)}")
        missing = [name for name in REQUIRED if name not in header]
        if missing:
            raise ValueError(f"缺少必需列:{', '.join(missing)}")

        accepted = 0
        errors: list[str] = []
        for row in reader:
            line_number = reader.line_num
            if not row or all(not cell.strip() for cell in row):
                continue
            if len(row) != len(header):
                errors.append(
                    f"第 {line_number} 行:应有 {len(header)} 列,实际 {len(row)} 列"
                )
                continue

            record = dict(zip(header, row))
            if not record["id"].strip() or not record["title"].strip():
                errors.append(f"第 {line_number} 行:id 和 title 不能为空")
                continue
            try:
                minutes = int(record["minutes"])
            except ValueError:
                errors.append(f"第 {line_number} 行:minutes 必须是整数")
                continue
            if minutes <= 0:
                errors.append(f"第 {line_number} 行:minutes 必须大于 0")
                continue
            accepted += 1

    return accepted, errors


def main() -> None:
    try:
        accepted, errors = inspect(Path(sys.argv[1]))
    except (IndexError, OSError, ValueError, csv.Error) as exc:
        raise SystemExit(f"无法检查 CSV:{exc}") from exc

    print(f"有效记录:{accepted}")
    for error in errors:
        print(error)
    if errors:
        raise SystemExit(1)


if __name__ == "__main__":
    main()

执行 python3 inspect_csv.py tasks.csv,我在本地运行得到:

有效记录:2
第 3 行:minutes 必须是整数
第 4 行:应有 3 列,实际 2 列
第 5 行:应有 3 列,实际 4 列

有效的两条记录来自第 2 行和第 6 行。脚本在发现行级错误后以退出码 1 结束,方便在自动化流程中阻止有问题的文件继续流转。没有错误时退出码为 0。

为什么要先检查表头?

csv.reader 返回字段列表,不会替业务判断 id、title 和 minutes 是否存在。脚本先用 Counter 找重复表头,再检查必需列。重复表头尤其容易被忽视:如果直接用 dict(zip(header, row)) 转字典,同名列的前一个值会被后一个覆盖。先拒绝重复列名,才能避免静默丢数据。脚本允许额外的非重复表头,但每条数据仍须与表头的总列数一致。

文件以 encoding="utf-8-sig" 打开,是为了兼容开头带 UTF-8 BOM 的 CSV;否则第一个列名可能变成 \ufeffid,看起来是 id,实际却匹配不上。newline="" 则让 csv 模块自己处理换行,尤其适合含引号换行的字段。

行号和错误边界

reader.line_num 计数的是读取到的物理行,本例每条记录占一行,因此提示与编辑器行号一致。若某个被引号包裹的字段跨了多行,它指向该记录读取结束时的行号,不应误写成“第几条记录”。空行会跳过;缺列或多列会报告但不计为有效记录;字段数正确后才检查必填值及正整数时长。

csv.reader(..., strict=True) 可以帮助发现部分格式错误,但它不会替代业务校验,所以列数和字段内容仍需显式检查。实际导入前,还可以按业务需要增加 ID 去重、时长上限、日期格式等规则;保持“解析”和“校验”两层分开,错误才容易定位和测试。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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