bigywater.tech
常见报错 · 17

Python UnicodeDecodeError: 'utf-8' codec can't decode byte:默认编码、errors 选项与 BOM

“UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc4 in position 0: invalid continuation byte” 是读中文文件时最常遇到的报错。网上的答案是“加 encoding='gbk'”或者“errors='ignore'”,但前者要先知道文件是什么编码,后者会悄悄丢数据。这篇在本机把几种报错、默认编码、识别办法、errors 选项和 BOM 一次性测清楚。

utf-8
Python 3.15.0 里即使 LC_ALL=C,open() 的默认编码仍是它(UTF-8 模式默认开着);只有 PYTHONUTF8=0 才会变成 US-ASCII
True
只有 errors=surrogateescape 解码后的字符串能 encode 回与原始字节完全相同的结果,ignore / replace / backslashreplace 都是 False
KeyError: 'id'
CSV 带 BOM、用 utf-8 读时,第一列名实际是 \ufeffid,按 'id' 取值会 KeyError

三种后半句

实测 · 本机执行输出
  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 这类单字节编码永远不报错。最终要看解出来是不是人话。

errors 参数

实测 · 本机执行输出
  原始字节: 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 直接报错。

BOM

实测 · 本机执行输出
  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 读即可。

分步演示

解码失败
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MySQL 26.10.0 与 Redis 8.10.2(Homebrew,每个实验自己起实例,端口 9150 以上,只监听 127.0.0.1,不碰本机已有的实例)/ Python 3.15.0 + PyMySQL(按各篇注明用到哪些及版本);报错文本都是真实复现时原样捕获的;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用 Python 3.15.0 的标准库,测试数据都是脚本里用 encode 生成的字节(GBK、Big5、Latin-1、UTF-16、带 BOM 的 UTF-8),文件写在临时目录里,实验结束后删除
    • 没有验证:Windows 与中文 Windows 上不写 encoding= 时的默认编码(本机是 macOS);Python 3.14 及以前的默认行为;chardet / charset-normalizer 等识别库的准确度;pandas.read_csv 的 encoding_errors 与默认行为;gb18030 是 GBK 超集这类关于编码本身的说法(来自文档与记忆,没有逐个字符验证);requests 的 response.text 如何猜编码;终端、数据库连接字符集导致的类似报错
    京ICP备2026067502号-1京公网安备11010502063487号