← 返回博客首页

电子表格格式选型:CSV、XLSX、Parquet 的类型丢失与编码陷阱

电子表格格式的选择,看起来是个「导出时点下拉框」的小事,实际是数据在系统之间流动时会不会悄悄变形的问题。类型丢失、编码错乱、容量截断这三类故障有一个共同点:导出时不报错,导入时也不报错,只有在三个月后有人翻数据时才发现。

本站的 电子表格格式速查 给了各格式的静态属性对比,本文补上的是决策链条:面对一个具体的数据流,该选什么、为什么、以及出问题时怎么定位。

第一个决策点:谁来读这份数据

格式选择的第一个分岔不是「哪种格式更好」,而是这份数据的读者是谁。

给人看(运营要打开、销售要核对、客服要查)—— 用 XLSX。单元格有类型、可以带公式、有多张表,Excel 打开就能用。这是 CSV 无法提供的。

给程序读(下游 ETL、数据库导入、另一个服务消费)—— 优先 Parquet/Arrow,规模小时用 XLSX。

只做中间格式(系统 A 导出、系统 B 导入)—— CSV 仍然是最通用的交换格式,但必须显式约定编码与分隔符,并在文档里写明。

这个分岔决定了很多后续细节。给程序读的 CSV 可以放心扁平化,因为没人会手工打开它;给人读的 CSV 就要考虑 Excel 打开时的类型猜测问题。

类型丢失:CSV 的根本局限

CSV(Comma-Separated Values)这个名字就说明了它的定位——分隔符分隔的值。它没有类型系统、没有多表概念、没有元数据。每个字段就是一串字符。

这带来一串连锁反应。假设你的数据里有这些值:

原始数据 CSV 里的实际内容 Excel 打开后
订单号 00123 5 个字符 123,前导零消失
日期 2024-06-01 10 个字符 日期值,按本机格式重排
布尔 true 4 个字符 布尔值
64 位 ID 19 位数字 科学计数法,精度丢失

注意最后一行:这不是显示问题,是数据本身被改了。19 位的数字被截成 15 位有效数字,无法还原。

往返转换(导出再导入)会叠加这些损失。站内 Excel 转 CSV 与 Excel 转 JSON 的差别正在这里——JSON 有类型,CSV 没有。

决策规则很简单:只要需要保类型就不能用 CSV。 具体做法有三个:

  1. 改用 XLSX(最省心,但失去纯文本的可 grep 性)
  2. 把类型编进数据(日期写成 ISO 8601 带时区,ID 写成字符串并在表头声明)
  3. 用 JSON Lines(每行一个 JSON 对象,类型完整且仍可流式处理)

编码与 BOM:中文乱码的根因

编码问题的复杂度在于没有统一默认值。同一个 Excel,在不同系统上另存的编码不同:

  • 中文 Windows:GBK
  • 英文 Windows / macOS / Linux:UTF-8

再加上一个关键细节:不带 BOM 的 UTF-8 文件,Excel 双击打开时识别不可靠。于是有两种翻车方向:

  • A 系统(Windows 中文)导出 GBK 无 BOM → B 系统(Linux)按 UTF-8 读 → 中文乱码
  • A 系统导出 UTF-8 无 BOM → 对面用 Excel 打开 → 也乱码

它们的共同点是都没有声明。所以稳健方案是:

导出统一用 UTF-8 with BOM。 BOM 是一段固定的开头字节(EF BB BF),接收方靠它就能确定编码。Excel 与现代工具都能正确识别,唯一代价是某些老程序会把 BOM 当成一个多余字符(这时用 strip BOM 处理一次即可)。

读取端则要先探测再解码:

raw = open('data.csv', 'rb').read()
if raw.startswith(b'\xef\xbb\xbf'):
    text, encoding = raw[3:].decode('utf-8'), 'utf-8-sig'
else:
    text, encoding = raw.decode('gbk'), 'gbk'   # 明确声明,而不是猜

最后那行注释是关键:不要靠猜。GBK 与 UTF-8 在短文本上视觉接近,猜错的概率不低。把编码写进交换文档,比任何自动检测都可靠。

除了编码,分隔符也有地区差异(逗号 / 分号 / 制表符),法国的 Excel 默认用分号。RFC 4180 规定 CSV 用逗号,遇到分号时整个文件会被当成单列。

容量:Excel 的硬限制与静默截断

Excel 的限制是硬性的:

  • 单表 1,048,576 行 × 16,384 列
  • 单元格最多 32,767 字符

超过之后的行为取决于工具,这是最危险的地方:pandas 会抛异常,但某些导出库会静默截断,只写出前 1,048,575 行而用户毫无察觉。文件能打开、格式正确、数据少了最后一行——这种故障可能几个月都发现不了。

超出限制时怎么选:

  • 超过百万行 → Parquet(列式压缩,类型完整)
  • 只是表格太宽 → 拆成多张表或转长格式(每行一个字段)
  • 需要人读且数据量不大 → XLSX(可以分 sheet)

体积上,扫描件类的 PDF 有 95% 是图片,压缩 PDF 的本质也是压图片。同样的思路适用于表格:对占比最大的那一部分下手。一个 20MB 的 CSV 里如果日期列占了 8MB(因为格式冗长),把它改成 2024-06-01 就能省下大半。

什么时候该换格式

Parquet 的价值在规模上来之后才显现:

  • 列式存储:分析查询通常只用 2–3 列,Parquet 只读这些列,速度差一个数量级
  • 压缩率高:同质量数据约为 CSV 的十分之一
  • 类型随文件保存:不存在 CSV 的类型丢失

经验阈值是单文件超过百万行或超过 1GB。在此之前,Parquet 增加的心智成本(人类无法直接 cat 看看里面是什么)与工具链复杂度大于收益。

中间还有 Arrow —— 内存中的列式格式,适合需要零拷贝转换的场景(Parquet → DataFrame 直接映射,不用解压)。

自查清单

  • [ ] 明确这份数据的读者是人还是程序,据此选格式
  • [ ] 需要保类型时不选 CSV(或把类型编进数据)
  • [ ] 导出用 UTF-8 with BOM,读取先探测 BOM
  • [ ] 交换文档里写明编码与分隔符,不靠猜
  • [ ] 导出后核对行数是否与源数据一致(防静默截断)
  • [ ] 数据量超过百万行时评估 Parquet

可复现的实测结果

用本站的 Excel 转 CSV 与 Excel 转 JSON 处理同一份含前导零与日期的表格,可以直接观察到类型差异:CSV 输出里 00123 就是 00123(若用 Excel 打开会变 123),JSON 输出里它保持字符串类型并在还原时仍带前导零。


广告

常见问题

为什么 CSV 里的 00123 打开后变成了 123?

因为 CSV 只存字符串,不存类型。文件里躺着的就是 `00123` 这五个字符,**是 Excel 打开时猜错了类型**——它看到全是数字就按数值解析,前导零被丢弃。同类问题还有一串:日期 `2024-06-01` 可能被识别成日期再按你本机的显示格式重排(原本期望的格式就变了)、`TRUE` 变成布尔值、超过 15 位的整数 ID 变成科学计数法(精度直接丢失)。根治办法只有一个:**需要保类型就不能用 CSV**。要么改用 XLSX(Excel 原生格式,单元格有类型),要么把类型信息编进数据本身——日期写成 `2024-06-01T00:00:00Z`、ID 写成字符串并在表头声明。

中文乱码到底是谁的错?

两边都有责任,但根因是**缺少编码声明**。具体链路:中文 Windows 上的 Excel 另存 CSV 默认用 **GBK**,而 Mac 与 Linux 默认用 **UTF-8**;更麻烦的是不带 BOM 的 UTF-8 文件,Excel 双击打开时识别并不可靠。于是典型翻车是:A 系统(Windows)导出 GBK 无 BOM,B 系统(Linux)按 UTF-8 读 ⇒ 中文乱码;反向则是 A 导出 UTF-8 无 BOM,对面用 Excel 打开 ⇒ 同样乱码。稳健做法是**导出统一用 UTF-8 with BOM**(Excel 与现代工具都能正确识别),读取时先探测前三个字节(EF BB BF)再决定解码器;实在要兼容 GBK 老系统,就把编码写进交换文档而不是靠猜。

Parquet 什么时候值得引入?

**先别急着上。** 如果你的数据量在几万行以内、且需要人能直接打开查看,CSV 或 XLSX 完全够用,Parquet 的二进制格式反而让人无法直接检查内容。Parquet 的价值在**规模上来之后**:列式存储只读取用到的列(分析查询通常只用 2–3 列,收益立竿见影)、压缩率高一个数量级、类型信息随文件保存(不存在 CSV 那种类型丢失)。经验阈值是**单文件超过百万行或占位数超过 1GB** 时值得引入;在此之前,Parquet 增加的心智成本与工具链复杂度大于收益。中间还有一类选择是 Arrow(内存格式),适合需要零拷贝转换的场景。

← 返回博客首页