用Python实现URL Encoding和Decoding

本文介绍了Python中URL编码和解码的过程,通过示例详细解释了如何使用encode和decode方法,以及在处理UTF-16编码时可能出现的问题和解决办法。还探讨了Python不同版本中urllib.parse模块unquote函数的实现差异。
摘要由CSDN通过智能技术生成

前些日子在一个论坛上看到网友拿03版《天龙八部》和13版《天龙八部》作对比。在比较两个版本的片尾曲的时候,提到了03版的片尾曲《宽恕》。帖子中提到,这首歌由王菲演唱、林夕作词、赵季平(电视剧《关西无极刀》片头曲的作者)作曲。


记得高中时看《天龙》的时候就挺喜欢这首歌的,于是就用火狐浏览器下载了这首歌。但是下载得到的文件名是:%E5%AE%BD%E6%81%95.mp3。用浏览器在网上下载包含汉字的文件时经常会遇到这种情况,于是我就想写个小程序一劳永逸解决这个问题——把这种“奇怪”的文件名还原回它本来的“面目”。

印象中,在CSDN论坛上看到有人讨论过这个问题,好像说这其实是中文文件名经过UTF-8编码产生的。在Python中处理字符编码解码问题很简单,所以我打算写个Python脚本解决这个问题。

在Python 3.x中,一个str对象可以通过调用encode方法来编码得到一个bytes类型的字节序列。而bytes对象则有一个decode方法来实现字节序列的解码操作。看一个例子:

>>> song = '海阔天空'
>>> song_bytes = song.encode('utf-8') # 以UTF-8编码song这个字符串
>>> song_bytes
b'\xe6\xb5\xb7\xe9\x98\x94\xe5\xa4\xa9\xe7\xa9\xba'
>>> song_bytes.decode('utf-8')
'海阔天空'

这个例子中,把“海阔天空”以utf-8形式编码,得到\xe6\xb5\xb7\xe9\x98\x94\xe5\xa4\xa9\xe7\xa9\xba这样一个字节序列。那么,如果创建一个文本文件“song.txt”,在其中敲入“海阔天空”并按UTF-8编码保存,然后用任意一个具有二进制数据编辑/显示功能的编辑器以二进制形式打开,我们将看到这样的字节序列:E6B5B7E99894E5A4A9E7A9BA。


同样:

>>> song = '宽恕'
>>> song_bytes = song.encode('utf-8')
>>> song_bytes
b'\xe5\xae\xbd\xe6\x81\x95'

即,把歌曲名“宽恕”按UTF-8编码将得到字节序列:

E5AEBDE68195
和上面我用浏览器下载得到的文件名:
%E5%AE%BD%E6%81%95
对比一下,我们会发现其中对应关系。

于是我有了这样的思路:
1. 从str类型的字符串: "%E5%AE%BD%E6%81%95"得到一个bytes类型的字节序列:b'\xe5\xae\xbd\xe6\x81\x95';
2. 对第1步中得到的字节序列进行解码,得到一个str类型的“正常”文件名。


第2步很简单。第1步,我们可以通过bytes类的fromhex方法来完成。

bytes.fromhex会把传入的字符串形式的十六进制数字(如:'E5 AE BD E6 81 95')转换成相应的bytes类型字节序列(如:b'\xe5\xae\xbd\xe6\x81')——前者两个十六进制数字对应后者一个字节,并忽略所有空白。具体代码如下:


>>> strange_file_name = "%E5%AE%BD%E6%81%95"
>>> strange_file_name = strange_file_name.replace('%', '')
>>> strange_file_name
'E5AEBDE68195'
>>> strange_file_name_bytes = bytes.fromhex(_)
>>> strange_file_name_bytes
b'\xe5\xae\xbd\xe6\x81\x95'
>>> _.decode('utf-8')
'宽恕'

效果不错!我忍不住要赞美生活了。但是,要一劳永逸,我还需要把它封装成一个小函数:

from re import compile as re_compile

_percent_pat = re_compile(r'(?:%[A-Fa-f0-9]{2})+')

def percent_decode(string):
    for substr in _percent_pat.findall(string):
        substr_dec = bytes.fromhex(
            substr.replace('%', '')).decode('utf-8')
        string = string.replace(substr, substr_dec)
    return string

前面,下载得到的歌曲名为:“%E5%AE%BD%E6%81%95.mp3”。其中的歌曲名称部分需要解码处理,而后缀".mp3"原封不动就行了。这也是上面percent_decode函数中使用循环的原因:使用正则表达式找到所有的经过UTF-8编码的序列,并解码。

本文到此,应该结束了。不过,幸好自己多想了一点:既然有解码,那么也应该有一个编码函数,这样才完整。但是,bytes并没有提供一个fromhex对应的“反操作”(Python中,float类型提供了fromhex,同时又提供了相应的hex。但需要提一点的是float.fromhex和bytes.fromhex的功能并不一样)。而我又想以一种看起来比较优美的方式来实现这个编码函数。于是,谷歌之。

搜索之后发现,生成包含百分号的文件名其实就是所谓的“URL Encoding”或“Percent Encoding百分号编码)”(我还找到了一个提供在线URL Encoding/Decoding的网站。)。而且Python标准库中已经提供相关模块来实现上面的“编码”与“解码”(示例代码)。(其实,我是在了解了这些之后才把上面我实现的解码函数命名为percent_decode的。)

在Python 3.x中,urllib.parse模块提供了如下几个函数:
urllib.parse.quote(string, safe='/', encoding=None, errors=None)
该函数实现百分号编码操作;

urllib.parse.quote_plus(string, safe='', encoding=None, errors=None)
同上,不过使用字符'+'替换掉string中的空格字符' ';

urllib.parse.quote_from_bytes(bytes, safe='/')
该函数把形如b'\xe5\xae\xbd\xe6\x81\x95'的字节序列编码成形如%E5%AE%BD%E6%81%95的字符序列;

下面这三个函数分别是上面这三个函数的“反操作”:
urllib.parse.unquote(string, encoding='utf-8', errors='replace')
urllib.parse.unquote_plus(string, encoding='utf-8', errors='replace')
urllib.parse.unquote_to_bytes(string)


urllib.parse.urlencode(query, doseq=False, safe='', encoding=None, errors=None)

该函数能根据query中的数据,通过调用quote_plus生成URL query string。比如,我们在使用用户名、密码登陆某个论坛的时候,或者在某个网站上搜索关键词的时候,urlencode能帮助我们得到最终的查询链接:

>>> from urllib.parse import urlencode
>>> query_filter = {'song': '宽恕', 'artist': '王菲'}
>>> query_parms = urlencode(query_filter)
>>> query_parms
'artist=%E7%8E%8B%E8%8F%B2&song=%E5%AE%BD%E6%81%95'
>>> query_url = 'http://www.example.com/query?{}'.format(query_parms)
>>> query_url
'http://www.example.com/query?artist=%E7%8E%8B%E8%8F%B2&song=%E5%AE%BD%E6%81%95'

上面提到的这几个函数完全可以满足我的需求。而且,通过写percent_decode我也大致明白了百分号编码的解码过程,那么,顺道学一下百分号编码的编码过程吧。即,学习urllib.parse模块的quote、quote_plus是怎么实现的。


首先,我说一下我的猜测:

从上面实现的percent_decode可以看出,由于str和bytes已经把字符串的编码和解码工作封装好了(str提供了encode接口,bytes提供了decode接口),所以在percent_decode实现解码的过程中,我只是把str形式的包含百分号的字符串转换成了相应的bytes形式的字节序列,然后把转换的结果丢给bytes的decode方法来得到最终的结果。那么,也可以猜测到,编码的过程则是:先使用str的encode方法得到一个bytes类型的字节序列,然后再转换成包含百分号的字符串形式。我不知道怎么去“优雅”地实现这个过程(我认为的优雅就是:代码尽量简洁、紧凑、尽量使用现有函数——比如前面的fromhex),那么就看Python是怎么实现的吧。


以下代码代码摘取自Python 3.3.3的urllib.parse模块(其中,以"##"开头的中文注释是我对这部分代码的理解):

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值