網址為什麼放不下中文、空格和表情符號
URL(網址)規範(RFC 3986)只允許 ASCII 字元集裡的英數字與特定符號(-_.~ 等)。中文、空格、表情符號、保留字(? # & 等)放進網址時,必須先轉成百分比編碼(percent-encoding)——每個位元組轉成 %XX 兩位 16 進位,瀏覽器與伺服器看到才能正確解析。
例如「楓葉」用 UTF-8 編成 6 個位元組,再轉百分比編碼 → %E6%A5%93%E8%91%89。不編碼直接放進 URL,有些舊伺服器會回 400 Bad Request,或解析成亂碼。
編碼字元對照:
- 空格:
%20(標準),有時也用+(form 編碼) - 中文字:UTF-8 通常 3 個位元組 / 字,例如「楓」→
%E6%A5%93 - Emoji:UTF-8 可達 4 個位元組,例如「🍁」→
%F0%9F%8D%81 - 常見符號:
&→%26、=→%3D、?→%3F、#→%23
要特地編過再貼,而不是貼了原字碰運氣,是因為出錯時多半沒有錯誤訊息。網址中間有空格, LINE、Discord、Markdown 編輯器或 PDF 閱讀器會把連結切在空格前,對方拿到的是半條網址,而且沒有任何提示。更安靜的是值裡面那個沒編過的 &:伺服器直接把它當成參數分隔字元,於是「珍珠 & 奶茶」這個搜尋,到伺服器變成搜尋「珍珠 」外加一個叫「奶茶」的神祕參數。沒有人報錯,只是資料錯了。沒編過的 # 一樣:它後面的東西根本不會送到伺服器,因為 fragment 是瀏覽器自己留著用的。
模式與編碼範圍怎麼選
常見使用情境:
- 中文查詢參數:Google 搜尋「楓葉工具箱」,網址列實際是
q=%E6%A5%93%E8%91%89... - 分享 PTT / 巴哈文章:含中文標題的網址要先 encode 才能貼到 LINE / Discord
- API query string:把使用者輸入塞進 query 前必須 encode,避免
&被誤判為參數分隔 - 反向 decode log:從 server log 看到一串
%XX,貼回來 decode 看原始查詢 - 生成 mailto: 連結:含中文主旨 / 內文時必須 encode
工具上方有兩排按鈕:模式決定進去還是出來,編碼範圍決定這串字有多少結構可以留著。上面五個情境裡,只有第四個從 log 撈出來的那種要按「URL 解碼」,其餘都是「URL 編碼」。
JavaScript 有兩個 URL 編碼函式,編碼範圍不同:
- encodeURI:保留 URL 結構字元(
:/?&=#)。適合對「完整網址」編碼,不會破壞結構 - encodeURIComponent:把所有結構字元也 encode,適合對「單一參數值」編碼。例如把 URL 嵌進另一個 URL 的參數時用
本工具預設用 encodeURI,保留結構。如果你的字串會成為「query string 的某個值」,把上方的 「編碼範圍」切到參數值,就會改用 encodeURIComponent;解碼同理,兩個範圍分別對應 decodeURI 與 decodeURIComponent。
不確定該用哪個範圍時,問自己一句:這串字應該有權力改變網址的結構嗎?使用者打進來的字永遠沒有,所以選參數值。另外,如果你是在程式裡把好幾組 key 與 value 湊成 query string,那連這個選擇都不必做,交給 URLSearchParams 或你那個語言的對應物件,讓它負責串接與跳脫,出錯的機會最小。
怎麼把連結編好再傳出去
- 模式留在URL 編碼,編碼範圍留在整個網址(encodeURI)。
- 把完整網址貼進輸入框蓋掉範例,連
https://一起貼,結構才看得出來。 - 看結果:空格應該都變成
%20,中文與 emoji 都變成一串%XX;斜線、問號、參數之間的&應該原封不動。這就是「它還是一條連結」的判斷依據。 - 按複製,傳出去以前先自己貼進瀏覽器網址列開一次。能開的連結才算過關。
有一種情況要換做法:你要塞進去的值本身就是一個網址,例如 redirect_uri 或 next= 後面那一段。這時候把整條外層網址編一次是不夠的,內層網址的斜線與問號會被留著,伺服器就把它們當成外層網址的一部分。請先把內層網址單獨貼進來,編碼範圍切到參數值(encodeURIComponent)編一次,再把結果貼回外層連結裡。
怎麼把 log 裡那串 %XX 讀回人話
- 模式切到URL 解碼。
- 貼上編碼過的字串。路徑跟 query 一起整段複製,不要只挑一截,切在一組
%XX中間會直接解不開。 - 如果
%3D、%26、%2F原樣留著沒被還原,那是decodeURI在保護網址結構。把編碼範圍切到參數值(decodeURIComponent)就會連它們一起解,這也正是你在處理「單一參數」而不是整條網址時要的。 - 結果裡還看得到
%25,代表這個值被編了兩次。把輸出複製回輸入框,再解一次。 - 出現
URI malformed,去找那個本來就想當百分比符號用的%,或是行尾被截斷的半組跳脫序列。 - 解得開但讀起來是亂碼,代表那些位元組不是 UTF-8。台灣舊系統的網址多半是 Big5,把「解碼字元集」切到Big5 再看一次,原因看本頁最後一段。
- 網址很長、參數很多時,模式切到拆解網址:協定、主機、路徑與錨點分開列,每個參數一列、逐項解碼,可以只複製其中一個值。
解碼也是從 access log 回答「使用者到底搜了什麼」最快的方法,順便可以確認客戶端在送出前有沒有把 token 或簽章編過。解出來如果是 JSON,交給「JSON 格式化」排版;如果是 Base64,交給「Base64 編碼解碼」。
結果貼到哪裡、要不要再處理一次
複製鈕會把結果放進剪貼簿。接下來貼去哪裡,決定還要不要多做一步:
- 貼進 HTML:編好的網址裡參數之間還是
&,寫在href屬性裡要改成&。那是 HTML 跳脫,跟百分比編碼是兩回事。 - 貼進終端機:整條網址用單引號包起來。沒包的
&會讓指令跑到背景去,?與*可能被 shell 展開,#後面會被當註解。 - 貼進試算表:長長的百分比字串會被當文字,但 Google 試算表與 Excel 可能自動把它變成超連結。之後要拿來比對的話,先把該欄設成純文字格式。
- 貼進聊天室或文件:LINE、Discord、Slack、Notion 與多數 Markdown 編輯器都是自動連結到第一個空格為止,這正是編過的網址傳得過去、原字傳不過去的原因。
同一件事在各語言怎麼寫
每個語言都有兩三個編碼函式,分法幾乎都一樣:一個照 RFC 3986 走、空格編成 %20;另一個照舊的 application/x-www-form-urlencoded 規則走、空格編成 +。JavaScript:
encodeURI('https://www.toolmaple.com/?q=楓葉 工具箱&page=1')
// https://www.toolmaple.com/?q=%E6%A5%93%E8%91%89%20%E5%B7%A5%E5%85%B7%E7%AE%B1&page=1
encodeURIComponent('珍珠 & 奶茶')
// %E7%8F%8D%E7%8F%A0%20%26%20%E5%A5%B6%E8%8C%B6
new URLSearchParams({ q: '珍珠 & 奶茶', page: '1' }).toString()
// q=%E7%8F%8D%E7%8F%A0+%26+%E5%A5%B6%E8%8C%B6&page=1注意最後一行:URLSearchParams 把空格編成 +,因為它實作的是 form 編碼而不是單純的百分比編碼。用在 query string 是對的,用在路徑片段就是錯的。
# Python
from urllib.parse import quote, quote_plus, unquote
quote('a b/c') # 'a%20b/c' 斜線預設是安全字元
quote('a b/c', safe='') # 'a%20b%2Fc'
quote_plus('a b/c') # 'a+b%2Fc'
unquote('%E6%A5%93') # '楓'- PHP:
rawurlencode()照 RFC 3986 走、空格給%20;urlencode()是 form 版、空格給+。 - Java:
java.net.URLEncoder.encode()雖然叫這個名字,做的是 form 編碼,空格會變成+,拿來編路徑片段是錯的;組網址請改用java.net.URI。 - Go:
url.QueryEscape()給+,url.PathEscape()給%20。 - curl:
curl -G https://example.com/s --data-urlencode "q=珍珠 & 奶茶"會幫你把 query string 組好,比在 shell 裡自己跳脫安全。
解碼也是同一組分法,而這裡正是 bug 藏身的地方:JavaScript 的 decodeURIComponent 與 Python 的 unquote 會把 %20 還原成空格,卻把 + 原樣留成一個加號。值是從 HTML 表單送上來的、你卻用 RFC 3986 那個解碼器去解,每個空格都會變成加號。表單來的東西請用 URLSearchParams 或 unquote_plus。在本頁,解碼或拆解網址時勾「把 + 當空格」就是同一套規則。
限制、字元集,與這頁不負責的事
離開瀏覽器的資料:沒有。轉換全程用瀏覽器內建的 encodeURI、encodeURIComponent、decodeURI 與 decodeURIComponent(Big5 用內建的 TextDecoder)在本地完成,輸入不會上傳、不會儲存、不會寫進紀錄,輸入框與結果也都對分析錄影遮蔽。
預設就是 UTF-8。百分比跳脫序列帶的是位元組,不是字元,規範本身也沒有記錄這些位元組是哪一種字元集編出來的。現在的系統都用 UTF-8,本頁也照 UTF-8 解。台灣的老系統如果是用 Big5 編出來的(少數政府或企業的舊網頁還看得到),照 UTF-8 解會是亂碼或直接失敗,把上方的「解碼字元集」切到 Big5 就解得回來;這個選項在「URL 解碼」與「拆解網址」兩個模式都有。
編碼不等於消毒。百分比編碼讓一串字可以安全地在網址裡傳輸,它不是 HTML 跳脫,不是 SQL 跳脫,也不是任何注入攻擊的防線。編碼過的值解開之後照樣要驗證,把轉址目的地編過也擋不住 open redirect。
長度規範沒上限,但每一段路都有。瀏覽器、網頁伺服器、反向代理與 log 管線各自有自己的請求行上限,整條路上最小的那個說了算,所以 query string 控制在 2KB 內最保險,再大的東西該放進 POST body。
編碼過的網址人類讀不動。螢幕閱讀器會把一串百分比編碼一個字元一個字元念出來,人用嘴念也一樣。連結請給有意義的文字,不要直接把裸網址貼出來;要給人看的文件裡,另外留一份解碼過的版本。
常見問題
為什麼有時候空格是 + 不是 %20?
兩種編碼都常見,語意略不同:application/x-www-form-urlencoded(form 提交)會把空格編成 +,URL path 與 query 多用 %20。現代 API 兩種都接受,但對應的 decode 函式不同(decodeURIComponent 不會把 + 還原成空格)。本頁解碼或拆解網址時勾「把 + 當空格」,就照表單的規則解。
為什麼解碼後是亂碼?
URL 編碼只規範「位元組怎麼表示」,不規範「位元組對應什麼字元集」。如果原本是 Big5 編碼的字元被 percent-encode,用 UTF-8 解就會亂碼。本工具預設 UTF-8;台灣老系統(早期政府、銀行網頁)用 Big5 編出來的網址,把「解碼字元集」切到 Big5 就解得回中文。
百分比編碼有長度上限嗎?
URL 規範本身沒長度上限,但瀏覽器與伺服器各有實作限制:Chrome ~2 MB、IE 2083、Apache 預設 8190、nginx 預設 8192。一般 query string 控制在 2KB 內最保險。
encodeURI 跟 encodeURIComponent 該用哪一個?
判斷方式是問「這個字串可以改變網址的結構嗎」。整個網址丟進去、而且你希望它還是一個可以點的連結,用 encodeURI,它會保留 : / ? # & = 這些結構字元;字串只是 query string 裡的一個值或一段路徑,用 encodeURIComponent,它連結構字元一起編掉。使用者打的字永遠屬於後者。本頁的「編碼範圍」就是在切這兩個函式,解碼那邊對應 decodeURI 與 decodeURIComponent。
把一個網址塞進另一個網址的參數,為什麼會壞掉?
因為內層網址自己也有 :、/、? 和 &,用 encodeURI 編外層時這些字元會被原樣留著,伺服器就把它們讀成外層網址的結構,redirect_uri 或 next= 後面的東西於是被截掉或被當成別的參數。正確做法是先把內層網址單獨用「參數值(encodeURIComponent)」編一次,再把編好的結果貼進外層連結。
解碼時跳出 URI malformed 是什麼意思?
代表這串文字裡有 % 後面接不出兩位 16 進位數字的地方。常見原因有三個:原文裡有一個本來就要當百分比符號用的 %,沒有被編成 %25;從 log 複製時剛好把一組 %XX 切成兩半;或是整行在結尾被截斷。把完整的一行重新複製一次通常就解決了。
資料來源
- IETF,RFC 3986, Uniform Resource Identifier (URI): Generic Syntax(unreserved 與 reserved 兩組字元,以及百分比編碼的規則)。
- MDN,encodeURIComponent()與URLSearchParams(各個函式分別放過哪些字元)。
- WHATWG,URL Standard(
application/x-www-form-urlencoded的序列化方式,以及那裡的空格為什麼是+)。 - Python Software Foundation,urllib.parse(上面引用的
quote、quote_plus與unquote行為)。請求行的長度上限來自各伺服器自己的預設值,版本之間會變動。
更多開發者工具
參數解開之後常常是別的東西喬裝的:Base64 交給「Base64 編碼解碼」,API 回應交給「JSON 格式化」,query string 裡那張 bearer token 用「JWT Decode 解碼」打開看過期時間。要產要塞進網址的識別碼,用「UUID 產生器」,v4 與 v7 本來就是 URL 安全的字元。要驗證一整批網址長得對不對,用「Regex 正規表示式測試」。
相關工具
- Base64 編碼解碼
文字 ↔ Base64 雙向編碼解碼,完整支援 UTF-8 中文與特殊字元。
- JSON 格式化
JSON 美化與壓縮雙向轉換,支援 2 / 4 / Tab 縮排,語法錯誤即時標出行號。
- QR Code 產生器
把文字、網址、Wi-Fi 連線資訊(SSID 帳密)轉成 QR Code,支援下載 SVG 向量…
- JWT Decode 解碼
JWT 解碼工具:貼上 JSON Web Token 解出 header 與 payload,過…
- Regex 正規表示式測試
輸入 regex pattern 與測試文字,即時標出所有匹配結果、捕獲分組與位置,支援 g、i、m、s、u、y 旗標。
- 雜湊計算
計算文字與檔案的 SHA-1、SHA-256、SHA-384、SHA-512 雜湊值,走瀏覽器原…
隱私:編碼與解碼都在你的瀏覽器裡用內建函式完成,貼進來的內容不會上傳、不會儲存、不會寫進紀錄,輸入框與結果也都對分析錄影遮蔽。分析只記錄這一頁被看過。
