URLエンコードとは何か

URLエンコード(パーセントエンコーディング)とは、URLに含められない文字を %XX の形式に変換する仕組みです。XX の部分にはその文字のバイト値が16進数で入ります。

たとえば、日本語の「東京」をURLに含めたい場合、次のように変換されます。

元の文字: 東京
URLエンコード後: %E6%9D%B1%E4%BA%AC

ブラウザのアドレスバーでは日本語がそのまま表示されることもありますが、実際にサーバーに送信されるHTTPリクエストでは、上記のようにエンコードされた文字列が使われています。

なぜURLエンコードが必要なのか

URLには「使える文字」と「使えない文字」があります。RFC 3986で定義されたURL構文では、安全に使えるのは以下の文字だけです。

URLにそのまま使える文字(非予約文字)

英字: A-Z a-z
数字: 0-9
記号: - _ . ~

URLで特別な意味を持つ文字(予約文字)

: / ? # [ ] @ ! $ & ' ( ) * + , ; =

これらの予約文字は、URLの構造を区切るために使われます。たとえば ? はクエリパラメータの開始、& はパラメータの区切り、# はフラグメントの開始を意味します。

エンコードが必要な場面

予約文字を「区切り」ではなく「データの一部」として使いたいときや、日本語のようなASCII範囲外の文字をURLに含めたいとき、URLエンコードが必要になります。

# 検索クエリに「C++」を含める場合
# + はそのまま使うとスペースと解釈される
https://example.com/search?q=C%2B%2B

# 日本語の検索キーワード
https://example.com/search?q=%E6%9D%B1%E4%BA%AC

もしエンコードしなければ、URLの構文が壊れたり、サーバー側で意図しない解釈がされたりします。

パーセントエンコーディングの仕組み

URLエンコードの変換プロセスを、ステップごとに見てみましょう。

ステップ1: UTF-8でバイト列に変換

まず、エンコードしたい文字をUTF-8のバイト列に変換します。

「あ」のUTF-8バイト列:
あ → 0xE3 0x81 0x82(3バイト)

ステップ2: 各バイトを %XX 形式に変換

各バイトの値を16進数で表し、先頭に % を付けます。

0xE3 → %E3
0x81 → %81
0x82 → %82

結果: 「あ」 → %E3%81%82

日本語文字のエンコード例

文字UTF-8バイト列URLエンコード
E3 81 82%E3%81%82
E3 81 84%E3%81%84
E6 9D B1%E6%9D%B1
E4 BA AC%E4%BA%AC
スペース20%20(または +)
&26%26

スペースの特殊な扱い

スペース(半角空白)のエンコードには2つの方式があります。

方式変換結果使われる場面
パーセントエンコーディング%20URL全般(パス部分)
フォームエンコーディング+HTMLフォームのクエリ文字列
# パス部分では %20
https://example.com/my%20document.pdf

# クエリ文字列では + も使われる
https://example.com/search?q=hello+world

多くのWebサーバーは両方を正しく処理しますが、厳密にはRFC 3986では %20 が正式な表記です。HTMLフォームの application/x-www-form-urlencoded 形式では + が使われる慣習があります。

日本語とURLエンコード

なぜ日本語URLが増えたのか

かつてはURLに日本語を使うことはほとんどありませんでした。しかし、IRIの標準化やブラウザの対応により、日本語ドメインや日本語パスが普及しました。

# ブラウザのアドレスバーに表示される形
https://ja.wikipedia.org/wiki/東京都

# 実際にサーバーに送信される形
https://ja.wikipedia.org/wiki/%E6%9D%B1%E4%BA%AC%E9%83%BD

ブラウザは表示上は日本語で見せていますが、内部的にはURLエンコードされた文字列で通信しています。

文字コードの問題

URLエンコードでは、どの文字コードでバイト列に変換するかが重要です。現在はUTF-8が標準ですが、古いシステムではShift_JISやEUC-JPでエンコードされることがあります。

# 「東京」をUTF-8でエンコード
%E6%9D%B1%E4%BA%AC

# 「東京」をShift_JISでエンコード
%93%8C%8B%9E

同じ日本語でも文字コードが違えばまったく異なるエンコード結果になります。エンコードとデコードで文字コードが一致しないと文字化けの原因になります。

現代のWebではUTF-8を使うのが標準であり、特別な理由がない限りUTF-8でエンコードしてください。

エンコード・デコードの実装方法

JavaScriptの場合

JavaScriptには3つのエンコード関数があります。用途に応じて使い分けが必要です。

const text = '東京 タワー&スカイツリー';

// encodeURI — URL全体をエンコード(予約文字はそのまま)
console.log(encodeURI(text));
// → %E6%9D%B1%E4%BA%AC%20%E3%82%BF%E3%83%AF%E3%83%BC&%E3%82%B9%E3%82%AB%E3%82%A4%E3%83%84%E3%83%AA%E3%83%BC
// ※ & はエンコードされない

// encodeURIComponent — URLの一部をエンコード(予約文字もエンコード)
console.log(encodeURIComponent(text));
// → %E6%9D%B1%E4%BA%AC%20%E3%82%BF%E3%83%AF%E3%83%BC%26%E3%82%B9%E3%82%AB%E3%82%A4%E3%83%84%E3%83%AA%E3%83%BC
// ※ & も %26 にエンコードされる

// デコード
console.log(decodeURIComponent('%E6%9D%B1%E4%BA%AC'));
// → 東京

使い分けの原則:

関数用途
encodeURI()URL全体をエンコードしたいときencodeURI('https://example.com/東京')
encodeURIComponent()URLの一部(クエリパラメータ等)をエンコードしたいとき?q= + encodeURIComponent('東京&大阪')

注意: encodeURIComponent():, /, ?, # などもエンコードするため、URL全体に使うとURLが壊れます。クエリパラメータの値部分にだけ使ってください。

// 正しい使い方
const query = encodeURIComponent('東京&大阪');
const url = `https://example.com/search?q=${query}`;
// → https://example.com/search?q=%E6%9D%B1%E4%BA%AC%26%E5%A4%A7%E9%98%AA

// 間違った使い方(URL全体にencodeURIComponentを使う)
const broken = encodeURIComponent('https://example.com/search?q=東京');
// → https%3A%2F%2Fexample.com%2Fsearch%3Fq%3D%E6%9D%B1%E4%BA%AC(URLとして機能しない)

Pythonの場合

from urllib.parse import quote, unquote, urlencode

# 文字列をURLエンコード
encoded = quote('東京タワー')
print(encoded)  # %E6%9D%B1%E4%BA%AC%E3%82%BF%E3%83%AF%E3%83%BC

# デコード
decoded = unquote('%E6%9D%B1%E4%BA%AC')
print(decoded)  # 東京

# クエリパラメータをまとめてエンコード
params = urlencode({'city': '東京', 'area': '渋谷区'})
print(params)  # city=%E6%9D%B1%E4%BA%AC&area=%E6%B8%8B%E8%B0%B7%E5%8C%BA

PHPの場合

// エンコード(スペースを %20 に変換)
echo rawurlencode('東京 タワー');
// → %E6%9D%B1%E4%BA%AC%20%E3%82%BF%E3%83%AF%E3%83%BC

// エンコード(スペースを + に変換 — フォーム形式)
echo urlencode('東京 タワー');
// → %E6%9D%B1%E4%BA%AC+%E3%82%BF%E3%83%AF%E3%83%BC

// デコード
echo urldecode('%E6%9D%B1%E4%BA%AC');
// → 東京

コマンドライン(curl, Python)

# curlでURLエンコード付きリクエスト(--data-urlencodeを使用)
curl -G "https://example.com/search" --data-urlencode "q=東京タワー"

# Pythonのワンライナーでエンコード
python3 -c "from urllib.parse import quote; print(quote('東京タワー'))"

# Pythonのワンライナーでデコード
python3 -c "from urllib.parse import unquote; print(unquote('%E6%9D%B1%E4%BA%AC'))"

オンラインツールで変換

コードを書かずに手軽にURLエンコード・デコードしたい場合は、URLエンコード・デコードツール が便利です。テキストを貼り付けるだけで即座に変換でき、UTF-8はもちろんShift_JISなど複数の文字コードにも対応しています。

URLエンコードが関わる具体的な場面

Google検索のURL

Googleで「東京 天気」と検索すると、URLは次のようになります。

https://www.google.com/search?q=%E6%9D%B1%E4%BA%AC+%E5%A4%A9%E6%B0%97

%E6%9D%B1%E4%BA%AC が「東京」、+ がスペース、%E5%A4%A9%E6%B0%97 が「天気」に対応しています。

HTMLフォームの送信

HTMLフォームを GET メソッドで送信すると、入力内容がURLエンコードされてクエリ文字列に含まれます。

<form method="GET" action="/search">
  <input name="q" value="東京タワー">
  <button type="submit">検索</button>
</form>
<!-- 送信先: /search?q=%E6%9D%B1%E4%BA%AC%E3%82%BF%E3%83%AF%E3%83%BC -->

POST メソッドで application/x-www-form-urlencoded 形式を使う場合も、リクエストボディ内でURLエンコードが使われます。

REST APIのパラメータ

API呼び出しでクエリパラメータに日本語や特殊文字を含める場合、適切なエンコードが不可欠です。

// APIキーに特殊文字が含まれる場合
const apiKey = 'abc+def/ghi=';
const url = `https://api.example.com/data?key=${encodeURIComponent(apiKey)}`;
// → https://api.example.com/data?key=abc%2Bdef%2Fghi%3D

SNSシェアリンク

TwitterやFacebookのシェアリンクにURLを含める場合、二重エンコードが必要になることがあります。

const shareUrl = 'https://example.com/記事タイトル';
const twitterLink = `https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}`;

よくあるトラブルと対処法

二重エンコードの問題

すでにエンコード済みの文字列をもう一度エンコードしてしまうトラブルは非常に多いです。

元の文字: 東京
1回目: %E6%9D%B1%E4%BA%AC(正しい)
2回目: %25E6%259D%25B1%25E4%25BA%25AC(% が %25 に変換されてしまう)

対処法: エンコード処理が何回呼ばれているかを確認しましょう。フレームワークが自動エンコードしている場合に、手動でもエンコードすると二重になります。

文字化けが発生する

エンコード時とデコード時で文字コードが一致しないと文字化けが起きます。

# UTF-8でエンコードした文字列をShift_JISでデコードすると文字化け
UTF-8: %E6%9D%B1%E4%BA%AC → 「東京」
Shift_JIS: %93%8C%8B%9E → 「東京」

対処法: 現代のWebではUTF-8が標準です。古いシステムとの連携時に文字化けが起きる場合は、文字コードの指定を確認してください。

スペースが + になる問題

HTMLフォーム由来のURLでスペースが + で表されることがあります。decodeURIComponent()+ をデコードしないため、手動で変換が必要です。

// + をスペースに戻してからデコード
function decodeFormData(str) {
  return decodeURIComponent(str.replace(/\+/g, '%20'));
}

decodeFormData('hello+world'); // "hello world"

長すぎるURLの問題

URLエンコードにより文字列が長くなるため、大量の日本語をクエリパラメータに含めるとURL長の制限に引っかかることがあります。

  • ブラウザのURL長制限: 一般的に2,000〜8,000文字程度
  • サーバーのURL長制限: Apache/Nginxのデフォルトは8,192バイト

対処法: 大量のデータを送る場合はGETではなくPOSTメソッドを使うか、検索パラメータをIDに変換するなどの工夫が必要です。

URLエンコードとセキュリティ

XSS(クロスサイトスクリプティング)対策

URLパラメータに悪意のあるスクリプトが含まれる場合があります。URLデコード後の値をHTMLにそのまま出力するのは危険です。

# 攻撃者が仕込む可能性のあるURL
https://example.com/search?q=%3Cscript%3Ealert('XSS')%3C/script%3E
# デコードすると: <script>alert('XSS')</script>

対処法: URLデコードした値をHTMLに出力する際は、必ずHTMLエスケープを行ってください。

SQLインジェクション

URLデコードした値をSQLクエリに直接埋め込むのも危険です。プリペアドステートメントを使いましょう。

オープンリダイレクト

URLパラメータにリダイレクト先URLを含めるとき、エンコードされた悪意あるURLに気づきにくくなります。リダイレクト先のバリデーションを必ず行ってください。

まとめ

URLエンコード(パーセントエンコーディング)は、Web開発で避けて通れない基本的な仕組みです。

押さえておくべきポイント:

  • URLに使える文字は限られており、日本語や特殊文字は %XX 形式にエンコードする必要がある
  • 現在の標準はUTF-8。特別な理由がない限りUTF-8でエンコードする
  • JavaScriptでは**encodeURIComponent()をクエリパラメータの値に**、encodeURI()をURL全体に使う
  • 二重エンコードに注意。フレームワークの自動エンコードと手動エンコードが重複していないか確認する
  • スペースは %20+ の2通りの表現がある。用途に応じて使い分ける
  • URLデコード後の値を出力する際は、XSS対策としてHTMLエスケープを忘れない

実際のURLエンコード・デコードを試したい方は、URLエンコード・デコードツール をご活用ください。テキストを入力するだけでリアルタイムに変換結果を確認でき、ブラウザ上で完結するため安全にお使いいただけます。

この記事の内容はAssistyURLエンコード・デコードで実際にお試しいただけます。