CSV 编码的噩梦:UTF-8、Shift-JIS 与乱码
每个处理 CSV 文件的团队最终都会收到一份长得像 "?????" 或 "\\\\u00e9\\\\u00e9\\\\u00e9" 的客户负载。编码在生产者到你的代码之间某个环节被搞坏了,没人能说清原始的是 UTF-8、Shift-JIS、Latin-1 还是更冷门的东西。本文是我们希望第一天就拥有的现场指南:你将看到的四种失败模式、每种的视觉特征、以及对应的修法。
四种「坏」的样子
CSV 文件到达时编码错误基本就是这四种样子。每种在屏幕上长得不一样,修法也不同。先识别视觉签名是第一步。
-
被错认为 UTF-8 的乱码。生产者用 UTF-8 写了日文 / 中文 / 韩文;你按 Latin-1 或 Windows-1252 读。字节是合法的 UTF-8,标签被标错了。你看到的是一串高位字符,看着像垃圾——日文「日本語」变成
日本文,或中文「中文」变成䏿–‡——每个可见字符其实是两到三个 UTF-8 字节被当作单个 Latin-1 字符解码。修法:检测编码(encoding-japanese 库对日 / 中 / 韩 有不错的检测器;其余用 mozilla 的 chardet),重新按 UTF-8 解码。 -
非 ASCII 全部变成问号。生产者写的就是 UTF-8;你或下游工具把每个非 ASCII 字节替换成
?。这是 Windows 工具走非 Unicode 代码路径读 UTF-8 时会发生的情况——比如 cmd.exe 在 CP936 下读 UTF-8 文件并丢掉高位字节。无法恢复,原字符已丢。修法:追溯生产者,修它的读路径。做不到就请生产者重新导出。 -
开头的 BOM 出现在第一列里当成字面字符。Excel 保存 UTF-8 文件会带三个字节的 BOM(0xEF 0xBB 0xBF)。不剥 BOM 的朴素 CSV 读取器看到的第一列表头是 "\uFEFFname" 而不是 "name"。修法:在解析前剥掉流开头的 BOM 字节。
-
日文文件里日元符号与反斜杠混淆。Microsoft CP932(Windows 用的 Shift-JIS 变体)把字节 0x5C 映射成
¥而不是\。一份日文 CSV 里写 Windows 路径分隔符C:¥Users¥me,导入到按\切分的 UTF-8 流水线后,路径段会全是空。修法:要么把文件当 Shift-JIS 处理,把 0x5C 归一化回\;要么让生产者从 Excel 以 UTF-8 重新导出(并在文件里只用 POSIX 路径)。
真正能跑的流水线
经历了足够多的生产事故后,我们对每份 CSV 摄入都固定了下面的操作顺序:
-
读原始字节,不读字符串。绝不让你的 CSV 阅读器以默认文本模式打开文件。文件的第一件事是一个字节序列,可能是 BOM 也可能不是;只有字节流告诉真相。
-
跑一个编码检测。日 / 中 / 韩 用 encoding-japanese 较可靠;欧洲与拉丁字母用 chardet(Python)或 jschardet(Node.js)。信任检测器的置信分,但也要肉眼检查文件(用十六进制编辑器打开或跑
xxd file.csv | head);检测器通常对,但并非永远对。 -
只解码一次。用检测出的编码把原始字节转成 JS / Python / Java 字符串。从这一步起,只用字符串;再也不碰字节。
-
如果存在就把 BOM 从第一个字符剥掉。解码后,BOM 变成位置 0 的单个 Unicode 字符 U+FEFF。删掉。
-
在前 8 KB 解码后的文本里嗅探分隔符。统计双引号外的
,、;、\t、|出现次数。能跨多行稳定给出相同列数的那个胜出。 -
逐字符解析走完引用字段。永远不要用正则按分隔符切;引用区域当作不透明,直到看到匹配的闭合引号。手写解析器第一次都做错这一条,会悄悄破坏含有字面分隔符的字段。
-
最后校验行数与列数。每一行的字段数必须相同。否则文件就坏了——生产者引号不一致、分隔符选错、或编码步骤破坏了一个恰好是 ASCII 的字节。
日文细节
如果处理日文客户的数据——财务导出、POS 日志、EDI 报文——编码故事独立存在,值得多花一段。
日文 Windows 自带三种都自称「日文」的编码:
- Shift-JIS(微软叫 CP932):日文 Windows 历史上的默认;覆盖 JIS X 0208 字符集加上厂商扩展汉字与 IBM 的 NEC 扩展。
- EUC-JP:90 年代 Unix 圈的默认;现代导出很少见。
- UTF-8:正确答案但不是默认;除非用户在「另存为」对话框里显式选,Excel 不保存 UTF-8 CSV。
三种之间的检测是 encoding-japanese 库存在的全部理由。一旦解码,下游最常见的 bug 就是上面描述的 ¥ / \ 混淆。还有两个常见怪相:
- 同一列里出现全角数字与 ASCII 数字。Excel 给印刷设计的产品写
2026(全角),但给技术 SKU 写2026(ASCII)。下游校验在数字解析前必须先 NFKC 归一化字符串。 - Wave dash 与 tilde。
〜(U+301C,wave dash)是日文 Windows 会写的;~(U+007E,tilde)是世界其他地方用的。多数比较代码把它们当成相等——查一下你的代码,因为这个差异会在 URL 匹配和邮箱校验上翻车。
工具与往返
CSV 清洗工具实现了上面流水线的每一步;它在文件加载时自动检测编码、让你确认或覆盖分隔符,并提供 UTF-8 和 Shift-JIS 两种下载按钮。这意味着从日本财务系统扔进来的文件能直接吐成一段干净的 UTF-8 流喂给 SQL 加载器,无需额外处理。
始终优先选用能让你下载同一份文件两种编码的工具。找不到的话,下策就是跑一遍上面步骤的一次性脚本——关键是这个脚本必须用含非 ASCII 字符的第一行来测试,因为编码 bug 会藏在第二行,直到第二行出现某个加载器关心的字符才暴露。
收尾
如果你只从本文带走一件事:永远不要让你的 CSV 阅读器以文本模式打开文件。字节流才是真相;字符串是下游。工具:CSV 清洗工具、CSV 编码指南,以及给非 ASCII 负载准备的 encoding-japanese 检测器。