在Python中进行URL解码UTF-8

402
在Python 2.7中,给定一个URL如example.com?title=%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B2%D0%B0%D1%8F+%D0%B7%D0%B0%D1%89%D0%B8%D1%82%D0%B0,如何将其解码为期望的结果example.com?title==правовая+защита
尝试过url=urllib.unquote(url.encode("utf8")), 但似乎给出了错误的结果。

4
通常情况下,URL的尾部只是一个cookie。你无法知道服务器使用哪种本地字符集编码,甚至不知道URL是否编码了一个字符串或其他完全不同的内容。(诚然,许多URL确实编码了可读的字符串;而且通常情况下,你可以很容易地猜出编码方式。但这在一般情况下或完全自动化时是不可能的。) - tripleee
5个回答

635

数据是使用URL转义进行UTF-8编码的字节,因此您需要使用urllib.parse.unquote()进行解码,该函数可以透明地将百分号编码的数据解码为UTF-8字节,然后再转换为文本:

from urllib.parse import unquote

url = unquote(url)

示例:

>>> from urllib.parse import unquote
>>> url = 'example.com?title=%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B2%D0%B0%D1%8F+%D0%B7%D0%B0%D1%89%D0%B8%D1%82%D0%B0'
>>> unquote(url)
'example.com?title=правовая+защита'

Python 2 的等效函数是 urllib.unquote(),但它返回一个字节串,所以您需要手动解码:

from urllib import unquote

url = unquote(url).decode('utf8')

3
那么为什么字符串中保留了加号(+)字符?我以为 %2B 是加号(+)字符,解码时会删除加字面量(literals)? - AlexLordThorsen
12
x-www-form-urlencoded数据中,+代表空格;您可以使用urllib.parse.parse_qs()来解析它,或者使用urllib.parse.unquote_plus()。但是+符号只应出现在查询字符串中,而不是URL的其余部分。 - Martijn Pieters

167
如果你正在使用 Python 3,你可以使用 urllib.parse.unquote函数:
url = """example.com?title=%D0%BF%D1%80%D0%B0%D0%B2%D0%BE%D0%B2%D0%B0%D1%8F+%D0%B7%D0%B0%D1%89%D0%B8%D1%82%D0%B0"""

import urllib.parse
urllib.parse.unquote(url)

提供:

'example.com?title=правовая+защита'

在Python3.8中,可以使用这种方法获取字典而不是查询字符串。 - Clocker
@Clocker 无法复现。请确保严格按照示例操作。如果您在将其适应自己的需求时遇到困难,请提出您自己的问题,并确保遵循 [ask] 和 [mre] 的建议。 - Karl Knechtel

26

使用requests库同样可以实现预期结果:

import requests

url = "http://www.mywebsite.org/Data%20Set.zip"

print(f"Before: {url}")
print(f"After:  {requests.utils.unquote(url)}")

输出:

$ python3 test_url_unquote.py

Before: http://www.mywebsite.org/Data%20Set.zip
After:  http://www.mywebsite.org/Data Set.zip

如果您已经在使用requests,那么可能会很方便,而不需要使用另一个库来完成此任务。


1
也适用于Python 2。 - lfurini
这只是urllib.parse的一个别名 - bfontaine
你的这条评论增加了很多内容。非常感谢你。 - ivanleoncz

2

我知道这是一个老问题,但是通过Google搜索我发现没有人提出只使用内置功能的解决方案。

所以我快速编写了自己的解决方案。

基本上一个URL字符串只能包含这些字符:A-Z、a-z、0-9、-、.、_、~、:、/、?、#、[、]、@、!、$、&、'、(、)、*、+、,、;、% 和=,其他所有字符都需要进行URL编码。

URL编码非常简单,就是一个百分号,后面跟随着对应于非法字符的代码点字节值的十六进制数字。

因此,基本上使用一个简单的while循环来迭代字符,如果它不是百分号,则添加任何字符的字节值,将索引增加1;否则添加跟在百分号后面的字节,并将索引增加3,累积这些字节并解码它们应该能够完美地工作。

以下是代码:

def url_parse(url):
    l = len(url)
    data = bytearray()
    i = 0
    while i < l:
        if url[i] != '%':
            d = ord(url[i])
            i += 1
        
        else:
            d = int(url[i+1:i+3], 16)
            i += 3
        
        data.append(d)
    
    return data.decode('utf8')

我已经测试过它,它完美地工作了。

2
在HTML中,URL可以包含HTML实体。这也会替换它们。
#from urllib import unquote #earlier python version
from urllib.request import unquote
from html import unescape
unescape(unquote('https://v.w.xy/p1/p22?userId=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&amp;confirmationToken=7uAf%2fxJoxRTFAZdxslCn2uwVR9vV7cYrlHs%2fl9sU%2frix9f9CnVx8uUT%2bu8y1%2fWCs99INKDnfA2ayhGP1ZD0z%2bodXjK9xL5I4gjKR2xp7p8Sckvb04mddf%2fiG75QYiRevgqdMnvd9N5VZp2ksBc83lDg7%2fgxqIwktteSI9RA3Ux9VIiNxx%2fZLe9dZSHxRq9AA'))

html.unescape 是不必要的。 - pylover
在我的电脑上没有使用unescape函数,所以示例中的&amp;没有被转换为&。我刚刚用Python 3.9.7进行了检查。 - Roland Puntaier
问题是关于解码URL,而不是HTML。 - bfontaine

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接