web-dev-qa-db-ja.com

Pythonでurlencoded unicode文字列の引用を解除するにはどうすればよいですか?

私は「Tanım」のようなユニコード文字列を持っていますが、これは何らかの形で「Tan%u0131m」としてエンコードされています。このエンコードされた文字列を元のユニコードに戻すにはどうすればよいですか? urllib.unquoteはUnicodeをサポートしていないようです。

48
hamdiakoguz

%uXXXXは 非標準のエンコーディングスキーム であり、実装はJavaScriptの土地で継続されているという事実にもかかわらず、w3cによって拒否されました。

より一般的な手法は、文字列をUTF-8エンコードし、%XXを使用して結果のバイトをエスケープすることです。このスキームは、urllib.unquoteでサポートされています。

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

残念ながら、%uXXXXをサポートするために本当にneedを必要とする場合は、おそらく独自のデコーダをロールする必要があります。そうでなければ、ユニコードを単にUTF-8エンコードしてから、結果のバイトをエスケープする方がはるかに望ましいでしょう。

より完全な例:

>>> u"Tanım"
u'Tan\u0131m'
>>> url = urllib.quote(u"Tanım".encode('utf8'))
>>> urllib.unquote(url).decode('utf8')
u'Tan\u0131m'
68
Aaron Maenpaa
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)
10
Markus Jarderot

これが絶対に必要な場合は、これを行います(「非標準」の叫びに本当に同意します)。

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
6
Ali Afshar

上記のバージョンには、文字列にASCIIエンコードとUnicodeエンコードの両方の文字が含まれている場合に時々フリークするバグがあります。ユニコードに加えて '\ xab'のような上位128の範囲の文字がある場合に特にそう思います。

例えば。 「%5B%AB%u03E1%BB%5D」はこのエラーの原因です。

あなたが最初にユニコードのものを最初にやったなら、問題は消えました:

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

非標準のエンコーディングスキーム を使用するURLがあり、標準化団体によって拒否されましたが、まだ一部のエンコーダーによって生成されています。 Python urllib.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でテスト済み)で動作します。

1
Martijn Pieters