文档章节

记一次 Python 编码的坑

rainyear
 rainyear
发布于 2016/05/08 20:57
字数 974
阅读 106
收藏 2

这次又遇到了 Python 编码导致的问题,与 PyTips 0x07~0x09 中解释过的 Unicode - Bytes 不同,这次遇到的是另外一种情况。应用场景如下:爬虫抓取网页数据,通过 requests 模块将数据 POST 到服务器,但是要去除数据中的空白符(包括'\r\n'等)。

问题出在 requests 模块通过 JSON 格式传递数据:

import requests as req
import json
import re

title = '你好,\n世界'
req.post(API, data=json.dumps({'title': title}))

# API
data = self.requests.body.decode()
data = re.sub(r'\s', ' ', data)
save_data(json.loads(data))

虽然 HTTP 是通过二进制(也就是 Bytes)进行传输的,但通过 self.requests.body.decode() 仍然保持了 Unicode-Bytes-[HTTP]-Bytes-Unicode 的原则,因此实际上可以断定问题不是出自 Unicode 编码上,忽略掉中间传输过程,上面的代码可以简化为:

import json
import re
title = '你好,\n世界'
data = json.dumps({'title': title})
data = re.sub(r'\s', ' ', data)
print(json.loads(data))
{'title': '你好,\n世界'}

问题出现了,re.sub(r'\s', ' ', data) 并没有出去空白符,而实际上这样做看起来是没问题的:

print(re.sub(r'\s', ' ', "{'title': '你好,\n世界'}"))
{'title': '你好, 世界'}

之前提到了只要保持 Unicode-Bytes-Unicode三明治形式就不会受到编码问题的困扰(前提是 Python 3),经过和大家的讨论和探索之后发现问题出在 json.dumps

print(json.dumps({'title': title}))
{"title": "\u4f60\u597d\uff0c\n\u4e16\u754c"}

根据经验,在 Python 3 中如果出现 "\u4f60" 这样的原始 Unicode 编码就很可能意味着这并不是你想要的结果,我们只希望看到正常显示的 Unicode 或二进制形式的字符:

print("\u4f60")
print("\u4f60".encode())
你
b'\xe4\xbd\xa0'

经过 json.dumps() 之后会将原来字典类型中的值变为 ascii 编码,且不是 encode() 这种编码,而是 ascii() 式的编码:

help(ascii)
Help on built-in function ascii in module builtins:

ascii(obj, /)
    Return an ASCII-only representation of an object.
    
    As repr(), return a string containing a printable representation of an
    object, but escape the non-ASCII characters in the string returned by
    repr() using \\x, \\u or \\U escapes. This generates a string similar
    to that returned by repr() in Python 2.

其中的区别可以通过下面的例子说明:

def print_code_and_size(s):
    print(s, type(s), len(s))
yu = '雨'
print_code_and_size(yu)
print_code_and_size(yu.encode())
print_code_and_size(ascii(yu))
print_code_and_size(json.dumps(yu))
雨 <class 'str'> 1
b'\xe9\x9b\xa8' <class 'bytes'> 3
'\u96e8' <class 'str'> 8
"\u96e8" <class 'str'> 8

也就是说 json.dumps() 将原本的 Unicode 字符拆分成一个个单独的 ASCII 码,而不是正常的 encode(),不过该方法提供了一个参数 ensure_ascii = False 可以避免这种拆分:

print_code_and_size(json.dumps(yu, ensure_ascii=False))
"雨" <class 'str'> 3

虽然原理是更清楚了,不过可惜的是这样并没有解决我们当前的问题,因为换行符本身就是 ASCII 码,并不会受到 ensure_ascii 参数的影响:

r = '\n'
print_code_and_size(json.dumps(r, ensure_ascii=False))
print(list(json.dumps(r, ensure_ascii=False)))
"\n" <class 'str'> 4
['"', '\\', 'n', '"']

还是被拆分成了单独的字符,因此仍然无法对 json.dumps() 返回的字符串进行去空白符的操作。因此针对这一问题正确的做法应该是在 json.dumps() 之前先去除空格:

import json
import re
title = '你好,\n世界'
title = re.sub(r'\s', ' ', title)
data = json.dumps({'title': title})
print(json.loads(data))
{'title': '你好, 世界'}

总结

这个问题本不该浪费这么多时间,原因是与编码问题纠缠在一起,导致一开始的思路就是跑偏的。总结下来有两点:

  1. Unicode-Bytes-[[===]]-Bytes-Unicode 的模式可以解决绝大部分编码问题;
  2. json.dumpsascii 这种形式的编码对应的解码分别为json.loadseval,在它们两者之间不要对字符串进操作。

输入图片说明

© 著作权归作者所有

共有 人打赏支持
rainyear
粉丝 3
博文 9
码字总数 10955
作品 0
杭州
CTO(技术副总裁)
Python读取大文件的"坑“与内存占用检测

python读写文件的api都很简单,一不留神就容易踩”坑“。笔者记录一次踩坑历程,并且给了一些总结,希望到大家在使用python的过程之中,能够避免一些可能产生隐患的代码。 1.read()与readlin...

LeeHappen
08/24
0
0
学习 tornado 服务器 - 配合fastdfs上传下载

空间收藏于 2014-06-27 01:34 传送连接 : http://182.254.145.145/group1/M00/00/00/Co8plFWPh0LvBBVSAACAYF6Y1Z8204.htm 在用python测试上传的时候发现一旦文件名是汉字的程序就会爆出异常...

钟元OSS
07/18
0
0
关于Python的几个过坑指南(MySQL-python、tornado)

最近碰到的Python项目中,需要安装MySQL-python依赖,并且项目使用tornado,以下是记录在其中碰到的坑。 0x01 tornado在render渲染html页面时报错 访问页面时服务器响应500,查看错误日志,提...

日生三金
08/09
0
0
上周热点回顾(7.9-7.15)

热点随笔: · 上海程序员 落户攻略(小坦克) · 欠的债,这一次都还给你们(阿豪聊干货) · 男程序猿和女程序猿的网恋(一)(56899◎か) · 给正在努力的您几条建议(附开源代码)(陈珙...

博客园团队
07/16
0
0
CentOS7+Python2.7.14环境安装odoo11

最近了解了一下开源的OpenERP系统,尝试搭建了环境,中间踩了不少坑,记下来分享下。 odoo介绍 odoo, 原名openERP,是开源的ERP系统。使用python和js开发。 odoo官网 安装说明 OenpERP(已更...

无鱼二饼
2017/11/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

[Python进阶] Python命令行参数

Python 获得命令行参数的方法 需要模块:sys 参数个数:len(sys.argv) 脚本名: sys.argv[0] 参数1: sys.argv[1] 参数2: sys.argv[2] 解析命令行参数 Python提供了一个getopt模块,可用于解...

Eappo_Geng
8分钟前
0
0
add docker api url to jenkins

add docker api url to jenkins add jenkins to dockergroup gpasswd -a $USER docker gpasswd -a jenkins docker https://stackoverflow.com/questions/37178824/how-do-i-find-the-docker-......

kewei_zhang
12分钟前
0
0
Scala入门篇

1、定义变量 var 可变 val 不可变,相当于Java中的final Unit相当于Java中的void,以()表示 scala> val a = println("ddd") ddd a: Unit = () 2,声明数组 scala> val arr = Array(1,2,3,4,5) ......

算法之名
12分钟前
4
0
利用redis统计分布式集群中接口缓存命中情况

接口使用了缓存,想看看缓存命中率,到底提升了多少了?固想到做个统计方法,单机情况下使用 AtomicImteger,考虑到分布式集群中多台服务器调用,所以考虑使用redis进行统计 原来的想法很简单用分布...

计算机的小二青年
13分钟前
0
0
前端加密JS库--CryptoJS 使用指南

有时候项目涉及到的敏感数据比较多,为了信息安全,我们常常需要对一些数据进行接口加密处理,如编码、将明文转化为暗文、加密比对、AES + BASE64 算法加密等。 Base64 编码 为什么要编...

舒龙虎
15分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部