尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

爬虫动态参数入门:JS逆向定位与Python复现完整指南

爬虫动态参数入门:JS逆向定位与Python复现完整指南 这次我们来看爬虫入门之后必经的一道坎动态参数。很多人用 Python requests 抓页面的时候静态页面一点问题没有。可一旦碰到搜索接口、列表接口、分页接口就发现请求里多了ts、sign、token一类参数。参数看着是字符串但每次请求都会变。直接复制到 requests 里第一次能用第二次就 403或者直接返回验证码页面。这就是动态参数在起作用。动态参数通常由前端页面的 JS 代码生成服务端收到请求后做校验。想稳定拿到数据就不能只盯着 Network 面板里那一串值而是要看懂这笔参数是在哪个 JS 文件、用什么规则算出来的。这也是 JS 逆向入门最核心的一课看懂动态参数怎么生成。这篇文章直接按实际开发顺序来讲先认识动态参数再看怎么定位它在 JS 中的生成位置然后断点调试确认逻辑最后用 Python 把它复现出来。中间会穿插批量请求、参数缓存、失败重试这些工程化做法帮你把整套流程落地到真实项目里。适合读者能用 Python requests 写基本爬虫但遇到带签名的接口会卡住的人以及刚接触 JS 逆向、不知道从哪下手的人。这篇文章不涉及复杂混淆还原先把主线流程讲清楚。1. 核心能力速览技能点说明核心问题请求中的动态参数是怎么生成的如何稳定复现主要工具Chrome DevTools、抓包工具、Python、Node.js动态参数类型时间戳、签名 sign、token、动态 cookie、行为校验参数定位方法Network 抓包、Initiator 调用栈、全局搜索、断点调试复现方式Python 重写算法、execjs 调用 JS、本地 Node 签名服务难度入门级不需要会 AST 混淆还原适用场景搜索接口、列表分页、登录接口、风控接口的合法测试合规前提只测试自有接口或获得授权的接口不抓取非公开数据2. 动态参数是什么服务端拿它拦什么2.1 动态参数的定义动态参数是指每次请求或一段时间内会变化的请求参数。最常见的是 URL 查询参数里的timestamp和sign其次是请求头里的cookie、authorization也可能是 POST 请求体里的encrypted字段。静态参数没什么好学的复制过来就能用。动态参数的麻烦在于它每隔一段时间就变化而且变化规则在浏览器 JS 里不在后端文档里。你只有两条路一是找到生成规则用 Python 重新实现二是想办法让这段 JS 在本地跑起来直接调用。无论哪条路第一步都是看懂参数是怎么生成的。2.2 服务端的拦截逻辑服务端加动态参数核心目的通常有三个。第一防止请求重放。请求里带时间戳服务器判断时间差超过 30 秒或 60 秒的直接拒绝这样从浏览器里复制出来的旧请求就没用了。第二防止参数篡改。请求参数经过签名服务端用同样的算法重新算一遍签名对不上就拒绝。这可以防止你手动修改page、keyword等参数去翻数据。第三提高爬虫成本。动态参数如果包含行为验证、加密 cookie、指纹信息服务端可以判断请求是否来自真实浏览器。这就是为什么爬虫里有一个专门的领域叫“补环境”。2.3 哪些场景一定会遇到动态参数从实际经验看下面几类接口几乎必然带动态参数新手学 JS 逆向也可以重点从这些方向练手搜索接口关键词搜索、商品搜索、用户搜索一般会带sign或token。列表分页接口翻页时会在 URL 里增加时间戳和签名还会校验 Cookie。登录接口密码通常会加密传输请求头带client签名。风控接口验证码校验、滑块验证、行为数据上报参数复杂且每次变化。这些接口核心套路是一样的找到 JS 生成参数 → 验证算法 → 用 Python 复现。所以不用一上来就盯着某一个大厂的复杂加密先找一个带简单签名参数的接口把流程跑通。3. 动态参数常见类型下面这张表整理了我建议新手优先认识的几类动态参数识别特征和应对方向都在里面。参数类型常见名字识别特征入门应对思路时间戳ts、timestamp、_t10 位或 13 位纯数字随时间变化Python 里生成对应格式转字符串拼到请求里签名sign、sig、signature32 位十六进制字符串典型 MD5全局搜索参数名找到拼接规则用 hashlib 重写动态 Tokentoken、access_token一段较长的随机字符串可能有有效期先找生成或换取 token 的请求再看加密逻辑动态 Cookiecookie 中含加密值每次刷新页面变化请求头带完整 Cookie用 Session 维护 Cookie或复现生成逻辑行为校验参数captcha、verify、risk滑块、点选、行为轨迹相关字段入门阶段建议先跳过这部分成本高、风险大加密 Bodydata、encrypted、payloadPOST 请求体不是明文 JSON定位加密函数用 Python 重写或用 Node 执行这里说一个实战中的判断原则如果接口返回数据是明文 JSON只有签名参数是动态的这种是最适合新手练习的类型。如果接口返回整个 body 都是密文那就要额外处理解密难度会提高一个档次。另外你可以做一个快速验证来判断动态参数是否真的被服务端校验把接口 URL 复制到浏览器新标签页打开改掉sign参数里的一个字符。如果返回错误或校验提示说明服务端确实校验了这个参数如果返回的数据一样说明这个参数当前没有被使用。这个测试在自有接口或授权接口上做没问题不要在别人的站点上随便试。4. 入门工具链与环境准备4.1 浏览器工具Chrome DevTools 是主力PC 端 Web 逆向Chrome DevTools 足够完成 90% 的工作。需要重点掌握的面板有四个Network 面板查看请求的 URL、参数、请求头、Cookie。Sources 面板查看 JS 文件、格式化代码、打断点、看调用栈。Console 面板临时执行 JS 代码Hook 函数验证变量。Application 面板查看 Cookie、LocalStorage、SessionStorage排查动态 Cookie 的来源。如果要在移动端 Web 页面调试可以用 Chrome 的远程调试功能或者使用抓包工具配合代理。4.2 抓包工具Fiddler 或 Charles网页端中 DevTools 的 Network 已经够用但有些页面做了防调试或者你需要看原生 App 的请求就离不开抓包工具。抓包工具的基本作用是建立本地代理把设备的 HTTPS 流量转发并解密。使用时需要在手机或模拟器里设置代理并安装抓包工具生成的 CA 证书。这个操作在测试自有设备时没有问题但要注意抓包只能用于自己拥有或已获授权设备上的流量分析不能用于窃取他人通信数据。4.3 本机运行环境Python Node.jsPython 负责最终发请求Node.js 用于执行 JS 源码或运行本地签名服务。Python 环境需要安装pip install requests pyexecjsNode.js 不是必须但强烈建议装。原因有两个第一调试 JS 时直接在 Node 里跑一段代码比在浏览器 Console 里方便第二复杂加密逻辑可以直接写成 Node 服务让 Python 调。Node 装好后可用node -v验证。磁盘空间方面普通入门不需要模型文件Python 环境和浏览器加起来占用很小几十 GB 的硬盘完全够用。5. 从抓包到定位动态参数5.1 第一步找到产生参数的请求打开目标页面按 F12 打开 DevTools切到 Network 面板勾选 Fetch/XHR然后刷新页面或触发一次搜索找到真正返回业务数据的接口。优先看 Fetch/XHR 而不是 All是因为业务数据接口基本都是 XHR 或 fetch 请求过滤后可以去掉图片、CSS、JS 文件等噪声请求。举个例子假设这个接口的 URL 是https://api.example.com/search?keywordpythonpage1ts1690000000signa5f4c0d8e1b2我们观察一下keyword和page是业务参数ts是时间戳每次刷新都会变化sign是 32 位十六进制字符串也变化。ts一眼能猜出来是时间戳真正的难点是sign。右键这个请求选择 Copy → Copy as cURL。这样可以在终端里直接还原请求确认带上同样参数能否稳定拿到数据。5.2 第二步用 Initiator 查看调用栈定位 JS 生成逻辑最快的方法是看 Initiator 列。在 Network 面板里点击请求找到 Initiator 标签页。它会显示这个请求是由哪一段 JS 发起的列出了调用链。顺着调用链点进去就能看到发起请求的代码。这一步通常能直接看到sign变量是在哪里赋值、调用了哪个函数。如果 Initiator 里显示了多个层级的函数调用从最上层一直往下点最终会落在某个 JS 文件的具体行号上。点击行号会直接跳到 Sources 面板的对应位置省去手动搜索的功夫。5.3 第三步全局搜索参数名如果 Initiator 不够清晰或者代码里有混淆和封装就用全局搜索。回到 Sources 面板按 CtrlShiftF 打开全局搜索输入sign也可以输入sign 、sign:、sign这些更精确的写法。搜索结果会列出所有包含sign的 JS 文件。优先查看发起请求的那段代码附近找到sign的赋值语句。赋值语句通常长这样var sign getSign(keyword, page, ts);或者params[sign] md5(keyword page ts 固定盐值);到这里我们已经知道参数由哪个函数生成。下一步就是断点调试确认函数内部逻辑。6. 断点调试找到参数生成逻辑6.1 在可疑函数处打断点在 Sources 面板里点击行号可以添加断点。把断点打在getSign函数内部比如第一行。然后重新触发一次请求代码执行到这一行就会暂停。这时看右侧的 Scope 面板能看到当前作用域的所有变量值。单步执行到 return 那一行观察每个变量如何被拼接、加密函数输入输出是什么。这个步骤的核心目标是回答三个问题输入是哪些业务参数中间有哪些固定字符串或盐值最终套用了什么加密函数比如 MD5、SHA1、RSA、AES单步执行时有两个常用按钮Step over 是跳过当前函数调用直接执行下一行Step into 是进入当前函数内部逐行查看。看签名逻辑时大概率要进入加密函数内部确认具体算法。6.2 XHR/fetch 断点如果找不到发起请求的 JS 代码可以直接用 XHR/fetch 断点。在 Sources 面板右侧的 XHR/fetch breakpoints 里添加要拦截的 URL 关键字。代码准备发送这个请求时浏览器会中断。然后沿着调用栈往回走就能找到请求参数被赋值的位置。调用栈里每一层都可以点击双击后查看这一层执行到哪一行观察sign是什么时候被塞进 params 的。6.3 Hook 函数临时验证在 Console 面板里可以临时替换 JS 里的加密函数用来确认参数生成入口。最常见的做法是 Hook 全局函数在函数被调用时打印参数和调用栈。if (window.md5 !window.__originalMD5) { window.__originalMD5 window.md5; window.md5 function(input) { console.trace(md5 called with:, input); return window.__originalMD5(input); }; }这里需要先确认md5是全局函数。如果不是就改成定位到的加密函数名。Hook 不是必须但对于确认“是不是这个函数参与了签名”非常高效。6.4 反调试代码怎么处理如果页面代码里有debugger语句或者检测到 DevTools 打开就死循环这类反调试手段的目的是增加调试成本。应对思路是在 Sources 里右键点击行号选择 Never pause here跳过无限 debugger。使用脚本注入重写检测用的函数。在本地复制一份页面资源用本地文件替换线上 JS再做静态分析。这里要提醒一句把反调试代码当作“学习 JS 执行机制”的样本没有问题但如果目标是绕过某个在线服务的风控去抓取非公开数据这已经超出合法的技术学习范围。本文只讨论在自有或授权测试环境中的分析方法。7. 用 Python 复现动态参数定位到参数生成逻辑后有几种方式在 Python 里复现。按推荐顺序讲。7.1 方式一Python 按算法重写最推荐如果参数生成逻辑不复杂比如只是字符串拼串后做 MD5建议直接用 Python 重写。这样不依赖额外进程速度最快也最容易维护。假设我们通过调试确认sign的规则是sign md5(keyword page ts mySecretKey)那么 Python 端的实现如下import hashlib import time import requests SECRET_KEY mySecretKey def generate_sign(keyword: str, page: str, ts: str) - str: raw f{keyword}{page}{ts}{SECRET_KEY} return hashlib.md5(raw.encode(utf-8)).hexdigest() ts str(int(time.time())) sign generate_sign(python, 1, ts) params { keyword: python, page: 1, ts: ts, sign: sign, } resp requests.get(https://api.example.com/search, paramsparams, timeout10) print(resp.status_code) print(resp.text[:500])判断成功的标准很简单返回 200 且带了正常的业务 JSON说明签名复现成功。如果返回 403 或验签失败提示就回去检查拼接顺序、盐值、是否缺少某个参数。一个容易踩的坑是服务端可能不是用keyword page ts拼接而是先做参数排序比如按 ASCII 码排序后拼接。遇到签不上时优先检查参数拼接顺序是否和 JS 里一致。7.2 方式二execjs 直接调用 JS 函数如果 JS 逻辑中有较长的加密过程或者含有 Node 自带的加密模块可以尝试用 execjs 把 JS 函数跑起来。import execjs import time js_code function getSign(keyword, page, ts) { // 这是从页面中抽取出来的签名函数实际内容需要从 Sources 中提取 var raw keyword page ts mySecretKey; // 为了演示这里简化成一次字符串替换 return raw.replace(/[0-9]/g, ); } ctx execjs.compile(js_code) ts str(int(time.time())) sign ctx.call(getSign, python, 1, ts) print(sign)需要注意execjs 每次调用都会启动一个独立的 JS 运行环境执行开销比纯 Python 高。如果只是低频调用或学习验证问题不大如果要在批量任务里大量使用建议改用方式三。7.3 方式三本地 Node 签名服务复杂加密场景下更稳的做法是把签名逻辑封装成一个本地 Node 服务Python 只发请求到本地服务获取签名。本地服务示例代码const express require(express); const crypto require(crypto); const app express(); const SECRET_KEY mySecretKey; app.get(/sign, (req, res) { const keyword req.query.keyword || ; const page req.query.page || 1; const ts req.query.ts || String(Math.floor(Date.now() / 1000)); const raw keyword page ts SECRET_KEY; const sign crypto.createHash(md5).update(raw).digest(hex); res.json({ sign }); }); app.listen(3000, () { console.log(sign service running at http://127.0.0.1:3000); });Python 端调用import requests sign_resp requests.get( http://127.0.0.1:3000/sign, params{keyword: python, page: 1}, timeout5, ) sign sign_resp.json()[sign] print(sign)这个方案的好处是独立进程管理签名逻辑更新时只需要替换 Node
返回列表