同一份 GBK 网页,为什么有的能抓对、有的变成乱码?这篇把 Response.encoding 的每一级来源都拿出来冲突一遍,再看自动探测在多短的文本上会出错,以及头里声明错编码后 Scrapy 的表现。
头 charset=gbk,正文 gbk → encoding=gb18030 解码结果与原文一致 没有头,<meta charset=gbk>,正文 gbk → encoding=gb18030 解码结果与原文一致 头 charset=utf-8 + <meta charset=gbk>,正文 gbk → encoding=utf-8 解码结果含 14 个替换字符 头 charset=gbk + <meta charset=utf-8>,正文 utf-8 → encoding=gb18030 解码结果含 1 个替换字符 BOM(utf-8-sig)+ 头 charset=gbk,正文 utf-8 → encoding=utf-8 解码结果与原文一致 构造参数 encoding=gbk,头 charset=utf-8,正文 gbk → encoding=gbk 解码结果与原文一致 没有头、没有 meta,正文 gbk(自动探测) → encoding=cp949 解码结果与原文不一致(乱码) 没有头、没有 meta,正文 utf-8(自动探测) → encoding=utf-8 解码结果与原文一致
TextResponse.encoding 先看构造参数,再依次看 BOM、HTTP 头的 charset、正文里的 <meta>,最后才自动探测。头和 <meta> 冲突时头赢,即使头是错的。
真实编码 3 6 12 25 50 (格子里是探测出的编码;★ 表示解码结果与原文不一致) gbk cp949★ cp949★ gb18030 gb18030 gb18030 big5 big5hkscs big5hkscs big5hkscs big5hkscs big5hkscs shift_jis cp932 cp932 cp932 cp932 cp932 euc-kr big5hkscs★ cp949 cp949 cp949 cp949
先试 ascii、utf-8,再交给 charset-normalizer。文本很短时(3、6 个字)GBK 会被判成 cp949,结果乱码;GBK 页面最好让服务器发对头,或在代码里显式指定编码。
text/plain; charset=utf-8,正文 GBK 的 JSON:r.encoding = utf-8 ;r.text = '{"����": "����", "����": 3}' ;替换字符 12 个
r.json() = {'����': 3}
application/json(没有 charset),正文 GBK:r2.encoding = cp949
r2.json() = {'츰俚': '탔녔', '鑒좆': 3}
同上,构造时 encoding=gbk:r3.json() = {'名字': '爬虫', '数量': 3}头里声明错误的编码不会抛异常,.text 里是替换字符;JSON 的键如果全是被替换的字符,会合并成同一个键,数据丢失。
charset=utf8 → r.encoding = utf-8 charset=UTF-8 → r.encoding = utf-8 charset=gb2312 → r.encoding = gb18030 charset=GBK → r.encoding = gb18030 charset=gb18030 → r.encoding = gb18030 charset=latin1 → r.encoding = cp1252 charset=iso-8859-1 → r.encoding = cp1252 charset=ascii → r.encoding = cp1252 charset=windows-1252 → r.encoding = cp1252 charset=shift_jis → r.encoding = cp932 charset=euc-kr → r.encoding = cp949 charset=big5 → r.encoding = big5hkscs charset=x-unknown → r.encoding = cp1252
w3lib 把各种写法映射到 WHATWG 的标准名:gb2312/GBK → gb18030,latin1/ascii → cp1252 等。
text/html → HtmlResponse 有 .text text/html; charset=utf-8 → HtmlResponse 有 .text application/xhtml+xml → HtmlResponse 有 .text application/xml → XmlResponse 有 .text text/xml → XmlResponse 有 .text application/json → JsonResponse 有 .text text/plain → TextResponse 有 .text application/javascript → TextResponse 有 .text image/png → Response .text 抛 AttributeError: Response content isn't text application/octet-stream → Response .text 抛 AttributeError: Response content isn't text application/pdf → Response .text 抛 AttributeError: Response content isn't text (无 Content-Type) → Response .text 抛 AttributeError: Response content isn't text
只有 TextResponse 及其子类(HtmlResponse、XmlResponse、JsonResponse)才有 .text 和选择器;二进制类型是普通 Response。
头 charset=gbk → encoding=gb18030 <title> = '爬虫入门' 头无 charset,<meta charset=gbk> → encoding=gb18030 <title> = '爬虫入门' 头 charset=utf-8(错的),<meta charset=gbk> → encoding=utf-8 <title> = '��������' 头无 charset、无 meta(自动探测) → encoding=big5hkscs <title> = '鰾單⻌藷'
真实抓取的结果与单元实验一致:头对或 <meta> 对就能还原,头错或什么都没有就乱码。