http://www.cnblogs.com/bitpeng/p/4748872.html

Python中文乱码,是一个很大的坑,自己不知道在这里遇到多少问题了。还好通过自己不断的总结,现在遇到乱码的情况越来越少,就算出现,一般也能快速解决问题。这个问题,我七月就解决了,今天总结出来,和朋友一起分享。

最近写过好几个爬虫,熟悉了下python requests库的用法,这个库真的Python的官方api接口好用多了。美中不足的是:这个库好像对中文的支持不是很友好,有些页面会出现乱码,然后换成urllib后,问题就没有了。由于requests库最终使用的是urllib3作为底层传输适配器,requests只是把urllib3库读取的原始进行人性化的处理,所以问题requests库本身上!于是决定阅读库源码,解决该中文乱码问题;一方面,也是希望加强自己对HTTP协议、Python的理解。

先是按照api接口,一行行阅读代码,尝试了解问题出在哪里!真个过程进展比较慢,我大概花了5天左右的时间,通读了该库的源代码。阅读代码过程中,有不懂的地方,就自己打印日志信息,以帮助理解。

最后我是这样发现问题所在的!

  1. >>> req = requests.get('http://www.jd.com')
  2. >>> req
  3. <Response [200]>
  4. >>> print req.text[:100]
  5. FILE: /usr/lib/python2.7/dist-packages/requests/models.pyc, LINE: 770 <==> ISO-8859-1
  6. FILE: /usr/lib/python2.7/dist-packages/requests/models.pyc, LINE: 781 <==> ISO-8859-1
  7. <!DOCTYPE html>
  8. <html>
  9. <head>
  10. <meta charset="gbk" />
  11. <title>¾©¶«(JD.COM)-×ÛºÏÍø¹ºÊ×Ñ¡-ÕýÆ·µÍ¼Û¡¢Æ·ÖÊ
  12. # 这里出现了乱码
  13. >>> dir(req)
  14. ['__attrs__', '__bool__', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getstate__', '__hash__', '__init__', '__iter__', '__module__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_content', '_content_consumed', 'apparent_encoding', 'close', 'connection', 'content', 'cookies', 'elapsed', 'encoding', 'headers', 'history', 'is_redirect', 'iter_content', 'iter_lines', 'json', 'links', 'ok', 'raise_for_status', 'raw', 'reason', 'request', 'status_code', 'text', 'url']

req有content属性,还有text属性,我们看看content属性:

  1. >>> print req.content[:100]
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <meta charset="gbk" />
  6. <title>¾©¶«(JD.COM)-؛ºЍ닗ѡ-ֽƷµͼۡ¢Ʒ׊
  7. >>>
  8. >>>
  9. >>> print req.content.decode('gbk')[:100]
  10. <!DOCTYPE html>
  11. <html>
  12. <head>
  13. <meta charset="gbk" />
  14. <title>京东(JD.COM)-综合网购首选-正品低价、品质保障、配送及时、轻松购物!</
  15. ## 由于该页面时gbk编码的,而Linux是utf-8编码,所以打印肯定是乱码,我们先进行解码。就能正确显示了。

可是,text属性,按照此种方式,并不可行!

  1. >>> print req.text[:100]
  2. FILE: /usr/lib/python2.7/dist-packages/requests/models.pyc, LINE: 770 <==> ISO-8859-1
  3. FILE: /usr/lib/python2.7/dist-packages/requests/models.pyc, LINE: 781 <==> ISO-8859-1
  4. <!DOCTYPE html>
  5. <html>
  6. <head>
  7. <meta charset="gbk" />
  8. <title>¾©¶«(JD.COM)-×ÛºÏÍø¹ºÊ×Ñ¡-ÕýÆ·µÍ¼Û¡¢Æ·ÖÊ
  9. >>> print req.text.decode('gbk')[:100]
  10. FILE: /usr/lib/python2.7/dist-packages/requests/models.pyc, LINE: 770 <==> ISO-8859-1
  11. FILE: /usr/lib/python2.7/dist-packages/requests/models.pyc, LINE: 781 <==> ISO-8859-1
  12. Traceback (most recent call last):
  13. File "<stdin>", line 1, in <module>
  14. UnicodeEncodeError: 'ascii' codec can't encode characters in position 60-63: ordinal not in range(128)
  15. # 对text属性进行解码,就会出现错误。

让我们来看看,这两个属性的源码:

  1. # /requests/models.py
    @property
  2. def content(self):
  3. """Content of the response, in bytes."""
  4.  
  5. if self._content is False:
  6. # Read the contents.
  7. try:
  8. if self._content_consumed:
  9. raise RuntimeError(
  10. 'The content for this response was already consumed')
  11.  
  12. if self.status_code == 0:
  13. self._content = None
  14. else:
  15. self._content = bytes().join(self.iter_content(CONTENT_CHUNK_SIZE)) or bytes()
  16.  
  17. except AttributeError:
  18. self._content = None
  19.  
  20. self._content_consumed = True
  21. # don't need to release the connection; that's been handled by urllib3
  22. # since we exhausted the data.
  23. return self._content
  1. # requests/models.py
  2. @property
  3. def text(self):
  4. """Content of the response, in unicode.
  5.  
  6. If Response.encoding is None, encoding will be guessed using
  7. ``chardet``.
  8.  
  9. The encoding of the response content is determined based solely on HTTP
  10. headers, following RFC 2616 to the letter. If you can take advantage of
  11. non-HTTP knowledge to make a better guess at the encoding, you should
  12. set ``r.encoding`` appropriately before accessing this property.
  13. """
  14.  
  15. # Try charset from content-type
  16. content = None
  17. encoding = self.encoding
  18.  
  19. if not self.content:
  20. return str('')
  21.  
  22. # Fallback to auto-detected encoding.
  23. if self.encoding is None:
  24. encoding = self.apparent_encoding
  25.  
  26. # Decode unicode from given encoding.
  27. try:
  28. content = str(self.content, encoding, errors='replace')
  29. except (LookupError, TypeError):
  30. # A LookupError is raised if the encoding was not found which could
  31. # indicate a misspelling or similar mistake.
  32. #
  33. # A TypeError can be raised if encoding is None
  34. #
  35. # So we try blindly encoding.
  36. content = str(self.content, errors='replace')
  37.  
  38. return content

看看注和源码知道,content是urllib3读取回来的原始字节码,而text不过是尝试对content通过编码方式解码为unicode。jd.com 页面为gbk编码,问题就出在这里。

  1. >>> req.apparent_encoding;req.encoding'GB2312'
  2. 'ISO-8859-1'

这里的两种编码方式和页面编码方式不一致,而content却还尝试用错误的编码方式进行解码。肯定会出现问题!

我们来看看,req的两种编码方式是怎么获取的:

  1. # rquests/models.py
  2. @property
  3. def apparent_encoding(self):
  4. """The apparent encoding, provided by the chardet library"""
  5. return chardet.detect(self.content)['encoding']

顺便说一下:chardet库监测编码不一定是完全对的,只有一定的可信度。比如jd.com页面,编码是gbk,但是检测出来却是GB2312,虽然这两种编码是兼容的,但是用GB2312区解码gbk编码的网页字节串是会有运行时错误的!

获取encoding的代码在这里:

  1. # requests/adapters.py
  2. def build_response(self, req, resp):
  3. """Builds a :class:`Response <requests.Response>` object from a urllib3
  4. response. This should not be called from user code, and is only exposed
  5. for use when subclassing the
  6. :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`
  7.  
  8. :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response.
  9. :param resp: The urllib3 response object.
  10. """
  11. response = Response()
  12.  
  13. # Fallback to None if there's no status_code, for whatever reason.
  14. response.status_code = getattr(resp, 'status', None)
  15.  
  16. # Make headers case-insensitive.
  17. response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {}))
  18.  
  19. # Set encoding.
  20. response.encoding = get_encoding_from_headers(response.headers)
  21. # .......

通过get_encoding_from_headers(response.headers)函数获取编码,我们再来看看这个函数!

  1. # requests/utils.py
  2. def get_encoding_from_headers(headers):
  3. """Returns encodings from given HTTP Header Dict.
  4.  
  5. :param headers: dictionary to extract encoding from.
  6. """
  7.  
  8. content_type = headers.get('content-type')
  9.  
  10. if not content_type:
  11. return None
  12.  
  13. content_type, params = cgi.parse_header(content_type)
  14.  
  15. if 'charset' in params:
  16. return params['charset'].strip("'\"")
  17.  
  18. if 'text' in content_type:
  19. return 'ISO-8859-1'

发现了吗?程序只通过http响应首部获取编码,假如响应中,没有指定charset, 那么直接返回'ISO-8859-1'。

我们尝试进行抓包,看看http响应内容是什么:

可以看到,reqponse header只指定了type,但是没有指定编码(一般现在页面编码都直接在html页面中)。所有该函数就直接返回'ISO-8859-1'。

可能大家会问:作者为什么要默认这样处理呢?这是一个bug吗?其实,作者是严格http协议标准写这个库的,《HTTP权威指南》里第16章国际化里提到,如果HTTP响应中Content-Type字段没有指定charset,则默认页面是'ISO-8859-1'编码。这处理英文页面当然没有问题,但是中文页面,就会有乱码了!

解决方案:

找到了问题所在,我们现在有两种方式解决该问题。

1. 修改get_encoding_from_headers函数,通过正则匹配,来检测页面编码。由于现在的页面都在HTML代码中指定了charset,所以通过正则式匹配的编码方式是完全正确的。

2. 由于content是HTTP相应的原始字节串,所以我们需要直接可以通过使用它。把content按照页面编码方式解码为unicode!

转载时注:3.reponse.json()方法也有这个问题,解决方法,就是json.dumps(response.content)

Python HTTP库requests中文页面乱码解决方案!的更多相关文章

  1. requests中文页面乱码解决方案【转】

    requests中文页面乱码解决方案!   请给作者点赞 --> 原文链接 Python中文乱码,是一个很大的坑,自己不知道在这里遇到多少问题了.还好通过自己不断的总结,现在遇到乱码的情况越来越 ...

  2. css中文字体乱码解决方案

    css中文字体乱码解决方案:把css编码和html页面编码统一起来.如果html页面是utf-8.css.js也统一成utf-8编码.还有一个避免中文乱码的办法就是把中文字体写成英文来表示 css中文 ...

  3. asp.net——地址栏传递中文参数乱码解决方案

    地址栏传递中文参数乱码解决方案: 很多人在使用地址栏传递参数的时候都会遇到一个麻烦的问题(参数为中文时乱码了),那要怎么解决呢? 其实解决这个问题也不怎么难,无非就是给要传递的中文参数一个编码解码的过 ...

  4. g++编译后中文显示乱码解决方案(c++)

    g++编译后中文显示乱码解决方案   环境:Windows 10 专业版 GCC版本:5.3.0 测试代码: 1 #include <iostream> 2 using namespace ...

  5. python第三方库requests简单介绍

    一.发送请求与传递参数 简单demo: import requests r = requests.get(url='http://www.itwhy.org') # 最基本的GET请求 print(r ...

  6. Python 标准库 BaseHTTPServer 中文翻译

    Python 标准库 BaseHTTPServer 中文翻译. 注意: BaseHTTPServer模块在Python3中已被合并到http.server,当转换你的资源为 Python3 时 2to ...

  7. Hive中文注释乱码解决方案(2)

    本文来自网易云社区 作者:王潘安 执行阶段 launchTask    回到Driver类的runInternal方法,看以下执行过程.在runInternal方法中,执行过程调用了execute方法 ...

  8. python 标准库 -- requests

    一. 安装 $ pip install requests requests 并不是python 标准库, 但为了汇总方便, 将其放置于此. 二. 用法 requests.get() : GET 请求 ...

  9. linux系统挂载U盘,中文文件名乱码解决方案

    本人(壮壮熊)所用系统:ubuntu 12.4 今天在使用mount指令挂在硬盘时,出现令人头疼的中文文件名乱码. 问题: 使用mount /dev/sdb1 /media指令挂在第二颗硬盘的第一个分 ...

随机推荐

  1. 通过Canvas及File API缩放并上传图片

    原文地址:Resize an Image Using Canvas, Drag and Drop and the File API 示例地址:Canvas Resize Demo 原文作者:Dr. T ...

  2. Gradle 1.12 翻译——第十二章 使用Gradle 图形用户界面

    有关其他已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或访问:http://gradledoc.qiniudn.com ...

  3. 04-GIT TortoiseGit冲突和补丁演示 案例演示

    TortoiseGit安装下载 http://download.tortoisegit.org/tgit/1.8.12.0/ 或https://code.google.com/p/tortoisegi ...

  4. c++友元函数与友元类

    友元函数和友元类的需要: 类具有封装和信息隐藏的特性.只有类的成员函数才能访问类的私有成员,程序中的其他函数是无法访问私有成员的.非成员函数可以访问类中的公有成员,但是如果将数据成员都定义为公有的,这 ...

  5. C++对象模型(一):The Semantics of Constructors The Default Constructor (默认构造函数什么时候会被创建出来)

    本文是 Inside The C++ Object Model, Chapter 2的部分读书笔记. C++ Annotated Reference Manual中明确告诉我们: default co ...

  6. Java关键字之finalize

    Java中提供了finalize方法,在垃圾回收器在进行内存释放时会首先调用finalize,但会有一些误区. 1).对象可能不被垃圾回收. 2).垃圾回收并不等于"析构",fin ...

  7. Android Widget 开发详解(二) +支持listView滑动的widget

    转载请标明出处:http://blog.csdn.net/sk719887916/article/details/47027263 不少开发项目中都会有widget功能,别小瞧了它,他也是androi ...

  8. android资源库

    原文地址:http://blog.csdn.net/xiechengfa/article/details/38830751 在摸索过程中,GitHub上搜集了很多很棒的Android第三方库,推荐给在 ...

  9. LeetCode之“树”:Binary Tree Level Order Traversal && Binary Tree Level Order Traversal II

    Binary Tree Level Order Traversal 题目链接 题目要求: Given a binary tree, return the level order traversal o ...

  10. 【Qt编程】基于QWT的曲线绘制及图例显示操作

    在<QWT在QtCreator中的安装与使用>一文中,我们完成了QWT的安装,这篇文章我们讲讲基础曲线的绘制功能. 首先,我们新建一个Qt应用程序,然后一路默认即可.这时,你会发现总共有: ...