CSV 能读出来还不够:别把空字符串、缺列和字段内换行混在一起

举报
yd_294604443 发表于 2026/11/03 09:23:12 2026/11/03
【摘要】 导入 CSV 时,逗号不一定是分隔符,一行文本也不一定是一条记录。本文用 Python 标准库的本地合成实验,说明引号、换行和字段数量分别影响什么,并给出一个可运行的结构校验示例。

摘要:导入 CSV 时,逗号不一定是分隔符,一行文本也不一定是一条记录。本文用 Python 标准库的本地合成实验,说明引号、换行和字段数量分别影响什么,并给出一个可运行的结构校验示例。

业务明细导入常从 split(",") 开始。样例只有编号、名称、金额时,这种写法似乎够用;名称中一旦出现逗号,列就错位。更隐蔽的是,有些错误不会抛异常,而是悄悄改变数据含义。

先区分物理行和逻辑记录

本次样例中的 "a,b" 是一个字段,"said ""hi""" 则表示包含双引号的文本。标准库 csv.reader 分别读出 a,b 和 said "hi";直接按逗号切分第一条数据,会得到四段,而预期只有三列。

字段还可以包含换行。本次文件使用 CRLF,一条备注跨两行,因此四个物理行只对应表头和两条数据记录。先调用 splitlines() 再交给解析器,虽然仍能读出记录,却把备注中的换行丢掉,得到 line1line2。解析没有报错,内容已经改变。

读取真实文件时应写 open(path, encoding="utf-8", newline=""),让 CSV 模块处理换行。本次按这种方式读取,字段内保留 \r\n;使用默认的 newline=None,文本层会将它转换为 \n。是否允许规范化应由数据约定决定,不能把两者当作字节完全一致。

空字段有位置,缺字段没有位置

默认读取行为下,1,,10 中间的空字段和 2,"",20 中间的带引号空字段,都会得到空字符串。尾部逗号也有意义:3,tail, 提供三个字段,最后一个为空;6,only_two 只提供两个字段。

本次用 DictReader 读取短行,缺少的金额自动填成 None;多出的列则默认放到键 None 对应的列表里。这是解析器的容纳策略,并没有替导入程序确认结构有效。若再写 value or "",缺列和明确留空就会被合并。

需要固定三列时,可以先用列表读取,核对表头及每条记录的列数,再映射成字典:

import csv
import io


def load_csv(text):
    expected = ["id", "note", "amount"]
    reader = csv.reader(io.StringIO(text, newline=""), strict=True)
    if next(reader, None) != expected:
        raise ValueError("unexpected header")
    result = []
    for record_no, fields in enumerate(reader, 2):
        if len(fields) != len(expected):
            raise ValueError(
                f"record {record_no}: expected 3 fields, got {len(fields)}"
            )
        result.append(dict(zip(expected, fields)))
    return result


if __name__ == "__main__":
    source = 'id,note,amount\r\n1,"a,b",10\r\n2,"line1\r\nline2",20\r\n'
    for row in load_csv(source):
        print(repr(row["note"]))

输出为 'a,b' 和 'line1\r\nline2'。这里的 record_no 从表头后的第二条逻辑记录开始计数,并非文件物理行号。表头采用精确匹配,列顺序改变、重复列名或空文件都会被拒绝;这是示例的输入契约。

严格模式仍需要业务校验

本次未闭合引号触发 csv.Error,短行、多列及重复表头由示例主动拒绝。但 strict=True 仍接受未加引号字段中的 ab"cd,也接受金额 not_a_number。因此它不能视为所有 CSV 规范的完整验证器,更不会检查金额、必填项或编号唯一性。

实际导入应在结构确认后,再约定空字符串是否有效、各列如何转换和错误如何定位。示例一次收集全部记录,适合解释小型固定格式文件;大文件还需另行设计大小限制、流式处理及失败后的提交规则。

例如,备注可留空而金额必填时,应分别校验这两列,不能只看整行字段数为三就接受。示例在内存中构建列表,遇到异常不会返回部分结果;若改为边读边入库,前面已写入的记录不会因后续解析失败自动撤回,需要另行安排事务或暂存机制。

2026年11月3日,本实验在 Windows、Python 3.12.14 下实际运行五组观察和四组预期拒绝用例,另执行文中代码。五组观察及代码退出0;四组坏输入退出2并产生预期错误,表示拒绝行为符合验收条件。原始输出、退出码和文件指纹已本地保存。全部数据均为合成内容,未调用云服务,也未验证生产导入流程。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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