基礎教學

URL 編碼完整指南:百分比編碼、RFC 3986 與保留字元

2026-03-13 閱讀時間約 8 分鐘 URL 編碼、RFC 3986、百分比編碼

什麼是 URL 編碼(百分比編碼)?

URL 編碼(URL Encoding),又稱百分比編碼(Percent-Encoding),是一種將字元轉換為 URL 安全格式的機制。由 IETF 在 RFC 3986 標準中定義,目的是讓所有字元都能在 URL 中安全傳輸,而不產生解析歧義。

URL 中只允許使用 ASCII 字元的一個子集。對於其他字元(如中文、日文、特殊符號),必須先將字元轉換為位元組序列,再以 %XX 的格式表示每個位元組,其中 XX 是兩位十六進位數字。

例如:中文「你好」的 UTF-8 位元組為 E4 BD A0 E5 A5 BD,URL 編碼結果為 %E4%BD%A0%E5%A5%BD。

RFC 3986 標準概述

RFC 3986 定義通用 URI 語法與保留字元;瀏覽器的 URL 解析與表單查詢序列化另由 WHATWG URL Standard 規範。實作時應依 URL 元件與接收端的解析方式選擇編碼規則。

根據 RFC 3986,URI 字元分為三類:

保留字元完整列表

RFC 3986 將保留字元分為兩組:

一般分隔符(gen-delims)

字元用途URL 編碼
:協議與主機分隔、port 分隔%3A
/路徑分隔符%2F
?query string 起始%3F
#fragment 起始%23
[IPv6 位址%5B
]IPv6 位址%5D
@使用者資訊分隔%40

子分隔符(sub-delims)

字元常見用途URL 編碼
!特殊標記%21
$路徑參數%24
&query 參數分隔%26
'字串標記%27
(群組標記%28
)群組標記%29
*萬用字元%2A
+加號;僅在表單編碼中代表空白%2B
,列表分隔%2C
;路徑參數%3B
=key=value 分隔%3D

不安全字元

字元原因URL 編碼
空格URL 不允許空格%20;表單編碼使用 +
"HTML 屬性引號衝突%22
<HTML 標籤衝突%3C
>HTML 標籤衝突%3E
{ }不安全字元%7B / %7D
|不安全字元%7C
\路徑衝突%5C
^不安全字元%5E
`不安全字元%60

中文字元的 URL 編碼原理

中文等非 ASCII 字元的 URL 編碼分為兩個步驟:

  1. UTF-8 編碼:將字元轉換為 UTF-8 位元組序列(每個中文字通常佔 3 個位元組)。
  2. 百分比表示:每個位元組以 %XX 格式表示。

「中」= UTF-8: E4 B8 AD → URL 編碼: %E4%B8%AD
「文」= UTF-8: E6 96 87 → URL 編碼: %E6%96%87

各程式語言實作範例

JavaScript

// encodeURIComponent - 編碼單一元件;保留 ! ' ( ) *
encodeURIComponent('你好 world!')
// → "%E4%BD%A0%E5%A5%BD%20world!"

// encodeURI - 保留 URL 結構字元(用於完整 URL)
encodeURI('https://example.com/搜尋?q=你好')
// → "https://example.com/%E6%90%9C%E5%B0%8B?q=%E4%BD%A0%E5%A5%BD"

// 解碼
decodeURIComponent('%E4%BD%A0%E5%A5%BD')
// → "你好"

空白與真正的加號

application/x-www-form-urlencoded 會把空白寫成 +、真正的加號寫成 %2B,URLSearchParams 也採用這個規則。相對地,decodeURIComponent('a+b') 仍會回傳 a+b,不會自行把加號換成空白。

new URLSearchParams({q: 'a b+c'}).toString()
// → "q=a+b%2Bc"

MDN 列出 encodeURIComponent 保留的字元,包括 !'()*。它不會把 RFC 3986 非保留集合以外的所有字元都轉成百分比編碼。

Python

from urllib.parse import quote, unquote, urlencode

# 編碼單一值(safe='' 代表連 / 也要編碼)
quote('你好 world!', safe='')
# → '%E4%BD%A0%E5%A5%BD%20world%21'

# 解碼
unquote('%E4%BD%A0%E5%A5%BD')
# → '你好'

# 編碼整個 query string
params = {'name': '張三', 'city': '台北市'}
urlencode(params)
# → 'name=%E5%BC%B5%E4%B8%89&city=%E5%8F%B0%E5%8C%97%E5%B8%82'

PHP

<?php
// rawurlencode - 遵循 RFC 3986(推薦)
rawurlencode('你好 world!');
// → '%E4%BD%A0%E5%A5%BD%20world%21'

// urlencode - 將空格編碼為 + 而非 %20(用於 HTML form 資料)
urlencode('你好 world!');
// → '%E4%BD%A0%E5%A5%BD+world%21'

// 解碼
rawurldecode('%E4%BD%A0%E5%A5%BD');
// → '你好'
?>

Java

import java.net.URLEncoder;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

// 編碼
String encoded = URLEncoder.encode("你好 world!", StandardCharsets.UTF_8);
// → "%E4%BD%A0%E5%A5%BD+world%21"(注意:Java 將空格編碼為 +)

// 解碼
String decoded = URLDecoder.decode("%E4%BD%A0%E5%A5%BD", StandardCharsets.UTF_8);
// → "你好"

Go

import "net/url"

// 編碼
encoded := url.QueryEscape("你好 world!")
// → "%E4%BD%A0%E5%A5%BD+world%21"

// 路徑編碼(空格編碼為 %20)
pathEncoded := url.PathEscape("你好 world!")
// → "%E4%BD%A0%E5%A5%BD%20world%21"

// 解碼
decoded, _ := url.QueryUnescape("%E4%BD%A0%E5%A5%BD")
// → "你好"

常見錯誤與正確做法

錯誤 1:雙重編碼
將已編碼的字串再次編碼,導致 %25 出現(%25 是 % 的編碼)。
錯誤:encodeURIComponent('%E4%BD%A0') → %25E4%25BD%25A0
正確:只對原始未編碼的字串執行一次編碼。

錯誤 2:使用過時的 escape() 函式
escape() 不會正確處理非 ASCII 字元(如中文),且已被標準廢棄。
請改用 encodeURIComponent() 或 encodeURI()。

錯誤 3:搞混 encodeURI 和 encodeURIComponent
在 query string 中使用 encodeURI() 會導致 &、=、? 等分隔符未被編碼,造成參數解析錯誤。
query string 的參數值請務必使用 encodeURIComponent()。

立即試用 URL 編碼工具

使用我們的免費工具即時驗證您的 URL 編碼結果,支援中文與多語言。

前往工具頁面