查看: 230|回复: 0

Python URL解码 unquote双重编码parse_qs踩坑

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
在爬虫、接口对接和自动化脚本里,URL 编码与解码几乎是绕不开的基础操作。相比编码,解码更容易踩坑,尤其是双重 URL 编码:字符串里大量出现 %25,如果只做一次 unquote,拿到的仍是第一层编码后的内容,后续 json.loads 容易直接抛 JSONDecodeError。本文基于 Python 内置 urllib.parse,不依赖第三方库,梳理单层解码、双重解码、parse_qs 自动解码以及常见错误处理。

一、URL 解码基础与 unquote/unquote_plus

URL 编码是把特殊字符转为 % 加十六进制,解码则是把 %XX 还原为原始字符。常见映射包括:%22 对应双引号,%7B 对应左花括号,%7D 对应右花括号,%3A 对应冒号,%2C 对应逗号,%25 对应百分号。其中 %25 是识别双重编码的关键字符。

Python 内置 urllib.parse.unquote 负责 URL 解码,配套的 unquote_plus 会把 + 还原为空格,后者适用于 application/x-www-form-urlencoded 表单查询串。普通 URL 路径和参数解析优先使用 unquote。

单层解码示例:
  1. from urllib.parse import unquote
  2. encoded_str = '%7B%22articleId%22%3A%2289c46c77f90e61ff7df01e3cc18eae9349f619f697ca4366e94fad724d913ee6%22%2C%22columnId%22%3A%22268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c%22%7D'
  3. raw = unquote(encoded_str)
  4. print(raw)
  5. # 输出:articleId 和 columnId 组成的 JSON 对象
复制代码

二、双重 URL 解码:先识别 %25,再连续 unquote

双重 URL 编码的形成逻辑是:原始 JSON 先被 quote 一次,得到 %7B、%22 等;再被 quote 一次,% 被编码成 %25,于是出现 %257B、%2522。解码顺序必须反过来:第一次 unquote 把 %25 还原为 %,得到单层编码串;第二次 unquote 才把 %7B、%22 还原为原始字符。

接口参数示例:
  1. params=%257B%2522articleId%2522%3A%252289c46c77f90e61ff7df01e3cc18eae9349f619f697ca4366e94fad724d913ee6%2522%2C%2522columnId%2522%3A%2522268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c%2522%257D
复制代码

完整解码代码:
  1. from urllib.parse import unquote
  2. import json
  3. full_param = 'params=%257B%2522articleId%2522%3A%252289c46c77f90e61ff7df01e3cc18eae9349f619f697ca4366e94fad724d913ee6%2522%2C%2522columnId%2522%3A%2522268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c%2522%257D'
  4. # 截取 params= 后面的值
  5. encode_value = full_param.split('params=')[1]
  6. # 双重解码
  7. decode_step1 = unquote(encode_value)
  8. decode_step2 = unquote(decode_step1)
  9. # 转为字典
  10. data = json.loads(decode_step2)
  11. print(data)
复制代码

执行过程拆解:第一次 unquote 后,%257B 变成 %7B,%2522 变成 %22,得到单层编码串;第二次 unquote 后,%7B 变成 {,%22 变成双引号字符,得到原始 JSON 字符串;最后 json.loads 加载成 Python 字典。如果只解码一次,字符串中仍会保留大量 %,json.loads 无法正常解析。

三、解析完整 URL 查询参数:parse_qs 已自动解码

日常开发中经常拿到完整 URL,需要直接解析 query 参数。此时可以用 urlparse 加 parse_qs,parse_qs 内部自带解码,不需要手动 unquote。
  1. from urllib.parse import urlparse, parse_qs
  2. url = 'https://www.chinaisa.org.cn/gxportal/xfgl/portal/content.html?articleId=89c46c77f90e61ff7df01e3cc18eae9349f619f697ca4366e94fad724d913ee6&columnId=268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c'
  3. parsed = urlparse(url)
  4. query = parse_qs(parsed.query)
  5. # parse_qs 返回 value 是列表
  6. res = {k: v[0] for k, v in query.items()}
  7. print(res)
复制代码

注意:parse_qs 已经完成一次 URL 解码,不要再对结果手动 unquote,否则会重复解码,遇到 %25 这类字符时容易产生脏数据。

四、常见解码踩坑清单

坑 1:双重编码只解码一次。现象是解码后的字符串还包含大量 %,json.loads 直接抛出 JSONDecodeError。判断依据是字符串包含 %25,几乎可以认定是双重编码,需要连续两次 unquote。

坑 2:对 parse_qs 的结果再次 unquote。parse_qs 内部已经自动解码,再次 unquote 会二次解码,遇到 %25 这类字符会产生脏数据。

坑 3:中文解码编码不匹配。unquote 默认 encoding='utf-8',如果碰到 GBK 编码的老网站,会抛出 UnicodeDecodeError。解决方式是显式指定编码:
  1. from urllib.parse import unquote
  2. s = '%D6%D0%CE%C4'
  3. print(unquote(s, encoding='gbk'))
复制代码

坑 4:不合法的 % 编码串。原始字符串被截断,出现 %3 这类不完整编码时,默认会抛异常。可以设置 errors='replace' 忽略错误:
  1. unquote(s, errors='replace')
复制代码

五、封装通用函数:自动处理单层与双重解码

如果项目里经常遇到单层或双重编码,可以写一个简易工具函数,判断字符串中是否包含 %25,自动选择解码次数。代码可以直接复用:
  1. from urllib.parse import unquote
  2. import json
  3. def auto_double_decode(s: str):
  4.     '''自动判断单层/双重URL解码,返回原始字符串'''
  5.     if '%25' in s:
  6.         s = unquote(unquote(s))
  7.     else:
  8.         s = unquote(s)
  9.     return s
  10. # 测试
  11. encoded = '%257B%2522articleId%2522%3A%252289c46c77f90e61ff7df01e3cc18eae9349f619f697ca4366e94fad724d913ee6%2522%2C%2522columnId%2522%3A%2522268f86fdf61ac8614f09db38a2d0295253043b03e092c7ff48ab94290296125c%2522%257D'
  12. raw_json = auto_double_decode(encoded)
  13. data = json.loads(raw_json)
  14. print(data)
复制代码

提示:这个简易函数只适用于爬虫常见场景,极端多层编码(3 层及以上)不适用。

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

双重编码常见于老 Java 政务、国企网站;网关或 Nginx 多层转发时,每层可能自动解码,前端 JS 编码后浏览器发起请求再次自动编码;接口返回的参数是经过两层编码的 JSON 字符串,并放在 URL query 里传递。这些场景下,如果只依赖一次 unquote,参数解析失败的概率会很高。

七、小结

URL 解码核心是 urllib.parse.unquote,单层解码是基础;%25 是识别双重 URL 编码最直观的标记,遇到它就要连续两次解码。开发中还要区分 unquote 和 unquote_plus,不要在 parse_qs 解析完成后重复解码。遇到中文乱码时记得切换 gbk 编码。编码和解码成对学习,在爬虫对接老旧门户、复杂网关转发接口时,能解决相当一部分参数解析失败问题。

以上内容围绕 Python 单层与双重 URL 解码的实战代码和踩坑点展开,核心是保留 unquote 的调用顺序、parse_qs 的自动解码行为以及编码错误处理方式。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-30 13:25 , Processed in 0.020318 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部