CSV乱码是BOM导致的?用Python从前3个字节开始排查

プログラミング・Web開発カテゴリを表すパンダのイラスト 编程・Web开发

关于本文
本文是通过利用生成式AI的自动化生成流程创建的。基于Python标准库的字符编码规范与CSV处理方式,梳理了安全排查UTF-8 BOM的步骤。

验证状态:📘 已确认官方规范・未在实机上运行所列代码

用Excel打开CSV时日语变成了乱码。遇到这种情况,直接断言是“因为没有UTF-8 BOM”为时尚早。检查前3个字节就能知道是否存在UTF-8 BOM,但仅凭没有BOM并不能确定乱码的原因。分别排查实际的字符编码、打开方式和分隔符才是捷径。

项目内容
对象负责交接CSV的业务人员、Python初学者
所需物品Python 3、待排查CSV的副本
附加库不需要(仅使用标准库)
修改对象仅读取。不修改原文件
验证范围与Python官方规范核对。未在实机上运行所列代码

首先观察3个字节

UTF-8 的 BOM(字节顺序标记)是放置在文件头部的 3 个字节的标识。十六进制表示为 EF BB BF。在 Python 中可以通过常量 codecs.BOM_UTF8 进行比较。

以下是sample.csv仅读取名为的文件的示例。请将文件名替换为您自己的测试副本。

from pathlib import Path
import codecs

path = Path("sample.csv")
with path.open("rb") as f:
    first_three = f.read(3)

print("先頭3バイト:", first_three.hex(" ").upper())
print("UTF-8 BOMあり:", first_three == codecs.BOM_UTF8)

如果是带 BOM 的 UTF-8,预期会显示 EF BB BF 和 True;如果是无 BOM 的常规 UTF-8 CSV 文件,则会显示另外的 3 个字节和 False。不过,即使文件少于 3 个字节也不会报错,程序只会对比成功读取的部分。这只是根据规范推导出的预期结果,并非写作时实际运行的日志。

从观察结果中能说明什么?

前导字节已知信息未知信息
EF BB BF包含 UTF-8 BOM后续的正文是否全部为正确的 UTF-8
除 EF BB BF 之外没有 UTF-8 BOM是 UTF-8、Shift_JIS 系列,还是其他字符编码
空可能是空文件原本预期的是哪种字符编码

BOM 是位于文件头部的标识符,并不能保证整个字符串的正确性。即使没有 BOM,UTF-8 也能正常使用。相反,即使有 BOM,文件中也可能混入非法的字节序列。

接下来确认是否可以作为 UTF-8 读取

除了判断有无 BOM 之外,还要确认能否正确解码。首先作为严格的 UTF-8 读取,如果出现问题则显示异常。

from pathlib import Path

path = Path("sample.csv")
raw = path.read_bytes()

try:
    text = raw.decode("utf-8-sig", errors="strict")
    print("UTF-8としてデコード可能")
    print("先頭の内容:", repr(text[:80]))
except UnicodeDecodeError as error:
    print("UTF-8としてデコード不可:", error)

utf-8-sig 如果头部有 UTF-8 BOM 则会跳过,如果没有则作为普通的 UTF-8 读取。这并非是删除 BOM 并覆盖原文件的处理。

即使在此处成功,也不能保证“原本的日文内容符合预期”。例如,如果已经错误地转换成其他字符编码的字符串再以 UTF-8 保存,那么即使解码成功,内容也可能已经损坏。请务必人工目测确认日文的列名和几行数值。

作为 CSV 读取时也要注意 newline

确认字符编码后,解析 CSV 时不使用字符串的 split(","),而是使用 csv.reader。这是为了处理引号内的逗号和换行符。

import csv

with open("sample.csv", "r", encoding="utf-8-sig", newline="") as f:
    reader = csv.reader(f)
    for index, row in enumerate(reader):
        print(row)
        if index >= 4:
            break

这是一个用于确认前 5 行的读取示例。如果 CSV 的分隔符是制表符或分号,请根据实际文件的规范修改为 csv.reader(f, delimiter="\t") 等。如果在行首看到 \ufeff,请检查是否把 BOM 作为字符读取进来了。

更改一处:比较 utf-8 和 utf-8-sig

用 encoding="utf-8" 和 encoding="utf-8-sig" 打开同一个文件,并比较第一个标题字符串。对于带 BOM 的文件,前者会将开头的 U+FEFF 作为字符保留,而后者则会在读取时去除 BOM。如果是无 BOM 的 UTF-8,通常两者内容相同。

不过,Excel 等应用程序如何判定 CSV,取决于产品版本和导入方法。不能断言仅凭添加 BOM 就能在所有环境下解决乱码问题。对于 Excel,请分别确认双击打开文件的方法以及在导入数据时显式指定字符编码的方法。

失败时的排查

症状需确认的事项应避免的操作
UTF-8 解码错误源系统的输出字符编码、文件传输路径盲目无依据地穷举字符编码并覆盖
解码成功但出现乱码输出端的值与 CSV 原始数据、是否存在二次转换断定仅由 BOM 引起
日语正常但列错位分隔符、引号、换行符、CSV 方言简单的逗号分割
首列名称中存在不可见字符U+FEFF 残留立即删除原始数据
仅在Excel中发生格式错乱导入方法与字符编码指定无条件仅修改Python端

业务文件请务必通过副本进行验证。切勿将包含个人信息的CSV上传至在线转换网站,这一点也很重要。由于字符编码转换有时会导致信息丢失,请将原件、转换后文件以及转换条件分开保存,在比对列数、条数以及日语代表值之后再投入使用。

常见误解

Q. 没有BOM就不是UTF-8吗?不是。在UTF-8中,BOM并非必需。

Q. 只要有BOM,Excel就一定能正确打开吗?不是。这取决于应用程序、版本、读取方式以及CSV的内容。

Q. 能判断出是Shift_JIS吗?仅凭前3个字节是无法判断的。请查阅原系统的规格,确认典型的日语及解码结果。日语Windows中使用的CP932与标准规范中的Shift_JIS也并不相同。

图解:即使弄清了BOM,调查仍在继续

如果CSV的前3个字节是EF BB BF,则可以判断带有UTF-8的BOM。但这并不证明其后面的数据全部都是正确的UTF-8。反之,即使没有BOM,也不能断定它就不是UTF-8。将初步观察中能够看出的部分与尚未明确的部分区分开来,就能避免对原因产生误判。

flowchart TD
    A["元CSVのコピーを用意"] --> B["先頭3バイトを読む"]
    B --> C{"EF BB BFか"}
    C -->|"はい"| D["UTF-8 BOM付きとして厳密に復号"]
    C -->|"いいえ"| E["UTF-8として厳密に復号"]
    D --> F{"文字として読めたか"}
    E --> F
    F -->|"はい"| G["列名・日本語の値・CSVの構造を比較"]
    F -->|"いいえ"| H["作成元の文字コード仕様を確認"]
    G --> I["Excel等の取り込み方法を調べる"]
    H --> I
    I --> J["根拠に基づき設定・変換を判断"]

图中的“解码成功”这一分支,并不等同于“恢复成了语义正确的日语”。如果之前的处理中已经把乱码字符串作为UTF-8保存了下来,那么即使解码本身成功,也无法恢复其原本的含义。在实际业务中,必须将列名、条数以及代表性数值与原系统的输出进行比对。

制作两种自己的CSV来观察差异

没必要拿业务中收到的客户数据当作教材。只使用虚拟的物品和数量,制作内容相同且分别带有BOM和不带BOM的两个文件,就能安全地比较首字节与数据内容的差异。以下代码是新建文件的示例,当同名文件存在时,可避免将其覆盖。所附代码本次未实际运行,结果为预测所得。

from pathlib import Path

text = "商品,数量\n青りんご,2\nみかん,3\n"
plain = Path("sample-utf8.csv")
bom = Path("sample-utf8-bom.csv")

for path in (plain, bom):
    if path.exists():
        raise FileExistsError(f"同名のファイルがあります: {path}")

plain.write_bytes(text.encode("utf-8"))
bom.write_bytes(text.encode("utf-8-sig"))
for path in (plain, bom):
    data = path.read_bytes()
    print(path.name, data[:3].hex(" ").upper(), len(data))

在此例中,虽然两个CSV都可以作为UTF-8来处理,但带BOM的一侧由于头部带有3个字节的标记,如果换行符和正文完全相同,其文件大小应该会大3个字节。日语字符在屏幕上看起来可能是一个字,但在UTF-8中可能会由多个字节表示。因此,整个文件的字节数与屏幕上的字符数是不同的概念。在比对结果时,请区分“内容”、“字符数”和“字节数”。

那么,有了 BOM 就一定能用 Excel 正确打开吗?并非如此。双击打开的方式与在数据导入界面指定字符编码的方式,其读取条件可能会有所不同。此外,由于 Excel 的版本和设置不同,其表现行为也会有所差异,因此必须记录通过哪种操作才能达到预期的显示效果。

乱码与“列错位”是不同的故障

仅日语显示损坏的情况,与日语虽然可读但各列向右错位的情况,其排查部位各不相同。由于 CSV 允许在引号括起来的字段中包含逗号或换行符,因此单纯按逗号分割字符串的处理方式有时无法正确读取。即便检查了前 3 个字节,也无法知晓分隔符和引号的规则。

例如,假设在某个单元格中写有“东京,品川”且被引号包围,其中的逗号并不一定是列的边界。即便正确指定了字符编码,如果对引号的解析出错,列数就会对不上。使用 Python 的 csv.reader 可以进行 CSV 的解析,但导入文件所使用的分隔符等条件必须与源系统的规范进行核对。

在业务交接时应当约定好的事项

在 CSV 的实际运用中,即使文件名相同,内容的格式也可能会发生变化。如果在创建方与接收方之间明确约定字符编码、是否有 BOM、换行符、分隔符、引号以及是否有表头,就能加快故障排查速度。若发生乱码,应保留原件,并记录使用哪款软件、通过什么操作打开时出现了该现象。

接收方编写程序依次尝试多种字符编码的做法看起来很方便,但有时即使使用了错误的字符编码也能偶然解码成功。切勿仅凭外观判断是否正确,对于重要数据,应核实源系统的规范和代表性数值。如果需要转换,应在核对转换后的列名、条数和日语样本后再投入使用。

应避免将包含机密信息或客户数据的 CSV 发送到在线的乱码诊断网站。首先在自己的终端上进行只读调查,并梳理出可用虚拟数据复现的条件才是安全的。不要以“因为没有 BOM”这句话草草了事,将字符编码、CSV 结构以及读取应用程序的设置分开排查,这在实际工作中非常有用。

总结

在排查 CSV 乱码时,首先观察前 3 个字节是否有 BOM,其次进行严格的解码与内容确认,最后区分CSV 的列结构与应用程序端的读取方式。在查明原因之前切勿覆盖原文件,这是实际工作中的最大安全保障。

官方信息与第一手资料

※ 本文中的运行示例基于官方规范编写,不保证在特定的 Windows 或 Excel 环境中必然成功运行。

文档信息

文章??
CSV乱码是BOM导致的?用Python从前3个字节开始排查
?布日期
更新日期
来源
https://papanda925.com/?p=18488&lang=zh

?可: ?于本站?有相??利的正文及原??表,除非?有?明,可依据 CC BY 4.0 使用。本文可能包含使用生成式AI?建或??的内容。若代??有?可声明,或?接的GitHub???定了?可,?代?以??可?准。引用内容、第三方?料、?片及商?不在本?可范?内。 使用政策

标题和URL已复制