CSV

Comma-Separated Values 的缩写,一种用逗号分隔数据的文本格式。广泛用于表格数据交换。

CSV (Comma-Separated Values) 是把每个字段用逗号分隔、每条记录用换行分隔的简单文本格式。它广泛用于与表格软件、数据库之间交换数据,也正因为简单,几十年来一直保持着标准数据交换格式的地位。

整理了 CSV 书写格式的文档是 RFC 4180 (2005 年 10 月),但它是作为 Informational 发布的,正文里明确写着"不规定任何类型的互联网标准"。也就是说 CSV 没有一份具有强制力的统一规范,实际上各软件的方言依然留存。RFC 4180 整理的内容包括:记录的分隔是 CRLF;字段内含有逗号、换行或双引号时,要用双引号把整个字段括起来,内侧的双引号写成两个;以及 text/csv 这个媒体类型和它的可选参数 charset 与 header (取值为 present / absent)。之后 RFC 7111 以追加"用 URL 的片段标识符指向特定行或列"的记法的形式更新了它。有没有标题行,从格式本身无法判别,只能交给交接双方的约定,这一点在实务中相当麻烦。

分隔符不是逗号而是制表符时称为 TSV (Tab-Separated Values),用分号分隔的 CSV 在欧洲也很常见。这是因为有些区域设置用逗号作小数点,而表格软件会依据区域设置来决定分隔符,所以同一个文件在不同环境下有时挤在 1 列里、有时又能正确分开。对方的环境不明确时要分发文件,明确写出分隔符和编码更安全。

日语环境下字符编码的问题很常见。用 Excel 打开 CSV 时,不带 BOM 的 UTF-8 文件可能出现乱码,因此有些场合会要求输出 Shift_JIS 或带 BOM 的 UTF-8。BOM (Byte Order Mark) 是把 U+FEFF 以 UTF-8 编码后的 3 个字节 (EF BB BF),加在文件开头,Excel 就能正确识别为 UTF-8。Python 的 csv 模块和 JavaScript 的各种库都可以控制编码与 BOM (Python 在写出时指定 encoding="utf-8-sig")。

除了乱码,还有一类陷阱是用表格软件打开的那一刻值就被改写。开头是 0 的数字串 (邮政编码或商品编号) 会丢掉开头的 0,1-2、2026/8 这样的值会被解释成日期,位数多的数值会变成指数表示。CSV 本身是不带类型的文本,这些转换都源自打开一侧的推测。打开之后覆盖保存,转换后的值就原样留了下来,所以交接路径中要经过表格软件时,稳妥的做法是导入时把列的类型指定为"文本",或者干脆约定不打开。还有一点,以 =、+、-、@ 开头的字段有可能被当成公式执行,把外部输入原样输出成 CSV 的功能需要注意安全问题。

CSV 与 JSON 如何分工,是实务中经常讨论的话题。CSV 适合表格数据,文件体积小,还能直接用 Excel 打开。而 JSON 可以表达嵌套结构和类型信息,是 API 响应格式的主流。一般的分工是:处理大量表格数据用 CSV,交换结构化的数据用 JSON。

解析 CSV 时,应对边界情况很重要。字段内的换行、转义后的双引号、空字段与 null 的区分、末尾逗号的处理等等,简单的 split(',') 无法正确处理的情况有很多。例如把 "a,b",c 这一行 (2 个字段) 交给 split(','),会裂成 '"a' / 'b"' / 'c' 这 3 个元素,而按规范解释应当是 a,b 与 c 两个字段。错位的不是行而是列,所以只做条数检查发现不了异常。推荐使用可靠的 CSV 解析库 (Python 的 csv 模块、JavaScript 的 Papa Parse 等)。按行分割也一样,不考虑字段内的换行就按换行切分,1 条记录会裂成多行。

从字符计数的角度看,CSV 的逗号和引号这些分隔符会影响数据体积。字段数越多逗号就越多,需要用引号括起来的字段越多,额外开销就越大。数据量大的时候,CSV 的开销往往比 JSON 少,但字段内含有很多逗号或换行时就不一定了。

分享这篇文章