V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
KyL
V2EX  ›  Python

py 爬中文网页,总是遭遇 UnicodeEncodeError

  •  
  •   KyL · 2015-01-17 17:12:01 +08:00 · 6192 次点击
    这是一个创建于 3379 天前的主题,其中的信息可能已经有所发展或是发生改变。

    觉得《码农周刊》挺有意思的,想写个脚本把内容抓下来。

    import urllib
    import httplib
    def get_html_content(url):
        response = urllib.urlopen(url)
        html = response.read()
        print type(html)
        return html
    
    
    if __name__ == '__main__':
        url = 'http://weekly.manong.io/issues/58'
        html = get_html_content(url)
        print html.decode('utf-8')
    

    结果报错误

    <type 'str'>
    Traceback (most recent call last):
      File "E:\src\infra.py", line 32, in <module>
        print html.decode('utf-8')
    UnicodeEncodeError: 'ascii' codec can't encode character u'\u201c' in position 44: ordinal not in range(128)
    [Finished in 1.6s]
    

    我真的查了许多,像是http://nedbatchelder.com/text/unipain.html 通读一遍,结果怎么都搞不定,求解。

    第 1 条附言  ·  2015-01-18 14:46:20 +08:00
    应该附上我的工作环境的。
    Windows 7 64bit
    Python2.7.9
    Sublime Text Ctrl + B 调试
    24 条回复    2015-02-01 22:05:14 +08:00
    ihciah
        1
    ihciah  
       2015-01-17 17:23:12 +08:00 via Android
    我这里正常…不过是用的urllib2.urlopen
    KyL
        2
    KyL  
    OP
       2015-01-17 17:29:03 +08:00
    @ihciah 你是PY2还是PY3?
    liqiu
        3
    liqiu  
       2015-01-17 17:34:29 +08:00
    aaaa007cn
        4
    aaaa007cn  
       2015-01-17 19:16:11 +08:00
    如果只考虑这个场合
    print html.decode('utf-8').encode('gb18030')
    Delbert
        5
    Delbert  
       2015-01-17 19:25:26 +08:00
    .decode("utf-8", "ignore")
    这样试试呢?
    9hills
        6
    9hills  
       2015-01-17 19:39:44 +08:00   ❤️ 2
    程序没问题,出问题的是你的环境。直接print Unicode的话,如果在Linux or OSX的默认UTF-8环境,是没有问题的。但是在Windows上就呵呵(4楼是windows上的解决办法)

    两点:
    1. 不要学3楼的reload sys哈,不是解决问题的办法。你reload前你懂它到底做了什么么,不懂的话就别写自己不懂的代码,如果你懂的话,那也不需要这个reload的办法
    2. 直接输出Unicode是不好的行为,输出的时候应该encode下的,环境是utf-8就encode成utf-8,环境是gbk就encode成gbk
    9hills
        7
    9hills  
       2015-01-17 19:42:12 +08:00   ❤️ 1
    处理编码的主要一个原则,就是让程序的默认隐式转换变成显式的。

    比如所有中文都写成 u"中文" 这样的Unicode,而不是让它的编码随文件编码变化而变化
    比如输出的时候显式转换编码,而不是依赖系统的编码转换。。
    pyKun
        8
    pyKun  
       2015-01-17 21:02:33 +08:00
    @9hills

    处理编码的主要一个原则,就是让程序的默认隐式转换变成显式的。

    +1
    lerry
        9
    lerry  
       2015-01-17 21:39:23 +08:00
    @KyL
    我理解的,输出之前encode,处理之前decode,

    另外,这样写,print的时候不会出错
    print u"你妹".encode(sys.stdout.encoding)
    lyhapple
        10
    lyhapple  
       2015-01-17 21:40:09 +08:00
    用python的requests库试试.
    pip install requests
    9hills
        11
    9hills  
       2015-01-17 21:43:20 +08:00
    @lerry 恩,这样适合写通用的CLI系统,可以兼容多种编码环境
    icedx
        12
    icedx  
       2015-01-17 21:47:12 +08:00
    print html.decode('utf8').encode('gbk','backslashreplace')
    因为楼主用的是Windows
    endoffight
        13
    endoffight  
       2015-01-17 22:12:02 +08:00 via Android
    建议安装chardect模块确定编码后再决定转不转码
    EPr2hh6LADQWqRVH
        14
    EPr2hh6LADQWqRVH  
       2015-01-17 22:15:34 +08:00
    不要assume数据的编码,拿到手保持Bytes的形态,然后自己尝试解码
    Sylv
        15
    Sylv  
       2015-01-18 13:44:25 +08:00
    你这个问题其实是和 Sublime Text 有关的,我刚好在总结一篇相关的 Unicode 编码问题的分析,写好会分享出来。

    而针对你这个问题的短答案是:
    你在最后将 str 类型的 html decode 为了 unicode 类型的字符串,然后想要打印 print 出这个 unicode 字符串时,Python 又需要将其转换为 str 后才能输出。在正常情况下,Python 能判断出正确的输出编码来进行转换而并不会出错,这就是前面有人说在他们的环境能正常运行的原因。而你会出现 UnicodeEncodeError 的原因是,因为某种原因,在 Sublime Text 里运行的 Python 不能判断出这个正确的输出编码,于是它就使用了它的默认编码 ascii 来进行转换,于是就出错了。
    在 Sublime Text 里想要 print 出一个 unicode 类型的字符串,正确的方法是显式地进行 encode('utf-8') 后再 print。而为什么我说要用的是 utf-8 编码,而不是前面说的 gbk 等编码呢?因为默认配置下的 Sublime Text 所接收的默认输出编码是 utf-8,你用其它编码会出现 [Decode error - output not utf-8] 错误。而如果你用 cmd 运行的话,可能就要用 gbk 等编码了。
    而针对这种情况下的你这段代码,其实最后没必要 .decode('utf-8') 一遍,因为 html 原本就是 utf-8 编码的 str 了,可以直接 print html 了,否则你需要多此一举 print html.decode('utf-8').encode('uff-8')。

    关于这个问题的详细解释和其它解决方法,请等我的长答案。
    KyL
        16
    KyL  
    OP
       2015-01-18 14:44:29 +08:00
    @Sylv 我确实是在用Sublime Text调试Py2的时候遇到这个问题的。str只可以hold1Byte字符的串,怎么会有所谓utf-8编码的str呢。期待着你的长答案。
    Sylv
        17
    Sylv  
       2015-01-18 15:16:23 +08:00
    @KyL str 存储的是 bytes 字节序列,不是只有 1 byte。它当然就有不同的编码,见下例,同样表示的是 “中文” 的 str 字符串,在不同编码下,其内部存储的字节序列是不同的:
    >>> text = u'中文'
    >>> text.encode('utf-8')
    '\xe4\xb8\xad\xe6\x96\x87'
    >>> text.encode('gbk')
    '\xd6\xd0\xce\xc4'
    KyL
        18
    KyL  
    OP
       2015-01-18 16:33:41 +08:00
    @Sylv py2中有两种字符串类型,str和unicode。 text = u'中文' 是Unicode类型,不是str类型。str只能储存ascii类型的字符吧。
    Sylv
        19
    Sylv  
       2015-01-19 04:10:37 +08:00
    @KyL 你概念理解有错

    首先,Python 2 unicode 和 str 转换方法要弄清楚:
    str.decode('utf-8') -> unicode
    unicode.encode('utf-8') -> str

    我上例中 text.encode('utf-8') 后已经是 str 了。

    然后你理解错了,str 并不是只能存储 ascii 类型的字符。str 存储的是一个一个的字节数据,也可以说就是一个一个字节的数字。然后这些数字代表的是什么字符,要看你用什么编码去解读它。

    例如现在一个 str 用一个字节存储了数字 97,那么你用 ascii 编码去解读它,那么这个 str 就是 'a'。

    然后现在有一个 str 用三个字节存储了三个数字:235、184 和 173,连起来用 16 进制表示也就是 '\xe4\xb8\xad'。然后你也可以用 ascii 编码去解读它,查 ascii 表后可以发现这三个数字都不在基本的 128 位 ascii 里,而是在扩展表里,都是一些很奇怪的字符,可见这个 str 用 ascii 编码去解读它对我们来说没有意义。
    但是我们换一个编码 'utf-8' 去解读它,这个 str 就变得有意义了,在 utf-8 编码里是用三个字节来存储一个汉字字符的,而不是像 ascii 编码一个字节就对应了一个字符。那么 235、184 和 173 这三个字节的数字在 utf-8 编码里对应的就是一个汉字字符的 “中”。你可以用以下方法验证:
    >>> char = u'中'
    >>> print type(char)
    <type 'unicode'>
    >>> char = char.encode('utf-8')
    >>> print type(char)
    <type 'str'>
    >>> print repr(char) # repr 方法可以将对象在 Python 内部的存储形式表现出来
    '\xe4\xb8\xad'
    >>> print char

    >>> print '\xe4\xb8\xad'


    在 print 这个 str 类型的 char 时,Python 只是把那三个数字直接发送给了用来显示的控制台(console)。console 就是用来输出的地方,例如 Sublime Text 的运行结果窗口,还有 Windows 下的 cmd。
    然后决定用什么编码去解读它,是由 console 来决定的。在 Sublime Text 下这个编码默认是 utf-8,它用 utf-8 去解读 235、184 和 173 这三个数字,发现是 “中” 字,那么它就会去字库里找出 “中” 字这个字形给我们显示出来,因此我们就能看到 “中” 了。
    而在 cmd 下,它用来解读的编码就不是 utf-8 了,而是 gbk 之类的。那它用 gbk 编码去解读这三个数字,可能它能找到另一些对应的字符,也有可能它完全找不到对应的字符,这时就产生了显示出乱码的情况。如果你想让它显示出 “中”,那么你就要让 Python 发送给它 gbk 编码下 “中” 所对应的数字,也就是 214 和 208,写成 16进制就是 '\xd6\xd0'。

    因此你在 print 的时候想要显示正常,要看你现在的输出 console 用的是什么编码,然后就要发送给它对应编码的 str。
    我最开始所说 html 是 utf-8 编码的 str,意思就是 html 里存储的字节序列,就是你想要的网页源码文字在 utf-8 编码下对应的一个个的数字,所以我们可以说它是 utf-8 编码的 str,因为它在 ascii 和 gbk 等编码下是没有意义的。Python 将它送给 Sublime Text, Sublime Text 也用 utf-8 编码去解读它,最后就能显示出你能看得懂的网页源代码文字。
    Sylv
        20
    Sylv  
       2015-01-20 12:44:26 +08:00
    @KyL 之前说的长答案 /t/163786
    KyL
        21
    KyL  
    OP
       2015-01-20 17:40:08 +08:00
    @Sylv 多谢
    KyL
        22
    KyL  
    OP
       2015-02-01 18:14:14 +08:00
    @aaaa007cn 为什么在cmd中使用gb18030是正确的呢。sys.stdout.encoding得到的结果是cp936,可是html.decode('utf-8').encode('cp936')报错。
    aaaa007cn
        23
    aaaa007cn  
       2015-02-01 18:47:21 +08:00
    https://en.wikipedia.org/wiki/Code_page_936
    https://en.wikipedia.org/wiki/GB_2312
    https://en.wikipedia.org/wiki/GBK
    https://en.wikipedia.org/wiki/GB_18030
    https://docs.python.org/2/library/codecs.html

    gb2312 收录 6763 个汉字
    gbk 收录 20900 多汉字
    gb18030 收录 70244 个汉字
    python2 中 cp936 是 gbk 的别名
    所以有时候 encode("cp936") 会出错
    一般在中文 windows 的 cmd 中用 python2 输出中文都是 encode("gb18030") 的
    KyL
        24
    KyL  
    OP
       2015-02-01 22:05:14 +08:00
    @aaaa007cn 谢谢
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5674 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 06:19 · PVG 14:19 · LAX 23:19 · JFK 02:19
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.