错误出现
使用request
模块爬取网页,将页面源文件res.text
保存到文件get.html
时,
import request
res = requests.get('http://weibo.com')
with open(r'd:\get.html', 'w') as f:
f.write(res.text)
发生了如下错误:
Traceback (most recent call last):
File "E:/Project/Study/study.py", line 16, in <module>
f.write(res.text)
UnicodeEncodeError: 'gbk' codec can't encode character '\xca' in position 349: illegal multibyte sequence
这是一个编码错误,翻译为:
gbk编解码器不能对位置349的字符’\xca’编码:不合法的多字节流
encode
指编码,是指将Unicode字符按照编码规则编码为字节流
例如将中文的"好"字编码为UTF-8和GBK
char = '好'
print(char.encode('utf-8'))
print(char.encode('gbk'))
#输出
b'\xe5\xa5\xbd' #得到字节流(Utf-8将汉字编码为3个字节)
b'\xba\xc3' #gbk将汉字编码为2个字节
意味着GBK编码器不能对字符’\xca’编码(Python3中能显示的字符都是Unicode字符),
错误等价于以下:
char = '\xca'
char.encode('gbk')
#输出
UnicodeEncodeError: 'gbk' codec can't encode character '\xca' in position 0: illegal multibyte sequence
错误分析
UnicodeEncodeError: ‘gbk’ codec can’t encode character ‘\xca’ in position 349: illegal multibyte sequence
对于,首先要找出出错的位置character '\xca' in position 349
text_arr=res.text.split('\n') #将源代码分行
length=i=0 #length是每行长度的累加,i是找到的行数
while True:
i=i+1
length+=len(arr[i])
if length>349 : #找出比349大的位置行在第几行
print(i)
break
#输出
11
所以出错位置在text_arr[10]
,即源代码第11行
>>>text_arr[10]
window.use_fp = "1" == "1"; // ÊÇ·ñ²É¼¯É豸ָÎÆ¡£
找到出错的字符位置
import re
>>>re.search('\xca',text_arr[10])
<re.Match object; span=(35, 36), match='Ê'>
>>>'Ê'=='\xca'
True
于是,确定不能编码的字符为该行第35个字符Ê
先不管这个字符,首先思考下为什么源代码res.text
为什么会出现乱码?
猜测res.text
不是用Unicode编码的,但是尝试用Unicode编码,所以显示乱码
' window.use_fp = "1" == "1"; // ÊÇ·ñ²É¼¯É豸ָÎÆ¡£'
根据request
模块的说明,修改源代码的编码方式res.encoding
为源网页<meta>
标签中charset
的属性
>>> re.search('charset=(.*?)[;"]',res.text).group(1) #查找charset
'gb2312'
>>> res.encoding
'ISO-8859-1'
>>> res.encoding='gb2312'
再次刚才显示出错的行,发现乱码已经修复,能够成功编码并保存
>>> res.text.split('\n')[10] #改变编码后的原来那行
' window.use_fp = "1" == "1"; // 是否采集设备指纹。' #显示为Unicode字符,乱码修复了
#保存
>>> with open(r'd:\get.html', 'w') as f:
try:
f.write(res.text)
except :
print("保存失败")
raise
else :
print("保存成功")
保存成功
进一步思考
再一次理清思路:
以下是保存为Unicode
的源网页1.html
,假如爬取该网页res=request.get("1.html")
<html>
<meta content="text/html; charset=gb2312">
' window.use_fp = "1" == "1"; // 是否采集设备指纹。'
</html>
res.content
保存着从源网页获取的Unicode字符串使用了gb2312
编码后的字节流
>>> res.content
b' window.use_fp = "1" == "1"; // \xca\xc7\xb7\xf1\xb2\xc9\xbc\xaf\xc9\xe8\xb1\xb8\xd6\xb8\xce\xc6\xa1\xa3'
res.encoding
表示从content
解码(decode)到text
时使用的编码
>>> res.encoding
'ISO-8859-1' #一开始失败的编码,后面修改为'gbk'才成功
res.text
保存着解码后的字符串
>>> res.text
window.use_fp = "1" == "1"; // ÊÇ·ñ²É¼¯É豸ָÎÆ¡£
所以,如果打开文件get.html
,使用默认编码模式gbk
,将res.text
写入时,gbk编译器gbk codec
会将res.text
使用f.encoding(默认 cp936,即gbk)
编码为字节流,然后输出到文件(文件保存的都是字节流).
编码encode
与解码decode
是一个可逆过程,所以大多数情况下(除了扩展情况,例如gbk是gb2312的扩展,所以两者共同拥有的字符不影响), 以一种模式编码字符串所得到的字节流只能由相同的编码来解码得到该字符串.
因为gbk codec
只能编码 使用res.encoding=gbk
解码得到的res.text
,所以在编码 使用了res.encoding='ISO-8859-1'
解码得到的res.text
会提示题目所示的错误.
所以刚刚的解决方案实际上是设置 res.encoding=gbk
,使res.content
解码为gbk
编码的res.text
,这样gbk codec
在对res.text
编码时,两个模式都是gbk
,就不会出错了.
另外一个可行解决方案是:以二进制打开文件,gbk codec
直接保存由gbk
编码的字节流.
res = requests.get('http://weibo.com')
with open(r'd:\get.html', 'wb') as f:
f.write(res.content)
文件以gbk
编码模式打开,可以写入使用gb2312
编码的字节流res.content