步骤一:识别加密
目的:找出请求里用于防爬或校验的“被加密”参数
找接口(步骤与操作)
进入目标站点,在页面搜索框输入关键词并搜索
打开开发者工具 —— Network,滚动页面或点击分页加载商品
清空 Network,再触发一次加载(例如点击“第1页”)以便筛查
使用 Network 的搜索(Ctrl + F),把一个商品标题粘贴进去,根据定位结果打开对应请求
在请求的 Preview/Response 中查看 JSON,展开 data.resultList 可见商品条目

找参数(对比请求找动态字段)
复制目标接口 URL,在 Network 的顶部筛选器里粘贴以仅显示该接口
刷新页面或再次触发请求,比较两个或多个请求的 Payload(负载)
记录所有参数并对比:出现变化的参数就是逆向重点

步骤二:参数定位
目的:在网页加载的 JS 源码中找到哪个函数/代码片段生成该参数
找位置(全局搜索)
使用正则精确匹配,例如
\bsign\b: 可避免匹配到assign、design
步骤三:参数解析
目的:理解加密函数的实现,明确它的输入与输出,并能复现在本地
找逻辑(查看变量与函数)

d = this.options
h = c.appKey || ("waptest" === d.subDomain ? "4272" : "12574478"),
j = (new Date).getTime(),
k = i(d.token + "&" + j + "&" + h + "&" + c.data),
l = {
jsv: A,
appKey: h,
t: j,
sign: k
}t(时间戳):直接取当前系统时间的毫秒级 Unix 时间戳(Date.now()),作为签名防重放的核心因子,服务端会校验时间偏差,超出阈值则拒绝请求。
sign(签名):通过固定格式拼接字符串后,调用前端已有
i方法哈希生成i方法可直接复制函数实现,无需额外开发。
核心拼接模板为{token}&{t}&{appKey}&{data},将拼接好的字符串传i方法,即可生成 32/64 位十六进制签名串,参数顺序、格式(无多余空格 / 换行)必须完全匹配,否则签名失效。token为前端会话密钥(token在Console控制台多次打印,看起来是恒定的,其实不然,而是大约一个半小时会发生变化,所以应该定位token生成)
appKey为固定业务标识、恒定可写死
data为 JSON 格式的业务参数,通常等于请求的表单数据(包含 pageNum、keyword 等)
t为时间戳
定位 token 生成
使用的 XHR 断点方法,定位 token 参数的生成位置(因为过程比较繁杂,这里就不具体展开了)
// 优先级判断
a.CDR && l(B) ? a.token = l(B).split(";")[0] : a.token = a.token || l(C)
// 格式清洗
a.token && (a.token = a.token.split("_")[0])核心 Cookie 键名:关键依赖两个预设 Cookie 键,核心为
_m_h5_tk,备选为_m_h5_c。Cookie 读取优先级:优先判断是否开启
CDR标识,若开启则读取_m_h5_c;若未开启,优先复用已有token,无已有值时则读取核心 Cookie_m_h5_tk。格式清洗规则:无论读取哪个 Cookie,最终都需对值做统一处理 —— 按下划线
_分割字符串,取第一部分作为有效token,剔除后缀冗余内容。
总结:token 的来源和 Cookie 紧密相关,时效由对应 Cookie 的有效期决定。
步骤四:参数调用
目的:将复现的签名逻辑集成到本地流程,用生成的加密参数发起合法请求并获取数据
把加密函数及其调用逻辑封装成一个可调用函数(JS 文件内)
//goofish_sign_crack.js
function get_sign(data,token,h) {
function i(a) {...}
j = (new Date).getTime()
k = i(token + "&" + j + "&" + h + "&" + data)
return [j,k]
}在 Python 中用 execjs 调用该函数得到加密参数的值
第一步:准备js代码需要的所有参数
token = cookies.get('_m_h5_tk', '').split('_')[0]
appKey = params['appKey']第二步:读取 JS 代码文件
建议使用utf-8编码`打开文件,避免中文或特殊字符导致解析错误。
with open('goofish_sign_crack.js', 'r', encoding='utf-8') as f:
js_code = f.read()第三步:编译 JS 代码为可调用的上下文
使用 execjs.compile(js_code) 创建一个隔离的 JS 环境。
import execjs
js = execjs.compile(js_code)第四步:调用 JS 函数获取签名
使用 js.call('get_sign', 参数1, 参数2, ...) 调用并获取返回值。
q = js.call('get_sign',data['data'],token,appKey)
# q 的形式为 [t, sign]
params['t'] = q[0]
params['sign'] = q[1]
print("生成的有效 t 和 sign:", q)完整的.py代码如下:
# 导入execjs模块,用于执行JavaScript代码(生成签名)
import execjs
# 导入requests模块,用于发送HTTP请求
import requests
# 定义请求所需的Cookie参数(从浏览器抓包获取,包含闲鱼会话信息、token等关键数据)
cookies = {
't': 'ca87ebc39b662ec0e1e8cadddf55ab37',
'cna': 'm+P9IarevSIBASQJilyUngif',
'isg': 'BJGRzWjsnD0rBfAp18pOEhZwoJ0r_gVwxQjuHHMnJNh3Grlsu07NQO95uO78Jp2o',
'cookie2': '1dde233c05f155d92918aa02f401f822',
'xlly_s': '1',
'_samesite_flag_': 'true',
'_tb_token_': '3eb16187b0db3',
'mtop_partitioned_detect': '1',
'_m_h5_tk': 'bdca7f16aac67b1af76b36eac6d929b3_1772469031135',
'_m_h5_tk_enc': '4bce1c2deda447408314028f5c547899',
'tfstk': 'gdYxnXq92YDcPagSprmosHQmAWluD0A2mKRQSOX0fLpJOBycfEbDBlpD_djs3ZY9eC9Kg1D2SS-yKBaDom7gWZ7N5vDn-2ADgN71X06-C-W5i60gcGafuaZBnI3K-2A2GSfjtQgHmyFGe1W15is_V_6P6o16CGaWNT5N1-1_lbd5UT1bCta1Fu1Cs1Z6CNGJNT5ahNsXCgGR_T615NMm3SBgGOU92R5sy7q0EN7pysIAdQnUWPwPEJXJGuzT5UpxL9dbbPa6ysIvyaqXVnIDfBSeoEgTzZApVa1HgqURRI1pnTvIRzQWZQ9GY3ka-gJJWGTf05G5FEpJuZ9mfypdkdIBlgF_SBd5HaCWRfqFEUBDCEIStyIGPejCl3mrnGbA9dT2H5U6dQAHug8tp86wm675tIuYywOf1g-w-e3pwJXdsoG-wlrNc_-eVSvgzJuzl_BnZuqabgfPw9c-wlrNc_5RKb0ublSla',
}
# 定义请求头(模拟浏览器请求,避免被风控拦截)
headers = {
'accept': 'application/json',
'accept-language': 'zh-CN,zh;q=0.9',
'content-type': 'application/x-www-form-urlencoded',
'origin': 'https://www.goofish.com',
'priority': 'u=1, i',
'referer': 'https://www.goofish.com/',
'sec-ch-ua': '"Not:A-Brand";v="99", "Microsoft Edge";v="145", "Chromium";v="145"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.0.0',
}
# 定义MTOP接口URL参数(接口核心配置)
params = {
'jsv': '2.7.2',
'appKey': '34839810',
't': '1772460078712',
'sign': 'd7bb1ed82e5a541415673979380434d0',
'v': '1.0',
'type': 'originaljson',
'accountSite': 'xianyu',
'dataType': 'json',
'timeout': '20000',
'api': 'mtop.taobao.idlemtopsearch.pc.search',
'sessionOption': 'AutoLoginOnly',
'spm_cnt': 'a21ybx.search.0.0',
'spm_pre': 'a21ybx.home.searchHistory.1.4c053da6OdvVjl',
'log_id': '4c053da6OdvVjl',
}
# 定义请求体数据(搜索业务参数)
data = {
# JSON字符串:搜索关键词、分页、排序等核心参数
'data': '{"pageNumber":2,"keyword":"小米17promax","fromFilter":false,"rowsPerPage":30,"sortValue":"","sortField":"","customDistance":"","gps":"","propValueStr":{},"customGps":"","searchReqFromPage":"pcSearch","extraFilterValue":"{}","userPositionJson":"{}"}',
}
# 核心步骤1:提取_m_h5_tk中的有效token(按_分割取第一段,签名关键)
token = cookies.get('_m_h5_tk', '').split('_')[0]
# 提取appKey(从params中获取,避免硬编码)
appKey = params['appKey']
# 核心步骤2:读取JS签名脚本(包含sign生成逻辑)
with open('goofish_sign_crack.js', encoding='utf-8') as f:
js_code = f.read()
# 编译JS代码(准备执行)
js = execjs.compile(js_code)
# 核心步骤3:调用JS中的get_sign函数生成t(时间戳)和sign(签名)
# 入参:业务data、token、appKey;返回值:[t, sign]
q = js.call('get_sign', data['data'],token,appKey)
# 更新params中的t和sign为动态生成的值(替换初始值)
params['t'] = q[0]
params['sign'] = q[1]
# 打印生成的t和sign,便于调试
print("生成的有效 t 和 sign:", q)
# 核心步骤4:发送POST请求调用闲鱼MTOP搜索接口
response = requests.post(
'https://h5api.m.goofish.com/h5/mtop.taobao.idlemtopsearch.pc.search/1.0/', # 接口地址
params=params, # URL参数
cookies=cookies, # Cookie参数
headers=headers, # 请求头
data=data, # 请求体
)
# 打印响应结果(调试用)
print("响应状态码:", response.status_code) # 打印状态码(200为成功)
print("响应文本:", response.text) # 打印原始响应文本(避免JSON解析失败)
try:
# 尝试解析JSON格式响应(便于结构化查看)
print("响应 JSON:", response.json())
except Exception as e:
# JSON解析失败时打印错误信息
print("JSON 解析失败:", e)