问题是这样的,在获取网页数据的时候由于网页的数据采用了混码,中文网页可能既有utf-8又有unicode编码,造成解析出来的数据包含乱码。确切地说不是乱码,是某一种编码没有被正确的解码。查询了不少解决方法,绝大多数只是提供了方法。可是由于大家各自抓取的数据五花八门,这些方法有的仅仅对其作者的场景有用,并非适用所有情况。所以理解发生这个问题的背后原因再去寻找适合自己的解决方案才更为有效。
不想看分析只想要解决方案的直接拉到最后即可。
下面我们举个例子
r = requests.get('https://www.zhihu.com',headers=headers, cookies=jar)
print(r.text)
#由于返回的结果太多,我仅截取有代表性的部分
#data-default-watermark-src=\"https:\u002F\u002Fpic2.zhimg.com\u002Fv2-
#6bb56219525997160eacd3ef4dd7fe1f_b.jpg\" class=\"origin_image zh-lightbox-thumb\"
#width=\"886\" data-original=\"https:\u002F\u002Fpic4.zhimg.com\u002Fv2-
#b972ab8ebce586b11285b99e7e0d8129_r.jpg\"\u002F\u003E\u003Cfigcaption\u003ECoco Lee 2023年2
#月手术后重新学习走路\u003C\u002Ffigcaption\u003E\u003C\u002Ffigure\u003E\u003Cp data-
#pid=\"vJNc6kvO\"\u003E我们的\u003Cb\u003E身体
上面是我们所看到知乎返回的结果,在结果里可以看到除了中文和普通的字母符号外,还包含了\u002F之类的字符串。如果对python的字符串编码有了解的话就能反应过来,这些都是unicode数据。
接下来我们再来看一下在解码前,返回的byte数据的样子。同样只截取了有代表性的部分 (数据和上面的不对应,只为了说明问题)
r = requests.get('https://www.zhihu.com',headers=headers, cookies=jar)
print(r.content)
#previous":"https:\\u002F\\u002Fwww.zhihu.com\\u002Fapi\\u002Fv3\\u002Ffeed\\u002Ftopstory\
#\u002Frecommend?
#action=pull&ad_interval=-10&before_id=5&desktop=true&page_number=2&session_token=c29f61750
#559697a36d54aaf66d3bf97","totals":0},"fresh_text":"\xe6\x8e\xa8\xe8\x8d\x90\xe5\xb7\xb2\xe
#6\x9b\xb4\xe6\x96\xb0"}}
下面我们来分析一下问题,从byte数据我们可以看到里面包含了如\u002F即unicode编码的数据,以及如\xe6的utf-8编码的数据。response.text在解码byte数据时,会通过response的header来决定采用哪儿种解码方式来解码。在这个例子中是采用了utf-8的方式解码了。那自然数据中属于unicode编码的部分就没有办法被顺利解码了。这也就解释了为啥response.text返回的时候中文和普通字母符号显示正常,而特殊符号的部分仍旧显示为/uxxx这种unicode的形式。
所以由于源数据采用了混合的编码 (utf-8 和 unicode),采用普通的解码方法即选择一种解码方式是行不通的。以此为例选择decode('utf-8')无法解析\u002F这类字符,如果选择decode('unicode_escape')则又会无法解析utf-8数据造成中文变成乱码。因此网上部分解决方案例如
content.encode("utf-8").decode("unicode-escape")
或者 content.encode("latin-1").decode("unicode-escape")
对于与包含中文的页面都是无效的。
解决思路
以下方案为转载。网上看了不少方案,觉得以下方案的通用性更强。思路就是一次解码不够,那就两次解码。将第一次解码后的字符串中的unicode数据通过正则表达式筛选出来进行二次解码。从而拿到真正浏览器中所见的内容。下面的这个例子中先通过utf-8解决中文的问题,然后再解决unicode的数据
before = r'data-original=\"https:\u002F\u002Fpic4.zhimg.com\u002Fv2-b972ab8ebce586b11285b99e7e0d8129_r.jpg\"\u002F\u003E\u003Cfigcaption\u003ECoco Lee 2023年2月手术后重新学习走路\u003C\u002Ffigcaption\u003E\u003C\u002Ffigure\u003E\u003Cp '
after = re.sub(r'(\\u[a-zA-Z0-9]{4})',lambda x:x.group(1).encode("utf-8").decode("unicode_escape"),text)
print (before)
print (after)
#
#从response.text获得的数据
#data-original=\"https:\u002F\u002Fpic4.zhimg.com\u002Fv2-
#b972ab8ebce586b11285b99e7e0d8129_r.jpg\"\u002F\u003E\u003Cfigcaption\u003ECoco Lee 2023年2
#月手术后重新学习走路\u003C\u002Ffigcaption\u003E\u003C\u002Ffigure\u003E\u003Cp
#第二次解码后的数据
#data-original=\"https://pic4.zhimg.com/v2-b972ab8ebce586b11285b99e7e0d8129_r.jpg\"/>
#Coco Lee 2023年2月手术后重新学习走路
此解决方案引用自:
Python使用content.encode(“utf-8“).decode(“unicode-escape“)导致中文乱码的解决方法_content.encode()___IProgrammer的博客-CSDN博客
此外还有一个思路也可以借鉴,如果要抓取的数据不复杂,可以在正则表达式上做文章。既然源数据有unicode部分,那就改正则表达式把unicode部分也获取出来。