专栏名称: dwzb
目录
相关文章推荐
51好读  ›  专栏  ›  dwzb

字符编码(三)|与网页或文件交互时的编码问题

dwzb  · 掘金  ·  · 2018-03-15 02:17

正文

本文在上文理论的基础上,列举一些实际使用时遇到的编码问题,分为读取网页和读写文件文件两个部分。因为本人遇到的问题可能只是冰山一角, 欢迎读者将自己遇到的编码问题写在评论区,如果报错种类和文中列的不同,我会加入文章之中。

与网页交互时的报错或乱码

本节列一些我在爬网页时会遇到的乱码问题,基于requests库,分为如下内容

  • r.text乱码如何得到真正的字符
  • unicode式乱码
  • 特殊字符无法显示问题

1.r.text乱码如何得到真正的字符

举一个例子,访问知乎的一个错误界面,会提示“你似乎来到了没有知识存在的荒原…”,但抓取到的这个页面的信息是乱码了,正好作为一个实例展示在这里

获取网页信息的代码如下

import requests
url='http://www.zhihu.com/api/v4/people/b4471a5431e1f53a96fa06c5e721afd5'
ua = 'Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11'
headers = {'User-Agent':ua}
r = requests.get(url,headers = headers)

我们来看一下r.text的返回结果(只截取一个乱码部分)

<strong>ä½ ä¼¼ä¹Žæ¥åˆ°äº†æ²¡æœ‰çŸ¥è¯†å­˜åœ¨çš„è’åŽŸ...</strong>

接下来需要对r.text进行编码或解码来获取真正的内容。

首先要明确

  • requests从请求的结果提取内容有两种方法
    • r.text提取的是unicode字符串
    • r.content提取的是二进制数,即经过编码的内容
    • 其实抓取页面先得到的是r.content,r.text是经过解码得到的结果,r.text乱码说明解码方式错误
  • 这个网页题头标明的是UTF-8编码 <meta charset="utf-8">

最开始什么都不知道,就随便试编码,因为是中文所以试试gbk

r.text.encode('gbk') # 报错
print(r.text.encode('utf-8').decode('gbk'))
# <strong>盲陆聽盲录录盲鹿聨忙聺楼氓聢掳盲潞聠忙虏隆忙聹聣莽聼楼猫炉聠氓颅聵氓聹篓莽職聞猫聧聮氓聨聼...</strong>

这样随便试是不行的

接下来进行分析每一步是什么样的过程

  • 首先明确网页题头 <meta charset="utf-8"> 的作用是:拿到二进制数r.content,用这个编码来解码可以获取正确的文本字符
  • r.text使用的肯定不是’utf-8’对二进制数进行解码,所以解出来的结果是错误的,我们需要知道它是用的什么编码
  • requests库有两个可以查看编码的属性,用这个可以知道r.text到底是用的什么来解码

我们一条一条来看

第一,我们可以尝试不从r.text出发获取文本,而是从r.content出发

print(r.content.decode('utf-8'))
# <strong>你似乎来到了没有知识存在的荒原...</strong>

我们发现确实正常显示了

第二,查看requests的两个编码属性

r.encoding # 'ISO-8859-1'
r.apparent_encoding # 'utf-8'

我们可以理解成,第一个是当前r.text解码时使用的编码,第二个是应该使用的编码

接下来我们就可以利用这个编码配合r.text来获取正确文本了

print(r.text.encode('ISO-8859-1').decode('utf-8'))
# <strong>你似乎来到了没有知识存在的荒原...</strong>

正常显示了!

更通用的做法是下面两种

print(r.content.decode(r.apparent_encoding))
print(r.text.encode(r.encoding).decode(r.apparent_encoding))

2.unicode式乱码

这里也是爬取知乎的一个界面,这个界面是Json格式的数据,就不贴爬虫代码了,只看r.text返回结果即可

"headline": "\u7edf\u8ba1\u4e13\u4e1a\u5b66\u751f"

这是什么,unicode编码,我们之前见过这个形式,但是从来都是我手动输入,没有用程序获得过一个字符的unicode编码,而且unicode编码打印出来不应该显示的是字符本身吗,怎么会这样显示出来?

我们想一想,什么时候print结果会出现 \n 就明白了,是在 print('\\n') 的时候,所以说明这里是原始字符是 \\u7edf\\u8ba1 这样的形式。而这样的形式如何处理才能得到真正的字符呢?我们先铺垫一个用法

a = '中文'
a.encode('unicode-escape')
# b'\\u4e2d\\u6587'

这是一种编码方式,所以如果我们能得到 b'\\u4e2d\\u6587' 就可以通过 b'\\u4e2d\\u6587'.decode('unicode-escape') 获取中文字符了。

现在有的是 '\\u4e2d\\u6587' ,不要把它看成特殊的编码,就看成最普通的字符,英文数字字符,全是收录在ASCII码中的通用字符,用任意一种编码方式都可以获得 b'\\u4e2d\\u6587' (因为ASCII编码的通用性,’abc’.encode(‘ascii’)结果是b’abc’而不是16进制数,之前讲过的),所以这样的乱码就可以解决了

print(r.text.encode('utf-8').decode('unicode-escape'))
# "headline": "统计专业学生"

同样,我们也可以检查这个网页的两个编码,发现 r.encoding 是空值, r.apparent_encoding 是ascii。 r.content r.text 结果是一样的,是unicode格式的数据。

3.特殊字符无法显示问题

这个问题比如在jupyter中写代码运行再print,是不会出现错误的,只有在.py文件中写好代码,在cmd中运行,将结果输出到cmd页面上才有可能发生。比如最简单的豆瓣top250的所有数据就不能全部打印到cmd页面上

在1.py文件中输入

import requests
url = 'https://movie.douban.com/top250'
r = requests.get(url)
print(r.text)

在命令行中输入 python 1.py 运行,会报如下错误

Traceback (most recent call last):
  File "live.py", line 119, in <module>
    print(r.text)
UnicodeEncodeError: 'gbk' codec can't encode character '\xee' in position 20554: illegal multibyte sequence

这个问题我们之前说过的,就是因为命令行下的代码页是chcp 936,会默认进行一步GBK编码的转换,而网页源码中有的字符没有被GBK收录,于是报错(类似&nbsp这样的字符)。当时我们的处理办法是将命令行改为chcp 65001就可以正常显示,这里也是这样。不过还有另外一种处理方式,这里主要讲这种方式

当这个不被识别的字符不重要时,就像豆瓣中的这个字符可能代表的是空格之类的东西,我们可以直接抛弃掉他们,从而将其他识别的字符放在cmd中显示。







请到「今天看啥」查看全文