应使用utf-8-sig编码读取utf-8-bom文件,因其能自动识别并跳过bom(\xef\xbb\xbf),避免将\ufeff误作正文字符;而utf-8编码不处理bom,导致开头出现不可见符号引发json解析失败、csv列名错位等问题。

open() 用 'utf-8' 读 UTF-8-BOM 文件,开头多出 \ufeff
不是文件坏了,是 Python 把 BOM 当成正文字符读进来了。UTF-8-BOM 的前三个字节 0xef 0xbb 0xbf 被 utf-8 解码后变成 Unicode 字符 \ufeff(零宽无间断空格),它不可见,但会卡在字符串最前面。
常见表现:
-
print(content[:10])输出开头带一个看不见的符号,content.startswith('\ufeff')返回True - JSON 解析失败:因为
{前多了\ufeff,导致json.loads()报Expecting value - CSV 表头错位:第一列名前面被塞进一个非法字符,
df.columns[0]看起来像'\ufeff姓名'
为什么 'utf-8-sig' 能解决,而 'utf-8' 不行
utf-8-sig 是 Python 内置的特殊编码别名,不是“更高级的 UTF-8”,它的唯一作用就是:遇到开头的 0xef 0xbb 0xbf 自动跳过,不把它解成 \ufeff;对没有 BOM 的 UTF-8 文件,行为和 utf-8 完全一致。
所以正确写法是:
with open('data.txt', encoding='utf-8-sig') as f:
content = f.read()
而不是:
# 错误:手动 strip 可能误伤合法内容
content = f.read().lstrip('\ufeff')
<h1>更错误:全局 replace,有些文本里真有 \ufeff(极少见但存在)</h1><p>content = content.replace('\ufeff', '')
</p>
Windows 记事本存的 “UTF-8” 默认带 BOM,VS Code 默认不带
这是乱码问题高频来源。你看到编辑器右下角写着 “UTF-8”,但没注意有没有 “(with BOM)” 后缀,就直接用 encoding='utf-8' 去读,大概率中招。
验证方法(命令行):
- Linux/macOS:
xxd -l 4 data.txt→ 看是否输出00000000: efbb bf... - Windows:
certutil -hashfile data.txt SHA256 | findstr /i "ef bb bf"(需配合十六进制判断)
实操建议:
- 统一用 VS Code / PyCharm 保存为 UTF-8 without BOM,避免源头问题
- 如果必须兼容别人发来的记事本文件,一律用
utf-8-sig,别猜 - 写入时也显式指定:
open('out.txt', 'w', encoding='utf-8-sig'),保证下游不踩坑
chardet 为什么常把 UTF-8-BOM 识别成 'ascii' 或 'utf-8'
chardet 是启发式探测,只读前几 KB 字节。UTF-8-BOM 的 0xef 0xbb 0xbf 在它眼里不是有效 ASCII 字符,但后面如果全是英文或数字,它可能直接判定为 ascii —— 这会导致你用 ascii 去读,立刻报错。
更糟的是:即使它返回 'utf-8',你也得意识到这不代表没 BOM。BOM 存在与否,和编码类型是两个维度的问题。
所以不要依赖 chardet.detect() 的单一结果。稳妥做法是:
- 先尝试
utf-8-sig(覆盖带/不带 BOM 的 UTF-8) - 失败再 fallback 到
gbk、latin-1等 - 永远比
chardet快,且不引入额外依赖
BOM 处理是编码链里最易被忽略的一环:它不报错、不中断流程,只是悄悄在字符串开头塞一个看不见的字符——等你调试 JSON、CSV、正则匹配或 API 请求体时,才突然发现哪都不对劲。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











