Python - URLエンコード・デコード

quote()、quote_plus()、unquote()、unquote_plus()関数を使用して、URLエンコード と URLデコード を行う方法について説明します。URLエンコードとは、URLに使用できない文字をパーセントエンコードすることで、URLデコードとは、パーセントエンコードされた文字を元の文字に戻すことです。

quote() または quote_plus()関数 を使用すると URLエンコードunquote() または unquote_plus()関数 を使用すると URLデコード が可能です。

これらの関数を使用するには 標準モジュール urllib.parse のインポートが必要です。

URLエンコード

import urllib.parse

# quote関数を使用
result = urllib.parse.quote('日本語')
print(result)  # 結果 = %E6%97%A5%E6%9C%AC%E8%AA%9E

# quote_plus関数を使用
result = urllib.parse.quote_plus('日本語')
print(result)  # 結果 = %E6%97%A5%E6%9C%AC%E8%AA%9E

URLデコード

import urllib.parse

# unquote関数を使用
result = urllib.parse.unquote('%E6%97%A5%E6%9C%AC%E8%AA%9E')
print(result)  # 結果 = '日本語'

# unquote_plus関数を使用
result = urllib.parse.unquote_plus('%E6%97%A5%E6%9C%AC%E8%AA%9E')
print(result)  # 結果 = '日本語'

URLエンコーディング

URLエンコード を行うには、quote() または quote_plus() 関数を使用します。 quote()関数 は URL中 の パス をエンコードするのに適しており、quote_plus()関数 は クエリストリング(クエリ文字列) をエンコードするのに適しています。
quote()関数 と quote_plus()関数 のエンコード結果の違い
文字 quoteのエンコード結果 quote_plusのエンコード結果
スラッシュ(/) 変換されない %2F
スペース %20 +

パス と クエリストリング(クエリ文字列)

例えば次のような URL があるとき、 パス は /search、クエリストリング は URL+エンコード方法 です。この URL は Google の 検索結果ページ を表示します。/search は検索結果ページを指し、 q=URL+エンコード方法 は「URL エンコード方法」を検索することを指示しています。
https://www.google.com/search?q=URL+エンコード方法
URLパスの スラッシュ(/) は、ウェブサイト内のディレクトリ(またはフォルダ)階層を表すため、quote()関数 では変換されません。また、URLパス中では スペースを使用できないため、スペースの代わりに %20 に変換されます。

クエリストリング中の スラッシュ(/) は ディレクトリ階層 と誤認するため、quote_plus()関数 では %2F に変換します。また、クエリストリングではスペースの代わりに + または %20 を使用できます。そのため、quote_plus()関数 では スペース+ に変換します。

スペース+ ではなく、%20 に 変換したい場合は quote()関数 を使用し、次のように 引数 safe に 空文字列 を渡します。

import urllib.parse

# quote関数を使用し、+ を %20 にエンコード
result = urllib.parse.quote(' ', safe='')
print(result)  # 結果 = '%20'

# safe='' を指定することで、スラッシュ(/) も変換される。
result = urllib.parse.quote('/', safe='')
print(result)  # 結果 = '%2F'

通常、quote()関数 は スラッシュ(/) を変換しません。しかし、引数 safe に空文字列を指定すると、スラッシュ(/) も変換対象となります。これは、引数 safe に渡した文字が変換対象から除外されるためです。デフォルトでは、safe引数 の値は '/' です。

quote_plus()関数 も、quote()関数 と同じように、エンコーディング対象から除外する文字を指定できる safe引数 を持っています。ただし、この関数では safe のデフォルト値は 空文字列('') のため、スラッシュ(/)%2F に変換されます。

quote()関数 と quote_plus()関数 でエンコードされない文字

quote()関数 は URLのエンコードを目的としているため、引数 safe が 空文字列 の場合でも、次の文字は エンコードされません。
quote()関数 と quote_plus()関数 でエンコードされない文字
文字 読み / 説明 文字コード(10進) 文字コード(16進)
ハイフンマイナス(hyphen-minus) 45 0x2D
. ピリオド(period) 46 0x2E
0 ~ 9 数字 48 ~ 57 0x30 ~ 0x39
A ~ Z 大文字 アルファベット 65 ~ 90 0x41 ~ 0x5A
_ アンダースコア(underscore) 95 0x5F
a ~ z 小文字 アルファベット 97 ~ 122 0x61 ~ 0x7A
~ チルダ(tilde) 126 0x7E

quote()関数 と quote_plus()関数 の違いまとめ

quote()関数 と quote_plus()関数 の違いをまとめると、次のようになります。
quote()関数 と quote_plus()関数 の違い
quote関数 quote_plus関数
スペース の変換結果 %20 +
引数 safe の規定値 '/' ''

URLデコーディング

URLデコード を行うには、unquote() または unquote_plus() 関数を使用します。 unquote()関数 は URL中 の パス をデコードするのに適しており、unquote_plus()関数 は クエリストリング(クエリ文字列) をデコードするのに適しています。

unquote()関数 と unquote_plus()関数 の違いは、+ 記号の変換結果だけです。

unquote()関数 と unquote_plus()関数 のデコード結果の違い
文字 unquoteのデコード結果 unquote_plusのデコード結果
+ +(変換されない) スペース

エンコーディング(文字コード)の指定

quote() および quote_plus()関数、unquote() および unquote_plus()関数 では、引数 encoding で エンコーディング を指定できます。引数 encoding を省略した場合の規定値は、'UTF-8'です。エンコードとデコードのエンコーディングが一致していないと、エラーや文字化けを引き起こします。

次に、エンコーディング を指定して エンコード と デコード を行う例を示します。

# shift-jis で エンコード
result = urllib.parse.quote('日本語', encoding='shift-jis')
print(result)  # 結果 = %93%FA%96%7B%8C%EA

# UTF-8 で エンコード
result = urllib.parse.quote('日本語')
print(result)  # 結果 = %E6%97%A5%E6%9C%AC%E8%AA%9E
# shift-jis で デコード
result = urllib.parse.unquote('%93%FA%96%7B%8C%EA', encoding='shift-jis')
print(result)  # 結果 = 日本語

# UTF-8 で デコード
result = urllib.parse.unquote('%E6%97%A5%E6%9C%AC%E8%AA%9E')
print(result)  # 結果 = 日本語

エラー処理

エンコーディングできない場合の動作

quote() および quote_plus()関数 の errors引数を使用すると、エンコーディングが失敗したときの動作を変更できます。 デフォルトは 'strict' で、エンコーディング失敗時には UnicodeEncodeError エラーが発生します。

次に、エラーの発生例を示します。絵文字 shift-jis に変換できないため、UnicodeEncodeError エラーが発生します。

try:
    urllib.parse.quote('♫', encoding='shift-jis', errors='strict')
except UnicodeEncodeError as err:
    print(err)  # 'shift_jis' codec can't encode character '\u266b' in position 0: illegal multibyte sequence

quote および quote_plus()関数 の errors引数 に指定できるパラメーター

errors引数を指定することで、エンコード失敗時に強制的にエンコードが可能なケースがあります。しかし、エンコード後の結果は、相手方サーバーにとってほとんど意味のないものです。 基本的にはエンコードできない場合は処理を中断し、別の手段を行うことが第一選択となります。
  1. errors='strict' (errors を 略時した場合のデフォルト値)

    UnicodeEncodeError エラーが発生します。try - except で例外をキャッチしない場合、標準エラー(stderr)にエラーメッセージが出力され、プログラムは停止します。

  2. errors='ignore'

    変換できない文字を 空文字 に置き換えます。

  3. errors='replace'

    変換できない文字を 文字 '?' に置き換えます。

  4. errors='backslashreplace'

    変換できない文字を \uXXXX 形式のコードポイント文字列に置き換えます。

    例えば 絵文字 コードポイントは \u266b のため、%5Cu266b に変換されます。これを unquote()関数 でデコードすると、\u266b を返します。

  5. errors='backslashreplace'

    UNICODE に 変換できない文字を U+DC80 から U+DCFF の範囲に置き換えます。 unquote()関数 でデコードする際にも errors='backslashreplace' を指定することで、エンコード前の状態を復元します。ただしセキュリティを確保するため、一部の文字は変換されません。

  6. errors='xmlcharrefreplace'

    変換できない文字を HTMLやXML で使用される &#XXXX; 形式の文字参照(数値文字参照)に置き換えます。

    例えば 絵文字 コードポイントは \u266b のため、%26%239835%3B に変換されます。これを unquote()関数 でデコードすると、♫ を返します。

  7. errors='namereplace'

    変換できない文字を \N{Name property} 形式に置き換えます。Name property には、Unicode Character Database で定義されている名前が入ります。

    例えば 絵文字 の場合、%5CN%7BBEAMED%20EIGHTH%20NOTES%7D に変換されます。これを unquote()関数 でデコードすると、\N{BEAMED EIGHTH NOTES} を返します。BEAMED EIGHTH NOTES は、連符の8分音符 を意味します。

デコーディングできない場合の動作

unquote() および unquote_plus()関数 も errors引数を持ちます。デフォルトは 'replace' で、エンコード失敗時に、エンコードできない文字を REPLACEMENT CHARACTER(U+FFFD �) に置きかえます。これは UNINODE で特殊な意味を持つ文字で、不明な文字 や 認識できない文字 を表します。

次にエンコード失敗時の例を示します。文字列 '%99' は デコードできないため、\ufffd(REPLACEMENT CHARACTER) を返します。

result = urllib.parse.unquote('%99', errors='replace')
print(result)  # 結果 = '�'(\ufffd “REPLACEMENT CHARACTER”)

unquote および unquote_plus()関数 の errors引数 に指定できるパラメーター

quote()関数 と比較すると、'xmlcharrefreplace' と 'namereplace' が使用できません。また、'replace' を指定すると quote()関数 では '?' を返しますが、unquote()関数 では 置換文字(REPLACEMENT CHARACTER) を返します。'strict' を指定すると例外が発生しますが、quote()関数 が「UnicodeEncodeError」であるのに対し、unquote()関数 では「UnicodeDecodeError」です。
  1. errors='replace' (errors を 略時した場合のデフォルト値)

    変換できない文字を 置換文字(REPLACEMENT CHARACTER) に置き換えます。

  2. errors='strict'

    UnicodeDecodeError エラーが発生します。try - except で例外をキャッチしない場合、標準エラー(stderr)にエラーメッセージが出力され、プログラムは停止します。

  3. errors='ignore'

    変換できない文字を 空文字 に置き換えます。

  4. errors='backslashreplace'

    変換できない文字を \xXX 形式の文字列に置き換えます。

    たとえば %99 の場合、 \x99 を返します。

  5. errors='backslashreplace'

    quote時に quote(xxx, errors='backslashreplace') とすると、UNICODE に 変換できない文字を U+DC80 から U+DCFF の範囲に置き換えてエンコードされます。同様に unquote 時にも 'backslashreplace' を指定することで、エンコード前の状態を復元します。

バイト列(bytes型)の エンコード と デコード

Python では 文字列(str型) の代わりに、バイト列(bytes型) を使用しても 文字 を扱うことができます。 quote() および quote_plus()関数、unquote() および unquote_plus()関数 では、bytes型 もサポートしています。

バイト列 を エンコード または デコード するには、文字列(str型) の代わりに バイト列(bytes型) を渡すだけです。

次に バイト列 の エンコード と デコード を行う例を示します。

import urllib.parse

# str型 を bytes型 に変換
bytesValue = '日本語'.encode()
print(bytesValue)  # 結果 = b'\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e'

# bytes列を URLエンコード
result = urllib.parse.quote(bytesValue)
print(result)  # 結果 = %E6%97%A5%E6%9C%AC%E8%AA%9E

# URLエンコード結果(str型) を bytes型 に変換
bytesValue = result.encode()
print(bytesValue)  # 結果 = b'%E6%97%A5%E6%9C%AC%E8%AA%9E'

# bytes列を URLデコード
result = urllib.parse.unquote(bytesValue)
print(result)  # 結果 = 日本語

unquote_to_bytes()関数

unquote()関数 では 引数に バイト列を渡しても、デコード結果は 文字列型 で返されます。 一方、unquote_to_bytes()関数 を使用すると、引数の型に関わらず、デコード結果は バイト列 で返されます。

なお、quote_plus()関数 に相当する関数は存在しません。

参考資料

検証環境