跳到主要内容
AZ Tools

处理 CSV 文件而不把它弄坏

CSV 看起来是计算领域最简单的格式:值、逗号、换行。可实际上,它是悄无声息毁掉最多数据的格式,因为它没有唯一的规范,每个生成方的选择都略有不同。几乎所有 CSV 问题都出自少数几种歧义,一旦你能把它们叫出名字,解法就显而易见了。

值里面的逗号,以及引号规则

一旦某个值包含了分隔符,格式就需要转义,惯例是把该字段用双引号括起来。被括起来的字段内部若出现引号,则写两遍。这就是为什么姓名字段里的 Smith, John 会呈现为 "Smith, John",也是含引号的值里引号会成对出现的原因——那不是笔误,是转义规则。

因此,按逗号切分一行 CSV 是行不通的,无论这看起来多么诱人。正确的解析器会跟踪自己此刻是否处在引号内部,而被引号括起的字段里合法地可以包含逗号、引号,甚至换行。如果一个文件从中途开始突然多出几列,几乎总是因为某个值里有未转义的分隔符。

分隔符并不总是逗号

在以逗号作小数点的地区,表格软件通常改用分号写出 CSV,于是在一台机器上完美打开的文件,到另一台机器上就全挤进了一列。制表符也很常见,而且正因为值里极少出现制表符,它反而更安全。

由于分隔符在文件里任何地方都没有声明,工具只能去猜,通常是逐个试探候选、看哪个能得到一致的列数。导入出问题时,检查分隔符是最快能排除的一项;而当你能控制输出端时,用制表符分隔可以整类地规避这个问题。

编码,以及那个想要 BOM 的表格软件

本该是 é 的位置显示成 é,说明一个 UTF-8 文件被当成传统单字节编码读取了。CSV 不携带编码声明,读取方要么被告知、要么只能猜,而猜错会在结构完好的前提下把所有非 ASCII 字符搅烂——这正是这类损坏经常一路存活进数据库的原因。

常见的元凶是 Excel:在很多系统上,除非文件以 UTF-8 字节序标记开头,否则它就假定为传统代码页。写入 BOM 能让带重音的字符在那里正确打开,代价是开头多出几个不可见字节,某些严格的解析器会把它当作第一列列名的一部分交还给你。事先知道这一点能省下困惑的半小时。

表格软件会悄悄改写的值

CSV 没有类型——一切都是文本——所以含义由读取方决定,而表格软件在这件事上很激进。邮政编码或商品编码被当成数字读取时,前导零就消失了。长标识符变成科学计数法。看起来像日期的字符串会按机器的区域设置重新排版,同一个文件因此在两个办公室里得出不同结果。

稳妥的做法是在**导入的那一刻**就把这类列指定为文本,而不是事后再修,因为转换一旦发生,原始数字就已经没了。凡是字符本身要精确保留的东西——标识符、电话号码、各类编码——都应把自动类型识别视为要关掉的功能,而不是稍后再纠正的问题。

把 CSV 搬到别的形态

转成 JSON 会带来真正的类型和嵌套,让数据更容易校验——但它也迫使你明确解决上述歧义,所以宜早不宜迟。反过来,从 CSV 生成 SQL 插入语句时,同样的转义问题以更危险的形式回来了:值里的引号必须按 SQL 的规则转义,而不是按 CSV 的。

在信任任何 CSV 之前的简短检查:

  • 解析前先确认分隔符——逗号、分号还是制表符。
  • 确认编码;若文件将交给 Excel,则添加 BOM。
  • 不要天真地按分隔符切分;要尊重被引号括起的字段。
  • 把标识符类的列以文本方式导入,保住前导零。
  • 转换后检查行数与列数,而不只是看前几行。

相关工具