Python读取文件报错UnicodeDecodeError 'gbk' codec can't decode byte的解决方法(encoding参数、编码检测与BOM处理)

读一份UTF-8的CSV,解释器直接抛异常,数据一行都没读到:

Traceback (most recent call last):
  File "D:\work\read.py", line 4, in <module>
    rows = open("data.csv").read().splitlines()
UnicodeDecodeError: 'gbk' codec can't decode byte 0xa4 in position 12: illegal multibyte sequence

0xa4在GBK里是合法首字节,可后面那个字节凑不成完整的双字节汉字,解码器就抛异常了。文件本身是UTF-8,被当成GBK读,中文一多必然炸。

踩过不止一次。

根子在open()的默认编码

open()不写encoding参数时,用的是locale.getpreferredencoding(False)的返回值。中文Windows上这个值是cp936(也就是GBK),Linux上多半是UTF-8。同一段代码换个系统,行为就不一样——脚本在开发机上跑得好好的,丢到Windows的服务器上就崩,多半是这个原因。

import locale, sys
print(locale.getpreferredencoding(False))   # 中文Windows输出 cp936
print(sys.getdefaultencoding())             # 这个常年是utf-8,跟文件读写无关

sys.getdefaultencoding()跟文件读写没关系,别拿它当判断依据,见过有人盯着它查半天。

先确认文件到底是什么编码

别猜。记事本另存时选的"UTF-8"在老版本Windows上是带BOM的,Excel导出的CSV常见GBK或者UTF-8带BOM,Linux上生成的日志基本是纯UTF-8。三种情况处理方式不一样。

看文件头几个字节最直观:

with open("data.csv", "rb") as f:
    head = f.read(4)
print(head)
# b'\xef\xbb\xbf'  -> UTF-8 with BOM
# b'\xff\xfe'      -> UTF-16 LE,Excel另存的"Unicode文本"就是它

命令行上也能查(Git Bash、WSL、Linux都行):

file -i data.csv
xxd -l 16 data.csv

file命令给出charset=utf-8还是charset=iso-8859-1,一眼清楚。给iso-8859-1基本等于"里面其实是GBK或UTF-8,file没认出来",遇到这种情况继续往下看。

拿不准就上检测库,虽然它不是百分百准,但对付常见中文编码够用:

pip install chardet
python -c "import chardet;print(chardet.detect(open('data.csv','rb').read()))"
# {'encoding': 'UTF-8-SIG', 'confidence': 1.0, 'language': ''}

confidence低于0.7的结果别当真,之前碰到一个几百KB的日志,chardet给出0.5的GBK,实际是UTF-8混了几行乱码。

解决办法:把encoding写死

1、纯UTF-8文件,显式声明:

with open("data.csv", encoding="utf-8") as f:
    rows = f.read().splitlines()

2、带BOM的文件用utf-8-sig。用它读纯UTF-8文件也不会出错,所以判断不了有没有BOM时,直接用utf-8-sig更省事:

with open("data.csv", encoding="utf-8-sig") as f:
    for line in f:
        print(line)

不用utf-8-sig的话,第一行的表头会粘着一个\ufeff,看着像正常列名,拿去做字典key时就对不上了,这个点排查起来很费时间。

3、确认是GBK或GB2312的,写encoding="gbk"。gbk能覆盖gb2312,也能解一部分gb18030,遇到"illegal multibyte sequence"再换成gb18030试试,它是超集。

4、csv模块读文件,除了encoding还要加newline="":

import csv
with open("data.csv", encoding="utf-8-sig", newline="") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["姓名"])

不加newline="",字段里嵌了换行的记录会被切成两条,行数看着多了但数据对不上。

5、pandas读CSV同样要带编码参数,报错也一样是UnicodeDecodeError:

import pandas as pd
df = pd.read_csv("data.csv", encoding="utf-8-sig")
# 少量坏字符不想整批失败时,加 encoding_errors="replace"

errors参数别乱用

errors="ignore"能让报错消失,代价是坏字节被悄悄丢掉。对账单、金额、证件号这种文件千万别用,实测过一次,两万行的对账文件跑完少了三个字符,核对时才发现。真要容错,用errors="replace",至少能看见问号,知道哪儿出过问题。

with open("data.csv", encoding="utf-8", errors="replace") as f:
    text = f.read()

想定位到底是哪一段坏了,用二进制读出来自己解,报错里给的是字节位置,直接切:

raw = open("data.csv", "rb").read()
try:
    text = raw.decode("utf-8")
except UnicodeDecodeError as e:
    print(e.start, e.end, raw[e.start-20:e.end+20])

写文件也是同一回事

open()写文件不写encoding,Windows上就按GBK写出去了,下游拿到的就是乱码。写和读的编码要成对:

with open("out.txt", "w", encoding="utf-8", newline="\n") as f:
    f.write("测试")

newline="\n"顺便避免Windows上把\n自动换成\r\n,脚本生成配置、证书这类文件时这一句很有用。

两个能提前暴露隐患的开关

PYTHONUTF8=1或者python -X utf8,能强制走UTF-8模式,临时救急用:

set PYTHONUTF8=1          # cmd
export PYTHONUTF8=1       # bash
python -X utf8 script.py

python -X warn_default_encoding(环境变量写法PYTHONWARNDEFAULTENCODING=1)会把所有没写encoding的open()以EncodingWarning报出来,改造老项目时开着它跑一遍,漏网的点全出来了。这个开关是Python 3.10加的。

往后看,Python 3.15起UTF-8模式会默认打开,PEP 686写的就是这件事,3.15.0a7在2026年已经放出来了。也就是说现在靠cp936兜着的代码,到了Python 3.15上行为会变:以前能读的GBK文件会开始报错,以前报错的UTF-8文件反而正常。手头有要长期维护的脚本,趁Python 3.11、3.12、3.13、3.14这几个版本还在,把encoding补齐最省事。

补齐的办法很机械:所有open()都带encoding,读用utf-8-sig,写用utf-8。跑一遍warn_default_encoding,报哪儿改哪儿。

数据截至2026年9月。