土薯工具 Toolshu.com 登录 用户注册

URL 里的 %20 是什么?一文搞懂 URL 编码的原理与踩坑

原创 作者:bhnw 于 2026-04-07 15:40 发布 4次浏览 收藏 (0)

从一个真实的 bug 说起

某天,后端同事反映接口收到的参数值不对:前端传了一个包含&符号的字符串作为查询参数,但后端解析出来的值被截断了。

原因很简单:& 在 URL 里是参数分隔符,没经过编码就直接拼进 URL,后端自然会把它当成参数边界解析。

这就是 URL 编码存在的意义——URL 里有一批字符有特殊含义,如果数据本身包含这些字符,必须先编码,才能安全传输。


URL 编码的本质:百分号编码

URL 编码的正式名称是百分号编码(Percent-Encoding),规则很简单:

把字符转成 UTF-8 字节,每个字节用 % 加两位十六进制数表示。

以空格为例:

空格 → UTF-8字节: 0x20 → URL编码: %20

以中文"中"为例:

中 → UTF-8字节: 0xE4 0xB8 0xAD → URL编码: %E4%B8%AD

所以 URL 里看到的 %20 就是空格,%E4%B8%AD%E6%96%87 就是"中文"。

哪些字符需要编码

RFC 3986 定义了 URL 中的"保留字符"和"非保留字符":

  • 非保留字符(不需要编码):A-Z a-z 0-9 - _ . ~
  • 保留字符(有特殊含义,作为数据时需要编码):: / ? # [ ] @ ! $ & ' ( ) * + , ; =
  • 其他字符(中文、日文、空格、特殊符号等):必须编码

encodeURI 和 encodeURIComponent:傻傻分不清

JavaScript 提供了两个函数,很多人搞不清该用哪个。区分方法只有一条:你编码的是整个 URL,还是 URL 里的某个参数值?

encodeURI:编码整个 URL

encodeURI 会保留 URL 结构字符不编码(因为它们是 URL 的组成部分),只编码真正不合法的字符。

encodeURI("https://example.com/search?q=hello world&lang=zh")
// → "https://example.com/search?q=hello%20world&lang=zh"
// 注意:? & = 都没有被编码,因为它们是 URL 结构的一部分

不会编码的字符: A-Z a-z 0-9 ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) #

encodeURIComponent:编码参数值

encodeURIComponent 会对几乎所有特殊字符编码,包括 ? & = # / 等,适合对单个参数值编码。

const keyword = "hello world & more"
const url = "https://example.com/search?q=" + encodeURIComponent(keyword)
// → "https://example.com/search?q=hello%20world%20%26%20more"
// & 被编码成了 %26,不会干扰参数解析

不会编码的字符: A-Z a-z 0-9 - _ . ! ~ * ' ( )

对比一眼看懂

const str = "a=1&b=2/c?d=中文"

encodeURI(str)
// → "a=1&b=2/c?d=%E4%B8%AD%E6%96%87"
// = & / ? 都保留了

encodeURIComponent(str)
// → "a%3D1%26b%3D2%2Fc%3Fd%3D%E4%B8%AD%E6%96%87"
// = & / ? 全部编码了

结论:拼接参数值时用 encodeURIComponent,几乎不会用错。


开发中最常见的几个坑

坑1:空格编码成了 + 而不是 %20

HTML 表单的 application/x-www-form-urlencoded 格式会把空格编码成 +,而不是 %20。这是历史遗留规范,和标准 URL 编码不同。

表单提交: hello world → hello+world
标准URL编码: hello world → hello%20world

后端解析时要注意区分。PHP 的 urldecode() 会把 + 还原为空格,但 rawurldecode() 不会。

坑2:对同一个值编码了两次

// 第一次编码
const encoded = encodeURIComponent("hello world")
// → "hello%20world"

// 又编码了一次!
const doubleEncoded = encodeURIComponent(encoded)
// → "hello%2520world"  (%25 是 % 的编码)

后端收到 %2520,解码一次得到 %20,再解码才能得到空格。这种 double encoding 是接口联调时的常见问题。

坑3:encodeURI 无法编码 #

# 在 URL 里表示锚点,encodeURI 不会对它编码。如果参数值里包含 #,必须用 encodeURIComponent

encodeURI("https://example.com?tag=#title")
// → "https://example.com?tag=#title"  # 没有被编码!

"https://example.com?tag=" + encodeURIComponent("#title")
// → "https://example.com?tag=%23title"  正确

坑4:后端语言的解码函数不统一

不同语言的 URL 解码函数行为略有差异:

# Python
from urllib.parse import unquote, unquote_plus
unquote("hello%20world")      # → "hello world"  (%20 → 空格)
unquote_plus("hello+world")   # → "hello world"  (+ → 空格)
// Java
URLDecoder.decode("hello+world", "UTF-8")   // → "hello world"(+ 会被解码为空格)
URI.create("hello%20world").getPath()        // → "hello world"(标准解码)
urldecode("hello+world")     // → "hello world"
rawurldecode("hello+world")  // → "hello+world"(+ 不解码)
rawurldecode("hello%20world") // → "hello world"

前后端约定好用哪种编码方式,解码函数要对应匹配。


中文 URL 的处理

浏览器地址栏输入中文网址时,浏览器会自动编码再发出请求。比如访问:

https://example.com/搜索?关键词=中文

实际发出的请求是:

https://example.com/%E6%90%9C%E7%B4%A2?%E5%85%B3%E9%94%AE%E8%AF%8D=%E4%B8%AD%E6%96%87

SEO 角度:中文 URL 对搜索引擎是友好的(百度尤其如此),但服务器日志和代码里看到的是编码后的版本,调试时注意解码再看。


快速验证

遇到一串看不懂的 URL 编码字符,或者需要把中文/特殊字符编码成 URL 安全的格式,可以直接用 toolshu.com 的 URL 编码解码工具。支持 encodeURI 和 encodeURIComponent 两种模式,粘贴即用。

发现周边 发现周边
评论区

加载中...