人人都会AI编程

30.4 编码与文件读写乱码问题

更新时间:2026-07-12

文件读写时出现乱码,是几乎所有 Python 开发者都踩过的坑。这个问题看似麻烦,但只要理解了“字符编码”的基本原理,就能轻松规避。

乱码是怎么产生的?

简单说,乱码的根源是 “编码(写入时)”和“解码(读取时)”使用了不一致的字符集。例如,一个文件用 UTF-8 编码保存了中文,但用 GBK 去读取,就会看到一堆看不懂的符号。

计算机只能存储二进制数字,字符需要按一套规则转换成数字(编码),读取时再按同一套规则把数字转回字符(解码)。当编码和解码的规则不一样时,数字对应的字符就完全错乱。

Python 里的真实表现

在 Python 中,字符串分为两种:

  • str(文本字符串):内存中的 Unicode 字符,可以表示所有国家的文字。
  • bytes(字节串):原始字节数据,比如从磁盘读出来的未经解码的内容。

当你打开一个文件时,如果指定了不正确的编码,Python 就可能在解码或编码时出错,表现可能是:

  • 直接抛出 UnicodeDecodeErrorUnicodeEncodeError,程序中断。
  • 采用“忽略”或“替换”策略后,读出来的内容全是问号或乱码,静默失败,更难排查。

实用解决方案与最佳实践

1. 始终显式指定编码
写代码时不要依赖系统的默认编码(不同操作系统默认编码不同),打开文件时明确使用 encoding='utf-8'。UTF-8 是当下最通用的编码,能覆盖全球字符。

# 不推荐:不指定编码,可能出乱码
with open('data.txt', 'r') as f:
    content = f.read()

# 推荐:显式指定 UTF-8 编码
with open('data.txt', 'r', encoding='utf-8') as f:
    content = f.read()

2. 处理未知编码的文件
如果你拿到一个来源不明的文件,不知道它的编码,可以尝试用 chardet 库自动检测。这不是百分百准确,但在多数场景下足够实用。

import chardet

with open('unknown.txt', 'rb') as f:
    raw_data = f.read()
    result = chardet.detect(raw_data)
    encoding = result['encoding']   # 例如 'GB2312' 或 'utf-8'

with open('unknown.txt', 'r', encoding=encoding) as f:
    text = f.read()

3. 写入文件时也指定编码
写入文本同样要显式指定编码,确保生成的文件能被正确读取。

with open('output.txt', 'w', encoding='utf-8') as f:
    f.write('中文内容\n')

4. 处理非法字符
有时文件里夹杂了少量不符合编码规则的“坏数据”,可以设置 errors 参数让程序跳过或替换,而不是崩溃。

# 忽略无法解码的字节(谨慎使用,可能会丢失数据)
with open('data.txt', 'r', encoding='utf-8', errors='ignore') as f:
    content = f.read()

# 将无法解码的字节替换为 � (U+FFFD)
with open('data.txt', 'r', encoding='utf-8', errors='replace') as f:
    content = f.read()

需要提醒的是,errors='ignore' 会静默丢弃数据,尽量只在确实无法修复的容错场景下使用。

5. 统一项目编码规范
在团队中,约定所有源代码文件、配置文件、数据文件都用 UTF-8 编码存储。编辑器中设置默认编码为 UTF-8(无 BOM),避免不同操作系统和工具带来的差异。

常见踩坑场景与应对

  • Windows 记事本默认保存为 ANSI(GBK):用 Python 读取这类文件时一定要指定 encoding='gbk',或者先用其他工具另存为 UTF-8。
  • Excel 导出的 CSV 中文乱码:CSV 文件经常会被 Excel 写成 GBK 编码,Pandas 读取时也要指定编码:
  import pandas as pd
  df = pd.read_csv('file.csv', encoding='gbk')
  
  • API 返回的 JSON 数据乱码:HTTP 响应中的 JSON 通常默认是 UTF-8,但偶尔会有例外。用 response.json() 时若报编码错误,可先取原始字节再手动解码:
  data = response.content.decode('utf-8')
  

一句话总结

永远显式指定 encoding='utf-8',遇到不明编码用 chardet 探测,发现坏数据用 errors 参数兜底。 养成这个习惯,就能告别 90% 的编码乱码问题。