Pythonでurlencoded unicode文字列の引用を解除するにはどうすればよいですか?
-
08-07-2019 - |
質問
"Tanım"のようなUnicode文字列があります。 「Tan%u0131m」としてエンコードされます。どういうわけか。このエンコードされた文字列を元のユニコードに戻すにはどうすればよいですか? urllib.unquoteはUnicodeをサポートしていないようです。
解決
%uXXXXは非標準のエンコードスキームであり、実装がJavaScriptの土地で生き続けているという事実にもかかわらず、w3cによって拒否されました。
より一般的な手法は、文字列をUTF-8でエンコードし、%XXを使用して結果のバイトをエスケープすることです。このスキームはurllib.unquoteでサポートされています:
>>> urllib2.unquote("%0a")
'\n'
残念ながら、本当に%uXXXXをサポートする必要がある場合は、おそらく独自のデコーダを展開する必要があります。そうでない場合は、ユニコードをUTF-8でエンコードしてから、結果のバイトを%エスケープする方がはるかに望ましいでしょう。
より完全な例:
>>> u"Tanım"
u'Tan\u0131m'
>>> url = urllib.quote(u"Tanım".encode('utf8'))
>>> urllib.unquote(url).decode('utf8')
u'Tan\u0131m'
他のヒント
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)
これが絶対に必要な場合にこれを行います(「非標準」の叫びに本当に同意します):
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
上記のバージョンにはバグがあり、文字列にASCIIエンコード文字とUnicodeエンコード文字の両方が含まれている場合に時々フリークします。ユニコードに加えて、 '\ xab'のような上位128の範囲の文字がある場合に特にそう思います。
eg。 "%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
非標準のエンコードスキームを使用するURLがあります、標準化団体によって拒否されましたが、まだ一部のエンコーダーによって作成されています。 Pythonの urllib.parse.unquote()
関数はこれらを処理できません。
幸運なことに、独自のデコーダを作成することはそれほど難しくありません。 %uhhhh
エントリはここでは UTF-16 コードポイントとなるため、サロゲートペアを考慮します。さらに混乱を招くために、%hh
コードポイントが混在しているのを見ました。
このことを念頭に置いて、Python 3の str
オブジェクトを渡すことを条件に、Python 2と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でテスト済み)で動作します。