URLエンコードで文字化けが直らない原因|二重エンコードと文字コードの見分け方
URLエンコードされた文字列をデコードしても文字化けが直らないとき、原因はほぼ次の3パターンのどれかです。「二重エンコード」「文字コードの不一致(Shift_JISなど)」「encodeURIとencodeURIComponentの取り違え」。それぞれの見分け方と直し方を、実際のデコード結果を見ながら整理します。
原因1:二重にエンコードされている
デコードした結果に % が残っているなら、二重エンコードを疑ってください。
たとえば「あ」をエンコードすると %E3%81%82 になりますが、これをもう一度エンコードすると %25E3%2581%2582 になります。% という記号自体が %25 に変換されるため、%25 が並んでいたら「もう一段エンコードされている」というサインです。
なぜ二重エンコードが起きるのか:
- CMSやフォームがURLを受け取るたびに自動でエンコードする仕様になっている
- リンクを生成する処理と、そのリンクをクリックしたブラウザの両方がエンコードしている
- Googleアナリティクスなどの計測パラメータを、既にエンコード済みの状態でさらにURL生成関数に渡している
直し方: 1回デコードして %25 が残っていたら、もう一度デコードします。URLエンコード / デコードツールなら「デコード」を選んだ状態で結果をコピーし、もう一度貼り付ければ2段階目のデコードができます。3重以上になっているケースは稀ですが、%2525 のように %25 が繰り返されていないかも確認してください。
原因2:文字コードがUTF-8ではない
デコードした結果が 縺ゅ▲ のような意味不明な文字列になる場合、二重エンコードではなく文字コードの不一致が原因です。
現在のWebはほぼUTF-8で統一されていますが、2000年代以前に作られた古いシステムや、一部の業務システム・メールフォームでは今もShift_JISやEUC-JPでURLを生成していることがあります。この場合、同じ「あ」でも次のようにバイト列がまったく違います。
| 文字コード | 「あ」のエンコード結果 |
|---|---|
| UTF-8 | %E3%81%82 |
| Shift_JIS | %82%A0 |
| EUC-JP | %A4%A2 |
ブラウザの標準デコード関数(decodeURIComponent)はUTF-8前提で動くため、Shift_JISやEUC-JPでエンコードされたバイト列をそのまま渡すと、デコードできてしまう場合とエラーになる場合の両方が起こり、意味の通らない文字列や URIError: malformed URI sequence になります。これはこのサイトのURLエンコードツールを含め、ブラウザ標準の関数を使うツール全般に共通する制約です。
見分け方: デコード結果が % を含まないのに文字化けしている場合は、この原因を疑ってください。対処には、元のバイト列がどの文字コードで書かれたかを特定したうえで、その文字コードに対応した変換が必要になります。UTF-8以外の文字コードのデコードは、汎用のURLエンコードツールでは直せないケースがある、という点は覚えておく価値があります。
原因3:encodeURIとencodeURIComponentを取り違えている
自分でURLを組み立てるコードを書いている場合、この取り違えが最も多い原因です。両者は「どこまでの記号をエンコードするか」が違います。
| 関数 | 用途 | & = ? / # の扱い |
|---|---|---|
encodeURI | URL全体をエンコードする | エンコードしない(そのまま残す) |
encodeURIComponent | クエリパラメータの値をエンコードする | エンコードする |
たとえば検索キーワードに & や = が含まれる場合、encodeURI で組み立てると、その記号がクエリパラメータの区切り文字と誤認識され、サーバー側でパラメータが分割されてしまいます。
const keyword = "A&B=C";
encodeURI(`https://example.com/search?q=${keyword}`);
// → https://example.com/search?q=A&B=C
// 「&」がエンコードされず、q と B という2つのパラメータに分かれてしまう
encodeURIComponent(keyword);
// → A%26B%3DC
// クエリパラメータの値として安全に埋め込める
判断基準はシンプルです。 URL全体(プロトコルやスラッシュを含む文字列)を扱うなら encodeURI、クエリパラメータの値1つだけを扱うなら encodeURIComponent を使います。実務でエンコードが必要になる場面のほとんどは後者です。
実際に直す手順
- まずそのままデコードしてみる
- 結果に
%25などの%が残っていたら、もう一度デコードする(原因1) %を含まないのに意味の通らない文字列になっていたら、文字コードの不一致を疑う(原因2)&や=を境に意図しないパラメータ分割が起きているなら、エンコード時の関数選択を疑う(原因3)
URLエンコード / デコードツールには、入力したURLを構成要素(プロトコル・ホスト・パス・クエリパラメータ)に分解して表示する機能があります。パラメータがいくつに分かれているかを見れば、原因3の「意図しない分割」が起きていないかをその場で確認できます。
見落としがちな注意点:+ 記号の扱いがツールの表示箇所によって違う
もう一つ、実務でつまずきやすいのが + の扱いです。URLエンコードの規格上、半角スペースの表現方法は2種類あります。
application/x-www-form-urlencoded(フォーム送信のクエリ文字列)では、スペースは+で表される- パーセントエンコーディング全般では、スペースは
%20で表される
decodeURIComponent はこの2つのうち後者にしか対応していません。つまり decodeURIComponent("a+b") を実行しても、結果は a+b のままで、a b(スペース入り)にはなりません。+ を %2B としてエンコードしていない限り、decodeURIComponent は + をスペースとして扱わないという仕様です。
一方、ブラウザ標準の URLSearchParams(new URL(...).searchParams など、クエリパラメータをキーと値に分解する機能)は、フォーム送信の仕様に従って + を自動的にスペースへ変換します。同じ「+」が、使う関数によって別の意味に解釈されるわけです。
これはこのサイトのURLエンコードツールでも同じ構造になっています。テキスト全体を「デコード」した上部の結果では + はそのまま残りますが、URLを貼り付けたときに下部へ表示される「クエリパラメータ」欄では、内部でURLSearchParamsを使って分解しているため + がスペースとして表示されます。同じ入力なのに上下で見え方が違っても、ツールの不具合ではなく、この仕様差によるものです。フォーム経由で発行されたURL(検索フォームの送信結果など)を扱うときは、この違いを念頭に置いてください。
まとめ
- デコード結果に
%25が残っていたら二重エンコード。もう一度デコードする %を含まないのに文字化けしているなら、UTF-8以外の文字コード(Shift_JISなど)を疑う。ブラウザ標準のデコード関数はUTF-8前提のため直せないケースがある&や=を境にパラメータが分割されるなら、encodeURIとencodeURIComponentの取り違えを疑う+はエンコード方式によってスペースを表す場合と、そのままの文字として扱われる場合がある。使っている関数がどちらの仕様に従っているかを確認する
文字化け全般の原因についてはメールが文字化けする原因と直し方でも扱っているので、あわせて参考にしてください。