“UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc4 in position 0: invalid continuation byte” 是读中文文件时最常遇到的报错。网上的答案是“加 encoding='gbk'”或者“errors='ignore'”,但前者要先知道文件是什么编码,后者会悄悄丢数据。这篇在本机把几种报错、默认编码、识别办法、errors 选项和 BOM 一次性测清楚。
GBK 编码的“你好”(c4 e3 ba c3): c4 e3 ba c3
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc4 in position 0: invalid continuation byte
e.start=0 e.end=1 e.reason='invalid continuation byte'
GBK 编码的“中文”(d6 d0 ce c4): d6 d0 ce c4
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0: invalid continuation byte
e.start=0 e.end=1 e.reason='invalid continuation byte'
Latin-1 的 café,结尾 e9: 63 61 66 e9
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 3: unexpected end of data
e.start=3 e.end=4 e.reason='unexpected end of data'
Latin-1 的 café au lait,e9 后面还有字节: 63 61 66 e9 20 61 75 20 6c 61 69 74
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 3: invalid continuation byte
e.start=3 e.end=4 e.reason='invalid continuation byte'
UTF-16 小端带 BOM 的“Hi”(ff fe …): ff fe 48 00 69 00
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
e.start=0 e.end=1 e.reason='invalid start byte'
一个被截断的 UTF-8 汉字(只有前 2 个字节): e6 b1
UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 0-1: unexpected end of data
e.start=0 e.end=2 e.reason='unexpected end of data'
open("gbk.txt").read() -> UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc4 in position 0: invalid continuation byte
open("gbk.txt", encoding="gbk").read() -> '你好,世界\n第二行\n'invalid continuation byte 常见于把 GBK 当 UTF-8 读;invalid start byte 说明开头字节就不可能是 UTF-8;unexpected end of data 说明数据在字符中间结束。e.start、e.end、e.reason 可直接取。
当前环境: open().encoding = utf-8 | utf8_mode = 1 | locale.getencoding() = UTF-8 LC_ALL=C: open().encoding = utf-8 | utf8_mode = 1 | locale.getencoding() = US-ASCII LC_ALL=C 且 PYTHONUTF8=0: open().encoding = US-ASCII | utf8_mode = 0 | locale.getencoding() = US-ASCII PYTHONUTF8=1: open().encoding = utf-8 | utf8_mode = 1 | locale.getencoding() = UTF-8 -X warn_default_encoding 下不写 encoding= 的 open():<string>:1: EncodingWarning: 'encoding' argument not specified
open() 不写 encoding= 时的编码取决于环境;在这个版本上 UTF-8 模式默认开着。加 -X warn_default_encoding 能找出所有没写 encoding= 的位置。
文件内容 utf-8 gb18030 big5 shift_jis latin-1 utf-16 GBK 的“你好,世界” 报错 ok 你好,世界 ok 斕疑,岍賜 报错 ok ÄãºÃ,ÊÀ½ 报错 Big5 的“你好,世界” 报错 ok , ok 你好,世界 ok ァAヲn,・@ャ ok §A¦n,¥@¬ 报错 UTF-8 的“你好,世界” ok 你好,世界 ok 浣犲ソ,涓栫晫 报错 报错 ok ä½ å¥½,ä 报错 (gb18030 是 GBK 的超集;同一串字节被另一种编码当作合法文本解出来时,不报错但字不对)
逐个候选解码,但“不报错”不等于“对”,latin-1 这类单字节编码永远不报错。最终要看解出来是不是人话。
原始字节: b'ok-\xc4\xe3\xba\xc3-end' errors=ignore -> 'ok--end' (长度 7);再 encode 回去 == 原始字节? False errors=replace -> 'ok-���-end' (长度 10);再 encode 回去 == 原始字节? False errors=backslashreplace -> 'ok-\\xc4\\xe3\\xba\\xc3-end' (长度 23);再 encode 回去 == 原始字节? False errors=surrogateescape -> 'ok-\udcc4\udce3\udcba\udcc3-end' (长度 11);再 encode 回去 == 原始字节? True errors=strict(默认)-> UnicodeDecodeError 读一个混有坏字节的日志文件:open(..., errors="replace") 的行数 = 3,其中坏的一行 = '���\n'
ignore 丢数据,replace 不可还原,surrogateescape 能无损还原,默认 strict 直接报错。
encoding="utf-8" 读 bom.json:第一个字符 = '\ufeff',len = 15
json.load(open(…, encoding="utf-8")) -> JSONDecodeError: Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)
json.loads(文件的原始字节 bytes) -> {'name': '张三'}(传 bytes 时成功)
encoding="utf-8-sig" -> {'name': '张三'}
csv.DictReader 用 utf-8 读 bom.csv:列名 = ['\ufeffid', 'name'],rows[0]["id"] -> KeyError: 'id'
用 utf-8-sig 读:列名 = ['id', 'name']UTF-8 BOM 会让 json.load 报错、让 CSV 第一列名带 \ufeff,用 utf-8-sig 读即可。