python 该如何处理 json 中的中文

2015-02-23 12:10:11 +08:00
 wozhapen

我是一个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'}


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

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

14871 次点击
所在节点    Python
38 条回复
mhycy
2015-02-23 20:55:32 +08:00
@wozhapen
我一般做法都是decode后再encode,对于显示成16进制的编码字串,可能还需要多次转换成真正的二进制内码

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

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

以上是我的个人做法,欢迎拍砖
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
2015-02-23 21:34:32 +08:00
@mhycy 哈哈,谢谢你的建议,这也是我喜欢的方式。。
这次遇坑主要是微信返回的源数据没有编码说明,导致我不知如何解码,经过9楼的回答算是明白原因。
谢谢你的回答。。
mhycy
2015-02-23 21:40:15 +08:00
@wozhapen 提起9楼的信息我倒是想起来了。。。
requests返回的数据建议手工使用chardet判定编码后解码,原因就是可能会有服务器返回不正确的编码声明
wozhapen
2015-02-23 21:50:46 +08:00
@mhycy 是的,返回的头部里'content-type': 'text/plain',未有编码信息。
昨天谷歌的时候有看到chardet,倒是没试过用它来检测。这次掉坑算是长了心眼。。
mhycy
2015-02-23 21:53:17 +08:00
@wozhapen 那货很不错,对中文来说文本相对较长的情况下检测精确度极高
wozhapen
2015-02-23 21:57:49 +08:00
@mhycy 好的,谢谢推荐,先收藏了,明天来试。
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
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
2015-02-24 21:48:53 +08:00
@wozhapen 文本太短的话置信度不可靠
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
2015-02-25 03:49:32 +08:00
@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'))。这样就不会出错了。
a2z
2015-02-25 09:07:22 +08:00
wozhapen
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
2015-02-25 23:23:00 +08:00
@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
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
2015-03-03 00:06:41 +08:00
@lincanbin 烧死你
Tink
2015-03-20 19:27:44 +08:00
反正我都是不管啥格式先decode成unicode,所有的处理都在unicode下进行,最后需要输出或者打印的时候,再转成utf8

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/172276

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX