Python URL编码踩坑:单层编码、双重编码实战解析

很多爬虫、接口对接时都会踩URL编码的坑,尤其是双重URL编码 ,乍一看很诡异,%25 到底是什么?本文结合真实业务场景讲清楚原理与Python实现。

一、什么是URL编码(urlquote)

URL中部分字符属于保留字符 ,不能直接放在查询参数里,例如 { } " : , 等。

URL编码作用:将特殊字符转为 %十六进制 格式。

常见对照表:

  • " → %22
  • { → %7B
  • } → %7D
  • : → %3A
  • , → %2C
  • % → %25 👉 重点!百分号本身编码后变成 %25,这就是双重编码的核心根源

Python内置模块 urllib.parse.quote 用来做URL编码,无需额外安装第三方包。

quote 关键参数:safe

quote(string, safe='/', encoding=None, errors=None)

  • safe:设置不需要编码 的字符,默认是 /,也就是斜杠不会被编码。
  • safe='':强制所有字符全部编码,不保留任何原始字符,爬虫对接很多后端接口时必须用这个。

示例单层编码:

python 复制代码
from urllib.parse import quote
import json

data = {
    "articleId": "89c46c77f90e61ff7df01e3cc18eae9349f619f697ca4366e94fad724d913ee6",
    "columnId": "268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c"
}
json_str = json.dumps(data, separators=(',', ':'))
# 单层URL编码
encode_1 = quote(json_str, safe='')
print(encode_1)
# 输出:%7B%22articleId%22%3A%2289c46c77f90e61ff7df01e3cc18eae9349f619f697ca4366e94fad724d913ee6%22%2C%22columnId%22%3A%22268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c%22%7D

二、双重URL编码是什么(本次业务场景)

很多老旧Java/PHP后端、门户站点接口,会在服务端自动做一次URL解码。

如果前端传递JSON字符串作为URL参数:

  1. 前端把JSON编码一次 → %7Bxxx%7D
  2. 浏览器/网关还会再自动编码一遍 ,把里面的%编码成%25
    最终就变成我们看到的:%257B%2522articleId%2522...%257D

本质:对已经编码后的字符串,再执行一次quote

python 复制代码
# 双层URL编码
encode_1 = quote(json_str, safe='')
encode_2 = quote(encode_1, safe='')
final_param = f"params={encode_2}"
print(final_param)
# params=%257B%2522articleId%2522%3A%252289c46c77f90e61ff7df01e3cc18eae9349f619f697ca4366e94fad724d913ee6%2522%2C%2522columnId%2522%3A%2522268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c%2522%257D

完整链路:从原始URL提取参数 + 双重编码

业务场景:从一个url链接中提取query参数,组装成json,再双重url编码拼成params=xxx

python 复制代码
from urllib.parse import urlparse, parse_qs, quote, unquote
import json

url = '[https://www.chinaisa.org.cn/gxportal/xfgl/portal/content.html?articleId=89c46c77f90e61ff7df01e3cc18eae9349f619f697ca4366e94fad724d913ee6&columnId=268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c](https://www.chinaisa.org.cn/gxportal/xfgl/portal/content.html?articleId=89c46c77f90e61ff7df01e3cc18eae9349f619f697ca4366e94fad724d913ee6&columnId=268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c)'

# 1. 解析url,提取查询参数
parsed = urlparse(url)
query_dict_raw = parse_qs(parsed.query)
# parse_qs返回值是列表,取第一个值转为普通字典
data = {k: v[0] for k, v in query_dict_raw.items()}

# 2. 字典压缩成json字符串,去掉多余空格
json_str = json.dumps(data, separators=(',', ':'))

# 3. 双重URL编码
encode1 = quote(json_str, safe='')
encode2 = quote(encode1, safe='')
result = f"params={encode2}"

print(result)

三、解码:双重编码反向还原

调试的时候,我们拿到params=%257B...字符串,需要还原回原始字典,解码顺序反过来:先unquote一次,再unquote第二次

python 复制代码
from urllib.parse import unquote
import json

encoded_str = "params=%257B%2522articleId%2522%3A%252289c46c77f90e61ff7df01e3cc18eae9349f619f697ca4366e94fad724d913ee6%2522%2C%2522columnId%2522%3A%2522268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c%2522%257D"
# 截取params=后面部分
val = encoded_str.split("params=")[1]
# 两次解码
d1 = unquote(val)
d2 = unquote(d1)
origin_dict = json.loads(d2)
print(origin_dict)

四、常见踩坑总结

  1. 忘记 safe='' :默认safe="/",/不会编码,很多接口校验严格,会直接报错。
  2. 分不清单层和双层编码:接口返回参数带有大量%25,基本就是双重URL编码。
  3. parse_qs返回list:同一个参数允许多次出现,{k:v[0]}只适用于参数唯一场景。
  4. json序列化带空格:json.dumps(data)默认带换行空格,编码后后端识别异常,务必用separators=(',',':')压缩json。
  5. quote_plus 和 quote 的区别:quote_plus会把空格编码成+,常用于application/x-www-form-urlencoded表单;quote空格编码成%20,url路径参数优先使用quote。

五、什么时候会遇到双重URL编码

  • 老项目Java后端,服务端自动做一次decode
  • 多层网关、Nginx转发,每层都会自动解码一次
  • 前端JS先编码,浏览器请求时又自动编码一遍
  • 爬虫对接政务、国企门户网站(本次案例就是典型场景)

调试小技巧:看到编码串大量%25,第一反应:这不是普通url编码,是双重编码。

六、小结

URL编码本质就是特殊字符转义,单层编码是基础;而双重URL编码 是爬虫对接老旧网站高频遇到的特殊场景。核心逻辑:JSON字符串 → quote一次 → quote第二次。

Python依靠urllib.parse内置库就能全部实现,无需requests或者其他第三方编码库。调试时可以逆向解码验证结果是否正确,避免接口报参数解析失败。

相关推荐
IvanCodes1 小时前
Python 正则表达式(十四):文本匹配、查找与替换
开发语言·python·正则表达式
茶栀(*´I`*)1 小时前
【Python数据可视化】Matplotlib从零到精通:图表核心解剖、多子图布局与三大经典图表实战
python·信息可视化·matplotlib
孙启超1 小时前
【AI开发之Rust】第 21 课:双端集成与出包 —— Android(.so→AAR)与 iOS(xcframework)
开发语言·后端·rust
SelectDB技术团队1 小时前
Agent Trace 数据底座建设:宽表建模、全文检索与成本聚合的配置与验证步骤
大数据·python·clickhouse·elk·elasticsearch·全文检索·复杂查询
Brilliantwxx1 小时前
【STM32】 __weak 弱引用、中断标志位、异常处理与 HAL_Delay 优先级
开发语言·stm32·单片机·嵌入式硬件
驭渊的小故事1 小时前
SpringBoot 配置文件详解:properties 与 yml 从入门到实战
java·开发语言·笔记
计算机源码社1 小时前
【27届大数据毕设】基于Spark的TikTok短视频属性分享与互动比率可视化分析系统 基于机器学习的TikTok短视频认证属性与互动效能关联分析
大数据·hadoop·python·机器学习·spark·毕业设计·课程设计
北冥有鱼被烹2 小时前
Linux timeout 命令完全指南:指定固定时间并发送固定信号
python
郝学胜-神的一滴2 小时前
游戏引擎原理与实践 02:揭开3A游戏背后的技术面纱
开发语言·计算机网络·程序人生·游戏·游戏引擎