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
wozhapen
V2EX  ›  Python

python 该如何处理 json 中的中文

  •  
  •   wozhapen · 2015-02-23 12:10:11 +08:00 · 14782 次点击
    这是一个创建于 3348 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我是一个python小白,最近在模拟微信的web协议,无聊练练,然后碰到中文乱码的问题,看文档问谷歌,弄了一天了也无果,所以请教大家。

    相关描述

    我借助的是requests来作get、post请求,创建了一个session全程使用,我这次请求返回的是一个json,
    当我打印原文(response.text)的时候中文显示如下:


    36氪(36Kr.com)是中国领先的科技新媒体,我们报道最新的互联网科技新闻以及最有潜力的互联网创业企业。

    进行json解析后(response.json())貌似已经变成unicode编码的ascii,打印的结果中文是这样的:


    u'36\xe6\xb0\xaa(36Kr.com)\xe6\x98\xaf\xe4\xb8\xad\xe5\x9b\xbd\xe9\xa2\x86\xe5\x85\x88\xe7\x9a\x84\xe7\xa7\x91\xe6\x8a\x80\xe6\x96\xb0\xe5\xaa\x92\xe4\xbd\x93\xef\xbc\x8c\xe6\x88\x91\xe4\xbb\xac\xe6\x8a\xa5\xe9\x81\x93\xe6\x9c\x80\xe6\x96\xb0\xe7\x9a\x84\xe4\xba\x92\xe8\x81\x94\xe7\xbd\x91\xe7\xa7\x91\xe6\x8a\x80\xe6\x96\xb0\xe9\x97\xbb\xe4\xbb\xa5\xe5\x8f\x8a\xe6\x9c\x80\xe6\x9c\x89\xe6\xbd\x9c\xe5\x8a\x9b\xe7\x9a\x84\xe4\xba\x92\xe8\x81\x94\xe7\xbd\x91\xe5\x88\x9b\xe4\xb8\x9a\xe4\xbc\x81\xe4\xb8\x9a\xe3\x80\x82'

    json解析后是一个dict,当我单独打印键值的时候中文显示是:


    36氪(36Kr.com)是中国领先的科技新媒体,我们报道最新的互联网科技新闻以及最有潜力的互联网创业企业。

    问题来了

    • 我获取的返回头部可知就是个纯文本
    {'connection': 'close', 'content-type': 'text/plain', 'content-length': '14315', 'content-encoding': 'deflate'}
    
    • 手动得中文内容

    • 我该如何正确的打印这个中文内容或者该如何与已知中文字符串比较,我的环境是

      • ubuntu14.04,
      • python2.7.6(系统自带的,需要换上python3吗??),
      • sys.getdefaultencoding()是‘ascii’,换成‘utf-8’后也是如上所述

    为了解决这个问题,我特地看了微信的web前端js关于那一段的请求处理,表示水平有限没发现什么。

    请大家帮忙解释一下上述原因,或者给个处理中文的思路!!

    38 条回复    2015-03-20 19:27:44 +08:00
    lincanbin
        1
    lincanbin  
       2015-02-23 12:14:39 +08:00   ❤️ 4
    print userinfo['NickName'].encode('iso8859-1')

    用PHP之类的语言的话就不需要考虑编码问题,人生苦短,不要用Python。
    fish267
        2
    fish267  
       2015-02-23 12:18:46 +08:00   ❤️ 1
    方法一: python3 解除编码烦恼。
    方法二:
    def unicode_init(obj, encoding = 'utf-8'):
    if isinstance(obj, basestring):
    if not isinstance(obj, unicode):
    obj = unicode(obj, encodeing)
    return obj
    zwzmzd
        3
    zwzmzd  
       2015-02-23 12:24:02 +08:00 via Android   ❤️ 1
    dict里面的中文用unicode形式存储,传输之前用json.dumps导出标准json,各个套件跟着规范走不会有问题
    wozhapen
        4
    wozhapen  
    OP
       2015-02-23 12:28:49 +08:00
    @lincanbin 可行。。谢谢。。
    哈哈,春节用python练手。。
    funagi
        5
    funagi  
       2015-02-23 12:32:52 +08:00   ❤️ 1
    试试
    ```
    response = session.get('http://xxxx')
    print(response.encoding)
    response.encoding = 'utf-8'
    print(response.json())
    ```
    wozhapen
        6
    wozhapen  
    OP
       2015-02-23 12:33:12 +08:00
    @fish267 好的,我尝试python3,
    方法二昨天已试过不行。。
    wozhapen
        7
    wozhapen  
    OP
       2015-02-23 12:34:57 +08:00
    @zwzmzd 我是从微信那边获取的数据,很显然我无法事先处理。。仍然谢谢你。。
    wozhapen
        8
    wozhapen  
    OP
       2015-02-23 12:39:41 +08:00
    @funagi 太棒了!!可行。。
    Sylv
        9
    Sylv  
       2015-02-23 13:00:29 +08:00 via iPhone   ❤️ 3
    这种情况是因为数据源的编码声明不规范,导致 reuqests 无法判断出返回数据的正确编码,从而使用了它的默认编码 iso8859-1 来将数据转换为 unicode,没使用正确的编码 utf-8,于是就出现乱码了。
    解决办法一就是手动设置返回数据的正确编码:response.encoding = 'utf-8'。这样结果就是正确的 unicode 类型字符串。
    或者将错误的 unicode 用同样的 iso8859-1 编码转换回去原本的 utf-8 编码的 str 类型字符串:data.encode('iso8859-1')。
    a2z
        10
    a2z  
       2015-02-23 13:02:30 +08:00   ❤️ 1
    前几天刚入这个坑:
    Python内部数据结构标准内码是unicode
    json.loads, json.dumps之类的只能接受unicode字符串,有些没有特殊字符的utf8之类可以侥幸通过
    requests.text会自动把网页解码unicode,输入到json模块的时候不用.encode(),那样画蛇添足

    你显示,保存的时候再用encode转换成其他格式比如utf8
    billlee
        11
    billlee  
       2015-02-23 13:13:26 +08:00   ❤️ 1
    ```python
    data_ugly = response.json()
    Signature = data_ugly['Signature'].encode('iso8859-1').decode('utf-8')`
    ```

    估计换 python 3 是没用的,可以查查响应头的 Content-Type 里的 charset 是不是 utf-8. 如果不是,那就是你访问的网站没有处理好编码问题;如果是,那就是 requests 对编码的处理 有问题。
    popbones
        12
    popbones  
       2015-02-23 13:31:49 +08:00
    学python必经之路
    wozhapen
        13
    wozhapen  
    OP
       2015-02-23 13:37:18 +08:00 via iPhone
    @Sylv 谢谢。
    wozhapen
        14
    wozhapen  
    OP
       2015-02-23 13:42:15 +08:00 via iPhone
    @a2z 你的解释可能是对的,但是我有试过打印的时候encode('utf-8'),不行的。。
    wozhapen
        15
    wozhapen  
    OP
       2015-02-23 13:52:39 +08:00
    @billlee 恩恩,你的解决方式跟一楼类似。
    碰到这个问题,我第一时间就是看头部,我在描述里也贴出了是'content-type': 'text/plain',编码未知,然后我就不知道该怎么处理了。。
    codegear
        16
    codegear  
       2015-02-23 14:30:11 +08:00
    Python中实际上有两种字符串,分别是str类型和unicode类型,这两者都是basestring的派生类。str实际上相当于string, 而unicode则是标准的字符串,相当于wstring。
    其中I/O读入的基本都是str类型,也就是说Python不关心这些字节代表什么含义。但是如果需要做真正的字符串操作的时候,建议使用unicode类型,这样确保不存在编码问题。
    JSON理论上应该可用ASCII完全表示,其中的宽字符用类似\u1111的形式表达。但是现在有很多不规范的JSON出现,例如s="""{"标题": "Hello, world"}"""。此时就需要你自己确定这部分内容用字节表达时,究竟用了什么编码,如果是UTF-8,那么就首先s_u = s.decode("UTF-8")变成unicode字符串。

    另一方面,只要保证这段字符串的编码和Python运行时环境的编码保持一致,也是可以直接loads(str类型变量)的。例如Linux下如果LANG是UTF-8,那么python屁颠屁颠跑着的时候就可以直接把UTF-8编码的JSON串塞进去。
    codegear
        17
    codegear  
       2015-02-23 14:31:29 +08:00
    貌似偏题了,sorry,请无视
    jas0ndyq
        18
    jas0ndyq  
       2015-02-23 15:11:48 +08:00 via iPad
    python编码着实坑
    mhycy
        19
    mhycy  
       2015-02-23 18:16:35 +08:00
    不要尝试在交互式解析器或者命行环境中直接输出文本,一个比一个坑
    最可靠的做法是把文本保存成一个文件然后再查看。

    如果进行比较,先把输入的字节码转成UTF8(解析成UTF8)字符串,然后再进行对比
    wozhapen
        20
    wozhapen  
    OP
       2015-02-23 20:31:53 +08:00
    @mhycy 谢谢指导,我昨天试过保存在文本中也是乱码。

    我试过把如下unicode编码的字节码
    ```
    u'36\xe6\xb0\xaa(36Kr.com)\xe6\x98\xaf\xe4\xb8\xad\xe5\x9b\xbd\xe9\xa2\x86\xe5\x85\x88\xe7\x9a\x84\xe7\xa7\x91\xe6\x8a\x80\xe6\x96\xb0\xe5\xaa\x92\xe4\xbd\x93\xef\xbc\x8c\xe6\x88\x91\xe4\xbb\xac\xe6\x8a\xa5\xe9\x81\x93\xe6\x9c\x80\xe6\x96\xb0\xe7\x9a\x84\xe4\xba\x92\xe8\x81\x94\xe7\xbd\x91\xe7\xa7\x91\xe6\x8a\x80\xe6\x96\xb0\xe9\x97\xbb\xe4\xbb\xa5\xe5\x8f\x8a\xe6\x9c\x80\xe6\x9c\x89\xe6\xbd\x9c\xe5\x8a\x9b\xe7\x9a\x84\xe4\xba\x92\xe8\x81\x94\xe7\xbd\x91\xe5\x88\x9b\xe4\xb8\x9a\xe4\xbc\x81\xe4\xb8\x9a\xe3\x80\x82'
    ```
    转成utf-8得到如下不想要的结果(貌似是简单的把单字节加上‘\xc2’变成双字节)
    ```
    '36\xc3\xa6\xc2\xb0\xc2\xaa(36Kr.com)\xc3\xa6\xc2\x98\xc2\xaf\xc3\xa4\xc2\xb8\xc2\xad\xc3\xa5\xc2\x9b\xc2\xbd\xc3\xa9\xc2\xa2\xc2\x86\xc3\xa5\xc2\x85\xc2\x88\xc3\xa7\xc2\x9a\xc2\x84\xc3\xa7\xc2\xa7\xc2\x91\xc3\xa6\xc2\x8a\xc2\x80\xc3\xa6\xc2\x96\xc2\xb0\xc3\xa5\xc2\xaa\xc2\x92\xc3\xa4\xc2\xbd\xc2\x93\xc3\xaf\xc2\xbc\xc2\x8c\xc3\xa6\xc2\x88\xc2\x91\xc3\xa4\xc2\xbb\xc2\xac\xc3\xa6\xc2\x8a\xc2\xa5\xc3\xa9\xc2\x81\xc2\x93\xc3\xa6\xc2\x9c\xc2\x80\xc3\xa6\xc2\x96\xc2\xb0\xc3\xa7\xc2\x9a\xc2\x84\xc3\xa4\xc2\xba\xc2\x92\xc3\xa8\xc2\x81\xc2\x94\xc3\xa7\xc2\xbd\xc2\x91\xc3\xa7\xc2\xa7\xc2\x91\xc3\xa6\xc2\x8a\xc2\x80\xc3\xa6\xc2\x96\xc2\xb0\xc3\xa9\xc2\x97\xc2\xbb\xc3\xa4\xc2\xbb\xc2\xa5\xc3\xa5\xc2\x8f\xc2\x8a\xc3\xa6\xc2\x9c\xc2\x80\xc3\xa6\xc2\x9c\xc2\x89\xc3\xa6\xc2\xbd\xc2\x9c\xc3\xa5\xc2\x8a\xc2\x9b\xc3\xa7\xc2\x9a\xc2\x84\xc3\xa4\xc2\xba\xc2\x92\xc3\xa8\xc2\x81\xc2\x94\xc3\xa7\xc2\xbd\xc2\x91\xc3\xa5\xc2\x88\xc2\x9b\xc3\xa4\xc2\xb8\xc2\x9a\xc3\xa4\xc2\xbc\xc2\x81\xc3\xa4\xc2\xb8\xc2\x9a\xc3\xa3\xc2\x80\xc2\x82'
    ```
    ********
    当然,对于与已知字符串比较的话,实际上可以构造出已知字符串的unicode编码的字节码,然后进行比较,其实我想知道的是更人性化的比较方式。
    谢谢你的回答。
    mhycy
        21
    mhycy  
       2015-02-23 20:55:32 +08:00   ❤️ 1
    @wozhapen
    我一般做法都是decode后再encode,对于显示成16进制的编码字串,可能还需要多次转换成真正的二进制内码

    第一次decode是原始字节码以一个特定字符集解析,然后变成内部unicode编码
    第二次encode是内部unicode编码变成最终对比时候的一个确定的字符集与编码以保证对比时候的准确性。

    对于内部写死在源码的字符串,一般直接encode成目标字符集进行对比(即便字符集一致)以保证跨平台时候的结果准确性。。

    以上是我的个人做法,欢迎拍砖
    aaaa007cn
        22
    aaaa007cn  
       2015-02-23 20:58:24 +08:00
    因为这个已经是 utf8 编码的字节码
    所以你应该通过 decode 拿到原始 unicode 字符串

    b'36\xe6\xb0\xaa(36Kr.com)\xe6\x98\xaf\xe4\xb8\xad\xe5\x9b\xbd\xe9\xa2\x86\xe5\x85\x88\xe7\x9a\x84\xe7\xa7\x91\xe6\x8a\x80\xe6\x96\xb0\xe5\xaa\x92\xe4\xbd\x93\xef\xbc\x8c\xe6\x88\x91\xe4\xbb\xac\xe6\x8a\xa5\xe9\x81\x93\xe6\x9c\x80\xe6\x96\xb0\xe7\x9a\x84\xe4\xba\x92\xe8\x81\x94\xe7\xbd\x91\xe7\xa7\x91\xe6\x8a\x80\xe6\x96\xb0\xe9\x97\xbb\xe4\xbb\xa5\xe5\x8f\x8a\xe6\x9c\x80\xe6\x9c\x89\xe6\xbd\x9c\xe5\x8a\x9b\xe7\x9a\x84\xe4\xba\x92\xe8\x81\x94\xe7\xbd\x91\xe5\x88\x9b\xe4\xb8\x9a\xe4\xbc\x81\xe4\xb8\x9a\xe3\x80\x82'.decode('utf8')

    注意开头那个 b
    2.x 可省略

    源编码未知的情况下
    建议直接二进制写入文本文件
    然后使用文本编辑器确认是哪种编码
    wozhapen
        23
    wozhapen  
    OP
       2015-02-23 21:34:32 +08:00
    @mhycy 哈哈,谢谢你的建议,这也是我喜欢的方式。。
    这次遇坑主要是微信返回的源数据没有编码说明,导致我不知如何解码,经过9楼的回答算是明白原因。
    谢谢你的回答。。
    mhycy
        24
    mhycy  
       2015-02-23 21:40:15 +08:00
    @wozhapen 提起9楼的信息我倒是想起来了。。。
    requests返回的数据建议手工使用chardet判定编码后解码,原因就是可能会有服务器返回不正确的编码声明
    wozhapen
        25
    wozhapen  
    OP
       2015-02-23 21:50:46 +08:00
    @mhycy 是的,返回的头部里'content-type': 'text/plain',未有编码信息。
    昨天谷歌的时候有看到chardet,倒是没试过用它来检测。这次掉坑算是长了心眼。。
    mhycy
        26
    mhycy  
       2015-02-23 21:53:17 +08:00
    @wozhapen 那货很不错,对中文来说文本相对较长的情况下检测精确度极高
    wozhapen
        27
    wozhapen  
    OP
       2015-02-23 21:57:49 +08:00
    @mhycy 好的,谢谢推荐,先收藏了,明天来试。
    zeroday
        28
    zeroday  
       2015-02-23 22:55:02 +08:00
    不知道下面的用法可不可行。

    将 utf-8 转化为 unicode
    response = unicode( response, 'utf-8' )
    将 unicode 转化为 utf-8 输出
    print response.encode('utf-8')
    wozhapen
        29
    wozhapen  
    OP
       2015-02-24 21:33:02 +08:00
    @aaaa007cn 你好,我不知道如何弄你说的那个 decode 拿到原始 unicode 字符串,所以没试,而对于你所说的接二进制写入文本文件我之前试过,
    ```
    f = open('./html','wb')
    f.write(response.text)
    f.close()
    ```
    还是需要设置sys.setdefaultencoding('utf8'),不然会出现
    ```
    UnicodeEncodeError: 'ascii' codec can't encode characters in position 131-148: ordinal not in range(128)
    ```

    @mhycy 我试了那个chardet,确实很强大,还给出了置信度,谢谢你,算是又多了一个工具。

    {'confidence': 0.99, 'encoding': 'utf-8'}


    @zeroday 我试了,貌似不行。
    ```
    t = unicode(response.text, 'utf-8')
    print t.encode('utf-8')
    ```
    出现 TypeError: decoding Unicode is not supported

    ***

    好了,这个帖子应该可以结了。后来者翻到的话,可以参考1、5、9楼。谢谢大家!!
    mhycy
        30
    mhycy  
       2015-02-24 21:48:53 +08:00
    @wozhapen 文本太短的话置信度不可靠
    aaaa007cn
        31
    aaaa007cn  
       2015-02-24 22:18:54 +08:00
    @wozhapen

    那是对 20 楼的回复
    >> 我试过把如下unicode编码的字节码
    >> 转成utf-8得到如下不想要的结果
    你给的其实是 utf8 编码的字节码
    而不是 unicode 字符串
    把一个 utf8 编码的字节码当作 unicode 字符串再次进行 utf8 编码当然得不到你所期望的结果

    至于把网页内容直接写入文本

    看源码

    Response.text
    https://github.com/kennethreitz/requests/blob/master/requests/models.py#L740-L751
    Content of the response, in unicode.

    Response.content
    https://github.com/kennethreitz/requests/blob/master/requests/models.py#L716-L718
    Content of the response, in bytes.

    所以你得
    f = open('./html','wb')
    f.write(response.content)
    f.close()

    或者显式地
    f = open('./html','wb')
    f.write(response.text.encode('??')) # 这个该填什么编码由具体情况决定
    f.close()

    我个人不建议折腾 sys.setdefaultencoding

    @mhycy
    requests 自己就是用 chardet 来自动检测的
    https://github.com/kennethreitz/requests/tree/master/requests/packages/chardet
    https://github.com/kennethreitz/requests/blob/master/requests/models.py#L636-L639
    Sylv
        32
    Sylv  
       2015-02-25 03:49:32 +08:00   ❤️ 1
    @wozhapen 你写文件出现 UnicodeEncodeError 的原因是:
    response.text 是 unicode 类型的字符串,Python 需要将其转换为 str 类型的字符串后才能写入文件,而 Python2 的默认编码是 'ascii',因此写入时其实等同执行了:f.write(response.text.encode('ascii')),而中文是无法用 ascii 来编码的,所以就出现了 UnicodeEncodeError。
    虽然可以用 sys.setdefaultencoding('utf8') 来将 Python 默认编码更改为 utf-8 来解决这个问题,但这个方法从来不是解决 Python2 编码相关问题的正确方法,是有后遗症的,原因见 /t/163786
    正确的方法是写入文件时显式地将 unicode 字符串用正确编码转换为 str 字符串:f.write(response.text.encode('utf-8'))。这样就不会出错了。
    wozhapen
        34
    wozhapen  
    OP
       2015-02-25 22:47:30 +08:00
    @mhycy
    @aaaa007cn
    是的,chardet的检测确实有问题,但又好像是requests的问题,我
    print response.encoding 得到是 ‘ISO-8859-1’(好像真像9楼Sylv说的那样)
    而用 chardet.detect(response.text) 得到是 ‘utf-8’
    但看了源码requests 确实是用 chardet 来检测得到encoding的值,按理说应该是一样的值不。

    真心谢谢 @aaaa007cn 的提醒,让我看了下源码,又get了些知识。

    ***

    @Sylv 谢谢你的总结,分析好透彻,读了两遍,已打印下来再读,大牛啊!!方法2是个好习惯,像我这样的入门小白应该培养。


    @a2z 不好意思,因为时间问题,你的资料我明天再读,有想法再请教。。
    aaaa007cn
        35
    aaaa007cn  
       2015-02-25 23:23:00 +08:00   ❤️ 2
    @wozhapen

    同样可以通过读源码来理解

    response.encoding 在这里赋值
    https://github.com/kennethreitz/requests/blob/master/requests/adapters.py#L214

    get_encoding_from_headers 的定义在这里
    https://github.com/kennethreitz/requests/blob/master/requests/utils.py#L308-L325

    所以 response.encoding 为 'ISO-8859-1'

    当且仅当 response.encoding 为 None 时才会 fallback 去使用 chardet 自动检测 response.content
    https://github.com/kennethreitz/requests/blob/master/requests/models.py#L760-L762
    也不会把检测结果保存到 response.encoding
    wozhapen
        36
    wozhapen  
    OP
       2015-02-26 22:26:07 +08:00
    @a2z 谢谢,仔细的读了那篇文章,通篇得出python的一个使用好习惯来避免编码问题,就是 @Sylv 总结的第2个解决方法
    > make your code into a Unicode-only clean room
    > put ‘airlocks’ at the entry points to your code which will ensure that any byte sequence attempting to enter your code is properly clothed in a protective Unicode bunny suit before being allowed inside.
    > 将输入的 bytes 马上解码成 unicode,而在程序内部中均使用 unicode,而当在进行输出的时候,尽早将之编码成 bytes

    @aaaa007cn 大牛厉害啊!! 我确实该花些时间来对着文档阅读一下requests的源码。

    谢谢你们!!还未入门的小白碰上你们这群大牛真幸运!!送出感谢!!
    tolerious
        37
    tolerious  
       2015-03-03 00:06:41 +08:00 via iPhone
    @lincanbin 烧死你
    Tink
        38
    Tink  
       2015-03-20 19:27:44 +08:00
    反正我都是不管啥格式先decode成unicode,所有的处理都在unicode下进行,最后需要输出或者打印的时候,再转成utf8
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1988 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 16:16 · PVG 00:16 · LAX 09:16 · JFK 12:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.