53

我有一个像“Tanım”这样的unicode字符串,它以某种方式编码为“Tan%u0131m”。如何将此编码字符串转换回原始 unicode。显然 urllib.unquote 不支持 unicode。

4

5 回答 5

70

%uXXXX 是一种非标准的编码方案,已被 w3c 拒绝,尽管实现仍然存在于 JavaScript 领域。

更常见的技术似乎是对字符串进行 UTF-8 编码,然后使用 %XX 对结果字节进行 % 转义。urllib.unquote 支持此方案:

>>> urllib2.unquote("%0a")
'\n'

不幸的是,如果你真的需要支持 %uXXXX,你可能不得不推出自己的解码器。否则,可能更可取的是简单地 UTF-8 编码您的 unicode,然后 % 转义结果字节。

一个更完整的例子:

>>> u"Tanım"
u'Tan\u0131m'
>>> url = urllib.quote(u"Tanım".encode('utf8'))
>>> urllib.unquote(url).decode('utf8')
u'Tan\u0131m'
于 2008-11-18T23:22:44.060 回答
11
def unquote(text):
    def unicode_unquoter(match):
        return unichr(int(match.group(1),16))
    return re.sub(r'%u([0-9a-fA-F]{4})',unicode_unquoter,text)
于 2008-11-18T23:22:24.703 回答
7

如果你绝对必须这样做(我真的同意“非标准”的呼声),这将做到这一点:

from urllib import unquote

def unquote_u(source):
    result = unquote(source)
    if '%u' in result:
        result = result.replace('%u','\\u').decode('unicode_escape')
    return result

print unquote_u('Tan%u0131m')

> Tanım
于 2008-11-18T23:32:49.127 回答
4

上述版本中存在一个错误,有时当字符串中同时存在 ascii 编码和 unicode 编码字符时,它会吓坏。我认为它特别是当除了 unicode 之外还有 '\xab' 等高 128 范围内的字符时。

例如。“%5B%AB%u03E1%BB%5D”会导致此错误。

我发现如果你只是先做 unicode ,问题就消失了:

def unquote_u(source):
  result = source
  if '%u' in result:
    result = result.replace('%u','\\u').decode('unicode_escape')
  result = unquote(result)
  return result
于 2008-12-16T03:13:58.407 回答
2

您有一个使用非标准编码方案的 URL ,被标准机构拒绝,但仍由某些编码器生成。Pythonurllib.parse.unquote()函数无法处理这些。

幸运的是,创建自己的解码器并不难。%uhhhh这里的条目是UTF-16代码点,所以我们需要考虑代理对。我还看到%hh混入了代码点,增加了混乱。

考虑到这一点,这里有一个可以在 Python 2 和 Python 3 中工作的解码器,前提是你str在 Python 3 中传入一个对象(Python 2 不太关心):

try:
    # Python 3
    from urllib.parse import unquote
    unichr = chr
except ImportError:
    # Python 2
    from urllib import unquote

def unquote_unicode(string, _cache={}):
    string = unquote(string)  # handle two-digit %hh components first
    parts = string.split(u'%u')
    if len(parts) == 1:
        return parts
    r = [parts[0]]
    append = r.append
    for part in parts[1:]:
        try:
            digits = part[:4].lower()
            if len(digits) < 4:
                raise ValueError
            ch = _cache.get(digits)
            if ch is None:
                ch = _cache[digits] = unichr(int(digits, 16))
            if (
                not r[-1] and
                u'\uDC00' <= ch <= u'\uDFFF' and
                u'\uD800' <= r[-2] <= u'\uDBFF'
            ):
                # UTF-16 surrogate pair, replace with single non-BMP codepoint
                r[-2] = (r[-2] + ch).encode(
                    'utf-16', 'surrogatepass').decode('utf-16')
            else:
                append(ch)
            append(part[4:])
        except ValueError:
            append(u'%u')
            append(part)
    return u''.join(r)

该函数深受当前标准库实现的启发。

演示:

>>> print(unquote_unicode('Tan%u0131m'))
Tanım
>>> print(unquote_unicode('%u05D0%u05D9%u05DA%20%u05DE%u05DE%u05D9%u05E8%u05D9%u05DD%20%u05D0%u05EA%20%u05D4%u05D8%u05E7%u05E1%u05D8%20%u05D4%u05D6%u05D4'))
איך ממירים את הטקסט הזה
>>> print(unquote_unicode('%ud83c%udfd6'))  # surrogate pair

>>> print(unquote_unicode('%ufoobar%u666'))  # incomplete
%ufoobar%u666

该函数适用于 Python 2(在 2.4 - 2.7 上测试)和 Python 3(在 3.3 - 3.8 上测试)。

于 2019-03-07T14:41:41.143 回答