应优先用 response.content 手动解码,按 utf-8→gbk→gb2312 顺序尝试,避免依赖 response.text 或 apparent_encoding;设 response.encoding 必须在首次访问 response.text 前。

直接用 response.content 手动解码,别碰 response.text —— 这是解决乱码最可靠的第一步。requests 默认用 ISO-8859-1 解码字节流,而中文网页实际多为 UTF-8 或 GBK,硬读 response.text 就等于默认走错路。
为什么 response.apparent_encoding 不可信
它基于 chardet 启发式判断,对短页面、纯 ASCII 内容或无 BOM 的 GBK 页面极易误判。比如一个只有 <meta charset="gbk"> 的响应头,apparent_encoding 可能返回 'Windows-1254' 或 'ascii',confidence 常低于 0.5。这不是 bug,是统计模型的天然局限。
- 永远只把它当备选,不作为主逻辑
- 若
chardet.detect(content[:10000])['confidence'] < 0.7,直接跳过 - 优先从 HTTP 响应头的
Content-Type提取charset=值,它比任何检测都权威
怎么安全地从 HTML meta 标签提取真实编码
很多老站不写响应头 charset,但会在 <meta> 里声明。用正则容易误匹配(比如匹配到 JS 字符串里的 charset),推荐用 BeautifulSoup 提前解析再查:
from bs4 import BeautifulSoup
soup = BeautifulSoup(response.content, 'lxml')
meta_charset = soup.find('meta', attrs={'charset': True})
if meta_charset:
real_encoding = meta_charset.get('charset', '').strip()
else:
meta_http = soup.find('meta', attrs={'http-equiv': lambda x: x and x.lower() == 'content-type'})
if meta_http and 'charset=' in (meta_http.get('content') or ''):
real_encoding = meta_http['content'].split('charset=')[-1].split(';')[0].strip()
- 必须传
response.content给 BeautifulSoup,不能传response.text - 提取后要
.strip(),避免空格或换行干扰 - 拿到
real_encoding后仍需验证:尝试response.content.decode(real_encoding, errors='replace'),若大量 出现,说明还是错的
手动解码时怎么处理异常和兼容性
即使猜对了编码名,字节流里也可能含非法序列(比如被截断的 UTF-8 多字节字符)。直接 .decode() 会抛 UnicodeDecodeError,必须兜底:
立即学习“Python免费学习笔记(深入)”;
- 用
errors='replace'把非法字节替换成 ,保全文本结构可读 - 用
errors='ignore'跳过非法字节,适合后续要正则提取的场景 - GBK 和 GB2312 实际兼容,但 Python 中
'gb2312'无法解部分 GBK 字符,优先试'gbk'或'gb18030' - 如果 UTF-8 解出来仍是乱码,大概率是页面用了 GBK 却声明了 UTF-8 —— 此时不要反复试,直接 fallback 到
'gbk'
真正难的不是“怎么解”,而是“信谁”。HTTP 头 > meta 标签 > chardet 检测,这个优先级不能颠倒;而所有解码操作必须基于 response.content,一旦你调用了 response.text,requests 就可能已按错误编码缓存了解码结果,再改 encoding 也无效。


















