十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python爬虫JS逆向实战:从加密参数定位到算法还原

Python爬虫JS逆向实战:从加密参数定位到算法还原 在爬虫开发中很多朋友卡住的不是 Python 语法也不是 requests 怎么用而是遇到带着加密参数、签名逻辑、动态 token 的接口就无从下手。尤其是登录密码被加密、请求头带上 sign、Cookie 里藏着指纹信息这类场景绕不开 JS 逆向。本文从我实际的项目经验出发完整拆解 Python 爬虫遇到 JS 加密时的思路和操作流程从抓包定位、断点调试、算法识别到 Python 侧还原覆盖 MD5、SHA、AES、RSA 等常用加密方式并整理一套可以直接上手的实战示例和排查清单。无论你是刚入门的新手还是想系统补齐 JS 逆向能力的开发者这篇内容都可以作为一份随时翻阅的参考。1. 背景与核心概念1.1 什么是 JS 逆向为什么爬虫会遇到它JS 逆向全称是 JavaScript 逆向工程。简单来说就是通过分析网页前端 JavaScript 代码的执行逻辑还原出某个接口参数的生成过程从而让 Python 脚本能够模拟浏览器完成同样的请求。为什么爬虫要掌握 JS 逆向因为现在绝大多数 Web 应用已经不再提供裸奔的接口了。服务端会要求请求必须携带一些“只有前端能生成”的参数例如登录接口的密码加密字段如password、encryptedPwd请求签名参数如sign、signature、token请求时间戳与随机数用于防重放浏览器指纹信息如fingerprint、deviceId请求头里的自定义字段如x-requested-with、authorization这些参数并不是服务器通过 Session 下发的而是前端通过 JavaScript 在浏览器环境里动态计算出来的。如果直接使用 requests 模拟请求缺少这些参数或者参数格式不对服务端就会返回错误。从专业角度定义JS 逆向是对前端 JavaScript 代码进行静态分析、动态调试、运行环境模拟最终提取出关键算法流程的过程。它的目标不是破解整个系统而是还原出爬虫请求所需要的“参数生成链路”。1.2 常见应用场景与学习边界JS 逆向的应用场景主要集中在以下几类场景说明典型目标登录密码加密登录表单中的密码在提交前被 JS 加密RSA、AES、MD5接口签名防护请求参数按规则拼接后生成 signMD5、SHA、HMAC数据加密传输响应内容被加密需要先解密再解析AES、Base64请求头动态字段每次请求生成 temporay token时间戳 哈希前端行为采集鼠标轨迹、事件序列被采集自研算法需要注意的是本文所讲解的 JS 逆向技术只应用于你拥有合法授权、明确允许抓取或已经获得许可的网站以及用于学习研究和技术验证的场景。任何绕过付费、破解鉴权、爬取非公开数据的行为都可能违反相关法律法规和平台服务条款。技术本身是中性的使用边界取决于每个人自己的选择。1.3 两种常见误解误解一JS 逆向等于破解密码。实际上绝大多数 JS 逆向处理的是前端加密算法比如把用户输入的密码用 RSA 公钥加密后再传输。RSA 公钥本来就是公开的前端加密严格来说是“混淆”而不是真正的“密码破解”。逆向的难点在于从压缩混淆的代码里找到加密算法、密钥和加密流程。误解二只要复制 JS 文件到 Python 里执行就能解决。这种思路在某些场景下可行但不推荐作为唯一方案。因为很多网站检测执行环境没有正常的window、document、navigator等浏览器对象时JS 代码会直接报错。更好的方式是把加密逻辑转换成 Python 实现或者用 Node.js 搭建一个最小执行环境。2. 环境准备与版本说明在开始写代码之前先把环境准备齐全。JS 逆向本身不是某一个语言的特权但既然是围绕 Python 爬虫来讲解我们需要准备 Python 环境、Node.js 环境和调试工具。2.1 开发环境清单工具作用说明Python 3.8编写爬虫和加密还原代码建议使用 3.9 或 3.10 以上版本Node.js 16运行和调试 JS 代码用于单独验证前端算法浏览器Chrome/Edge抓包与断点调试使用开发者工具 F12Fiddler/Charles移动端抓包处理 App 请求时使用本文以浏览器为主pycharm 或 vscode编写 Python 代码按个人习惯选择即可版本说明不同网站的加密库版本差异很大本文示例使用通用版本的库进行演示。你需要根据自己实际遇到的目标站点调整依赖版本重点是理解思路而不是照搬某一套版本组合。2.2 Python 依赖安装下面列出本文会用到的 Python 库。如果已经有部分库可以跳过对应安装命令。pip install requests pycryptodome rsa python-dotenv pandas说明requestsHTTP 请求库。pycryptodome提供 AES、DES、MD5 等对称加密和哈希算法注意安装后导入名是Crypto。rsaRSA 公钥加密、私钥解密的 Python 实现。python-dotenv用于管理密钥等敏感配置不推荐把密钥写死在代码中。2.3 Node.js 环境说明JS 逆向中很多场景需要直接运行一段前端代码来验证算法的正确性。Node.js 是最方便的工具。安装完成后可以在命令行中运行node -v检查版本。如果需要安装第三方 npm 包例如在 Node 中模拟浏览器环境的jsdom可以使用 npm init 初始化项目后安装。npm init -y npm install jsdom crypto-js说明crypto-jsJavaScript 的加密算法库常见前端加密都基于它。jsdom在 Node 中模拟浏览器的window、document等对象用于补环境。2.4 学习路径建议如果你是完全零基础建议先按以下顺序学习再做实战熟悉 Chrome 开发者工具中的 Network 面板和 Sources 面板。熟悉 JavaScript 基础语法变量、函数、对象、原型、闭包。熟悉 Python 中哈希、对称加密、非对称加密的基本调用方式。最后再进入断点调试和算法还原阶段。3. 前端加密场景与定位思路3.1 如何确定请求中哪些参数是加密的拿到一个接口后首先需要通过抓包观察请求参数。打开 Chrome 开发者工具切换到 Network 面板刷新页面或点击按钮触发目标请求找到对应的 XHR 请求。在 Payload 或 Headers 里观察参数常见可疑字段有长度明显异常的长字符串通常是 Base64 或十六进制哈希值每次请求都会变化的字符串一般是时间戳、随机数或两者组合后的加密结果与明文参数存在映射关系例如明文中有一个password123456加密后变成passwordxxxxx拿登录接口为例原始请求参数可能是{ username: test_user, password: a1b2c3d4e5f6... }这里的password显然不是明文而是一个 32 位或 64 位的字符串这时候就要往前找加密入口。3.2 快速定位加密函数的位置定位加密函数最常用的方法有三种。方法一全局搜索关键字在开发者工具中按CtrlShiftF打开全局搜索框搜索password、encrypt、sign、md5、sha256、AES、RSA等关键字。前端代码压缩后仍然会保留这些字符串通常在Object.defineProperty或函数名中可以看到。方法二XHR 断点在 Sources 面板右侧找到 XHR/fetch Breakpoints然后点击 号添加目标接口的 URL 关键字。当浏览器发起匹配该 URL 的请求时会自动停在发送请求的那一行 JS 代码上。此时可以顺着调用栈向上回溯找到参数构造的位置。方法三事件断点如果你是通过点击按钮触发的请求可以在 Event Listener Breakpoints 中勾选 click 事件。点击页面按钮后代码会停在事件处理函数中再逐步找到请求参数的处理逻辑。3.3 断点调试的基本思路以 Chrome 开发者工具为例在调试点位置可以看到Scope当前作用域内的所有变量。Call Stack函数调用栈可以跳回上一层调用处。Watch监听表达式的值。进入断点后我们通常关注以下内容当前函数入参是什么。返回值被赋值给了哪个变量。变量在后续请求中如何使用。算法依赖了哪些全局对象如window、document、navigator。举个例子当你定位到某一行代码是var pwd encryptedPassword(username, password);那么在 Console 中直接执行encryptedPassword函数、查看其内部实现就能还原出加密逻辑。如果执行后报ReferenceError说明该函数不在当前作用域需要进入闭包或从压缩代码中寻找。4. 核心加密算法解析与 Python 实现在实际逆向的过程中绝大多数场景都逃不开以下算法MD5/SHA 系列、Base64、AES、DES、RSA。理解这些算法在 Python 中如何实现遇到前端算法时就能快速对应上。4.1 MD5 与 SHA 系列MD5 和 SHA 系列属于哈希算法典型特点是输出固定长度。MD5 输出 32 位十六进制字符串SHA-1 输出 40 位SHA-256 输出 64 位。前端代码示例JavaScriptconst crypto require(crypto); function md5Hash(str) { return crypto.createHash(md5).update(str, utf8).digest(hex); } function sha256Hash(str) { return crypto.createHash(sha256).update(str, utf8).digest(hex); } console.log(md5Hash(123456)); console.log(sha256Hash(123456));Python 对应实现import hashlib def md5_hash(text: str) - str: md5 hashlib.md5() md5.update(text.encode(utf-8)) return md5.hexdigest() def sha256_hash(text: str) - str: sha256 hashlib.sha256() sha256.update(text.encode(utf-8)) return sha256.hexdigest() if __name__ __main__: print(md5_hash(123456)) print(sha256_hash(123456))在逆向时要注意MD5 经常不是单纯对原始字符串计算而是对拼接后的字符串计算比如sign md5(apiPath timestamp secretKey)所以遇到 MD5 时要找到字符串的拼接顺序。拼接顺序不同最终签名结果完全不同。4.2 Base64 编码Base64 不是加密算法而是一种编码方式但它在加密流程中无处不在。加密后的二进制数据通常会通过 Base64 编码成可打印字符串。前端代码示例const encoded btoa(hello world); console.log(encoded);Python 对应实现import base64 text hello world encoded base64.b64encode(text.encode(utf-8)) print(encoded.decode(utf-8))注意前端btoa只支持 Latin1 字符如果内容包含中文通常会先对 Unicode 编码比如encodeURIComponent再执行btoa。Python 侧对应的是先quote再编码。这是中文参数最常见的坑。4.3 AES 对称加密AES 是目前最常见的对称加密算法。它需要统一的密钥 Key部分模式下还需要偏移量 IV加密模式以 CBC 和 ECB 最多。前端常见写法基于 crypto-jsconst CryptoJS require(crypto-js); const key CryptoJS.enc.Utf8.parse(1234567890123456); const iv CryptoJS.enc.Utf8.parse(1234567890123456); function encryptByAES(text) { const encrypted CryptoJS.AES.encrypt(text, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); } console.log(encryptByAES(hello world));Python 对应实现from Crypto.Cipher import AES from Crypto.Util.Padding import pad import base64 key b1234567890123456 iv b1234567890123456 def encrypt_by_aes(text: str) - str: text_bytes text.encode(utf-8) padded_data pad(text_bytes, AES.block_size) cipher AES.new(key, AES.MODE_CBC, iv) encrypted cipher.encrypt(padded_data) return base64.b64encode(encrypted).decode(utf-8) if __name__ __main__: print(encrypt_by_aes(hello world))关键点说明参数说明Key密钥16/24/32 字节对应 AES-128/192/256IV偏移量CBC 模式必须设置ECB 模式不需要Padding填充方式前端常用 Pkcs7Python 用 pad 默认 Pkcs7输出格式前端默认输出 Base64 字符串Python 侧也要转 Base64如果 Python 加密结果和前端不一致优先检查 Key 和 IV 是字符串还是字节以及填充方式是否一致。4.4 RSA 非对称加密RSA 在登录接口中非常常见。前端使用公钥加密密码服务端使用私钥解密。由于公钥是公开的逆向 RSA 加密的核心就是找到公钥和加密模式。前端常见写法基于 JSEncryptconst JSEncrypt require(node-jsencrypt); const publicKey -----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----; function encryptByRSA(text) { const encryptor new JSEncrypt(); encryptor.setPublicKey(publicKey); return encryptor.encrypt(text); } console.log(encryptByRSA(123456));Python 对应实现import rsa public_key_pem -----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY----- def encrypt_by_rsa(text: str) - str: public_key rsa.PublicKey.load_pkcs1_openssl_pem(public_key_pem.encode(utf-8)) encrypted rsa.encrypt(text.encode(utf-8), public_key) return encrypted.hex()RSA 环境差异较大的点在于Python 的rsa库load_pkcs1_openssl_pem对应BEGIN PUBLIC KEY公钥。有些网站公钥是BEGIN RSA PUBLIC KEY对应 DER 格式读取方式不同。前端加密结果通常是 Base64Python 侧可能需要编码成 Base64 而不是 hex。JavaScript 的 JSEncrypt 默认使用 PKCS1 v1.5 填充Pythonrsa默认也是但如果网站使用了 OAEP需要改用rsa.pkcs1_oaep模块。4.5 HMAC 签名HMAC 是带有密钥的哈希算法常见于 API 签名场景。它与普通 MD5 的区别是需要一个 secret 密钥参与计算。前端常见写法const crypto require(crypto); function hmacSha256(str, secret) { return crypto.createHmac(sha256, secret).update(str).digest(hex); } console.log(hmacSha256(hello world, secret_key));Python 对应实现import hmac import hashlib def hmac_sha256(text: str, secret: str) - str: return hmac.new(secret.encode(utf-8), text.encode(utf-8), hashlib.sha256).hexdigest() if __name__ __main__: print(hmac_sha256(hello world, secret_key))HMAC 常见变形是加盐例如hmac(secret, timestamp token body)。遇到这类签名时注意收集参与签名的字段有哪些字段顺序是什么。5. 实战案例登录密码加密与接口签名还原这一节用一个综合示例把定位、分析、还原、编码的完整流程走一遍。示例站点是虚构场景但流程适用于大多数携带加密参数的网站。5.1 案例需求分析假设目标网站的登录接口为POST https://api.example.com/login请求参数如下{ username: test_user, password: U2FsdGVkX1iVwY6kPqZ7G9Q8a06x4y..., timestamp: 1735286400, sign: f81d4fae7dec793d2b2597829c4b9efc }观察发现password是 Base64 字符串格式类似 AES 加密后的输出。timestamp是标准 Unix 时间戳。sign是 32 位十六进制字符串像是 MD5。5.2 抓包定位加密入口在 Chrome 中打开该网站进入登录页面。打开开发者工具 Network 面板点击登录按钮找到 login 请求。然后切到 Sources 面板添加 XHR 断点断点条件填写login。刷新页面后再次点击登录按钮代码停在发送请求的位置。在调用栈中向上查找找到类似getParams或buildRequestData的函数。进入该函数后在 Console 中观察变量的值。最终可以定位到以下几段逻辑const encryptedPwd CryptoJS.AES.encrypt(password, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }).toString(); const params { username: username, password: encryptedPwd, timestamp: Date.now() / 1000, sign: md5(username timestamp secret_key) };这就解释了三个问题password由 AES 加密生成。timestamp是当前时间戳。sign是对username timestamp secret_key做 MD5。5.3 还原密钥与算法在浏览器断点处把变量key和iv的值打印出来并在页面源码中搜索secret_key。假设得到以下结果key:a3f1d5c2e4b6a7d8iv:b2e4f6a8c1d3e5f7secret_key:example_secret_2024如果密钥被混淆比如来自某个getRandomKey()函数就需要继续向上追踪该函数如果密钥是写死的字符串那么直接记录即可。5.4 编写 Python 模拟代码根据上述分析编写完整的 Python 请求脚本。import hashlib import time import requests import base64 from Crypto.Cipher import AES from Crypto.Util.Padding import pad KEY ba3f1d5c2e4b6a7d8 IV bb2e4f6a8c1d3e5f7 SECRET_KEY example_secret_2024 def aes_encrypt(text: str) - str: cipher AES.new(KEY, AES.MODE_CBC, IV) padded pad(text.encode(utf-8), AES.block_size) encrypted cipher.encrypt(padded) return base64.b64encode(encrypted).decode(utf-8) def generate_sign(username: str, timestamp: str) - str: raw f{username}{timestamp}{SECRET_KEY} return hashlib.md5(raw.encode(utf-8)).hexdigest() def build_login_params(username: str, password: str) - dict: timestamp str(int(time.time())) params { username: username, password: aes_encrypt(password), timestamp: timestamp, sign: generate_sign(username, timestamp) } return params def login(): url https://api.example.com/login params build_login_params(test_user, 123456) headers { Content-Type: application/json, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.post(url, jsonparams, headersheaders, timeout10) print(请求参数:, params) print(响应状态码:, resp.status_code) print(响应内容:, resp.text) if __name__ __main__: login()5.5 验证与调整运行脚本后需要注意以下情况如果响应返回 “sign error”优先检查时间戳是否与服务端误差过大或者SECRET_KEY是否正确。如果响应返回 “password invalid”优先检查 AES 加密结果是否与浏览器中一致。可以先把浏览器里的密文和 Python 生成的密文对比。如果响应返回客户端版本过低可能需要额外携带请求头或 Cookie。将脚本输出的参数与浏览器 Network 面板里的请求参数逐项对比是排查问题最有效的方式。6. 进阶复杂场景与补环境思路6.1 自研 JS 算法与代码混淆当网站不使用标准加密库而是自己实现了一套加密逻辑或者对代码做了混淆压缩直接还原算法就会变得困难。此时有两种方案。方案一纯 Python 重写。这适用于算法简单的情况但遇到复杂的位运算、非标准 Base64 表、自定义编码时重写成本很高。方案二使用 Python 调用 Node.js 执行原版 JS 代码。在 Python 中可以通过subprocess调用 Node也可以使用PyExecJS或js2py等库。但需要注意大多数混淆代码依赖浏览器环境直接执行会报window is not defined。对于依赖浏览器环境的情况需要补环境。所谓补环境就是在 Node.js 中伪造出浏览器才有的对象比如global.window global; global.document { cookie: , getElementById: function() {}, addEventListener: function() {} }; global.navigator { userAgent: Mozilla/5.0, platform: Win32 };补环境的目的是让混淆代码认为自己在浏览器中运行从而不报错。但这并不是一个轻松的过程需要根据实际报错逐步补充缺失的对象和方法。初学者建议先学习 JavaScript 原型链、闭包和浏览器 API 基础再着手补环境。6.2 JavaScript 动态生成字符串一些网站会在运行时动态生成密钥密钥不直接出现在 JS 文件中而是在请求过程中通过window.atob解码、字符串拼接、数组索引等方式构造。例如var key [97, 51, 102, 49, 100, 53].map(function(x) { return String.fromCharCode(x); }).join();这种情况下在断点中观察key的最终值就行不需要逐行分析生成过程。6.3 响应体加密除了请求参数加密部分网站会对响应体加密。此时服务端返回的是密文前端通过 JS 解密后再渲染页面。Python 爬虫同样需要实现解密逻辑。处理思路与请求参数加密一致定位前端解密函数还原算法后在 Python 中解密。如果响应体是 JSON 结构可能是{ code: 0, data: U2FsdGVkX1... }data字段解密后才是真正的内容。对于 AES 解密Python 实现如下from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 def aes_decrypt(cipher_text: str) - str: encrypted base64.b64decode(cipher_text) cipher AES.new(KEY, AES.MODE_CBC, IV) decrypted cipher.decrypt(encrypted) return unpad(decrypted, AES.block_size).decode(utf-8)6.4 WebAssembly 与原生 Hook少数反爬机制会把核心算法编译成 WebAssembly或者通过浏览器插件注入原生函数。这类场景难度较高本文不再展开。但可以明确一个方向优先观察接口返回的字段中是否包含可用于调试的信息或者通过 Hook 调用点来捕获入参和返回值。Hook 是一种常用的调试手段即在运行时替换某个函数打印函数的调用信息再返回原始结果。例如在 Console 中执行const originalAtob window.atob; window.atob function(str) { console.log(atob called with:, str); return originalAtob(str); };这样就能在后续请求中捕获所有 Base64 解码操作从而快速定位加密链路。Hook 也可以在 Python 的 Selenium 或 Playwright 环境中注入用于辅助分析。7. 常见问题与排查思路JS 逆向过程中会遇到大量报错和异常。这里整理一份高频问题清单方便在实战时快速定位。问题现象常见原因解决思路Python 调用 JS 报window is not definedJS 代码依赖浏览器环境在 Node 中补环境或改用 Python 重写算法AES 加密结果与前端不一致Key、IV 编码方式不同确认 Key 和 IV 是 UTF-8 还是 Base64 解码后的字节动态密钥抓不到密钥在请求过程中动态生成使用 XHR 断点和函数调用栈回溯密钥生成位置sign 经常失效时间戳或随机数未同步检查 timestamp 是否为服务端当前时间不能使用缓存值接口返回 403 Forbidden缺少必要的请求头或 Cookie对比浏览器请求头补齐 User-Agent、Referer、Cookie翻页后参数不同分页参数参与了签名检查页码字段是否被加密或拼接进签名代码被混淆难以阅读前端做了 obfuscator 混淆先搜索接口字段关键字再配合断点动态观察补环境后仍然报错缺少某个浏览器对象的方法根据报错信息逐个补充对象方法或原型方法请求频率过高被封 IP未做访问控制添加随机延时、限速和失败重试机制反爬升级算法变更网站更新了加密方案建立算法特征的回归测试维护多套适配逻辑7.1 定位问题的基础流程遇到问题时建议按以下顺序排查先确认抓包中的请求参数与 Python 脚本中的请求参数是否完全一致。再确认参数是否参与签名如果参与签名计算使用的字段顺序是否正确。确认请求头是否完整尤其是 Content-Type、User-Agent、Referer。确认 Cookie 是否需要从浏览器中复制或需要先请求某个接口获取 Cookie。最后再检查加密算法本身。很多时候加密算法没有问题反而是请求头或 Cookie 不完整导致请求被拒绝。7.2 实战排错示例假设你在模拟某接口时返回{ code: 400, message: invalid sign }此时可以按如下步骤操作在浏览器中重新点击一次请求复制当前请求参数。用 Python 输出自己的请求参数逐字段对比。重点检查时间戳是否一致或者服务端是否要求毫秒级时间戳。检查签名原文的拼接顺序例如是param1 param2还是param2 param1。检查编码方式例如是否需要URLEncoder编码后再拼接。8. 最佳实践与工程建议8.1 接口分析阶段不要一上来就直接写 Python 代码先建立一份接口文档记录以下信息请求 URL、方法、请求头。每个请求字段的含义、是否参与签名、是否加密。加密算法、密钥来源、密钥类型。响应结构、错误码含义。把这些信息整理成表格或文档可以避免后续反复抓包对比。8.2 代码结构分层建议把爬虫代码按职责拆分成多层project/ ├── api/ │ ├── client.py # 请求客户端 │ └── endpoints.py # 接口地址与参数配置 ├── crypto/ │ ├── aes_util.py # AES 封装 │ ├── rsa_util.py # RSA 封装 │ ├── hash_util.py # MD5/SHA/HMAC 封装 │ └── signer.py # 签名逻辑 ├── parser/ │ └── response_parser.py # 响应解析 ├── storage/ │ └── db_handler.py # 数据存储 ├── config.py # 配置文件 └── main.py # 主入口这样拆分的优点是当某个网站的加密算法升级时只需要修改crypto目录下的对应模块而不会影响爬虫主体代码。8.3 密钥与敏感信息管理在真实项目中密钥不应该硬编码在 Python 文件中。建议使用环境变量或.env文件管理。示例.env文件API_BASE_URLhttps://api.example.com AES_KEYa3f1d5c2e4b6a7d8 AES_IVb2e4f6a8c1d3e5f7 SECRET_KEYexample_secret_2024Python 中读取import os from dotenv import load_dotenv load_dotenv() AES_KEY os.getenv(AES_KEY).encode(utf-8) AES_IV os.getenv(AES_IV).encode(utf-8) SECRET_KEY os.getenv(SECRET_KEY)注意不要把包含密钥的.env文件提交到 Git 仓库中要加入.gitignore。8.4 请求频率与异常处理爬虫的稳定性比速度更重要。推荐的请求策略单次请求之间增加随机延时例如time.sleep(random.uniform(1, 3))。添加指数退避重试机制遇到网络错误或 5xx 状态码时重试。限制单 IP 的请求速率避免触发服务端限流。对每个请求建立超时时间避免线程阻塞。一个简单的重试例子import time import random import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session requests.Session() retry Retry(total3, backoff_factor0.5, status_forcelist[500, 502, 503]) adapter HTTPAdapter(max_retriesretry) session.mount(https://, adapter) def safe_request(url, **kwargs): time.sleep(random.uniform(1, 3)) resp session.get(url, timeout10, **kwargs) resp.raise_for_status() return resp8.5 数据存储的去重与增量爬虫拿到数据后在入库前要做去重处理。常见方式使用业务唯一标识建立唯一索引。使用数据库主键冲突时执行更新或忽略。对抓取任务的起始 ID 或时间范围做增量记录。以 MySQL 为例可以使用INSERT IGNORE或ON DUPLICATE KEY UPDATE实现去重。若使用内存或文件存储可以维护一个已完成任务的集合。8.6 日志记录完整的日志能大大提升排查问题的效率。建议记录以下信息每次请求的 URL、参数、响应状态码。加密参数生成耗时。解析成功与失败的数据条数。异常堆栈。使用 Python 内置logging即可不需要额外引入复杂框架。8.7 合规与稳定性在工程层面最值得长期投入的不是造轮子逆向一套算法而是设计出稳定、可维护、可监控的采集系统。以下是几条核心原则优先使用官方 API 或公开数据源JS 逆向是最后选项。抓取前查看网站的 robots 协议和用户条款。请求频率设置合理不要对目标服务器造成压力。对自己抓取的数据负责不用于非法或黑灰产场景。定期检查现有逻辑是否失效建立告警机制。9. 总结与学习路线JS 逆向并不是一条“背代码”就能走通的路它更多是前端基础、算法基础、调试技巧和 Python 工程能力的综合运用。9.1 本文核心内容回顾通过本文的整理你应该已经掌握了如何判断接口中哪些参数是加密参数。如何使用浏览器开发者工具定位加密入口。如何识别 MD5、SHA、AES、RSA、HMAC 等常见算法。如何在 Python 中还原这些算法。如何排查 sign error、参数不一致、响应加密等问题。如何设计一个结构清晰、可持续维护的爬虫工程。9.2 下一步学习建议如果你在本篇文章基础上继续深入推荐按以下顺序进阶JavaScript 进阶原型链、闭包、作用域、异步编程。代码混淆与反混淆obfuscator 处理、变量名还原、控制流扁平化。浏览器调试进阶本地覆盖、条件断点、Hook 调试。移动端抓包Fiddler、Charles、抓包 HTTPS 证书配置。验证码处理滑块验证、点选验证、行为轨迹模拟。分布式采集任务队列、代理池、爬虫调度框架。9.3 给新手的鼓励很多人初学 JS 逆向时都会经历“浏览器断点看不懂、Python 还原不对、接口怎么调都失败”的阶段。这是非常正常的。我的建议是不要一次追求还原最复杂的站点先从登录接口的 MD5 加密入手再尝试 AES接着做一次带 sign 的接口。每完成一个案例就把代码、笔记和踩坑记录整理到自己的文档库中。随着案例数量的积累你会逐渐发现自己对请求、参数、算法、环境这几个维度的理解都会上升一个台阶到那时再回头看最初的难点会很自然地有一种“原来如此”的感觉。如果这篇文章对你有帮助建议收藏备用遇到具体问题时能随时查阅。下一篇文章我可以继续拆解更复杂的动态密钥场景和补环境实战欢迎持续关注。
返回列表