1. 从“请求失败”到“参数逆向”一个爬虫工程师的日常今天想聊一个几乎所有爬虫工程师都会遇到的经典场景你兴致勃勃地打开一个网站打开开发者工具准备像往常一样复制请求头、提取接口URL却发现数据根本不在你熟悉的XHR或Fetch标签页里。页面内容明明加载了但Network里就是找不到那个返回核心数据的请求。或者你找到了请求但里面有几个关键的参数比如sign、token、_signature看起来像是一串毫无规律的乱码直接复制下来用一次就失效。恭喜你你遇到了前端JavaScript简称JS加密也就是我们常说的需要“JS逆向”的网站。“黑猫投诉平台”就是一个非常典型的、用于练习JS逆向的“新手村”级目标。它不像一些大型电商或社交平台那样有极其复杂的混淆和反调试机制但其核心数据接口恰恰使用了需要逆向才能获取的动态参数。这正好符合我们“入门”的定位——既有挑战性又不至于让人一开始就望而却步。通过这个案例你将不再对Network里那些看不懂的参数感到恐惧而是能清晰地理解它们从何而来又如何用代码去复现这个生成逻辑。这个过程就是JS逆向的核心让机器重新学会人类浏览器执行JS代码的过程。2. 逆向目标分析与抓包初探在动手写一行代码之前细致的观察和分析能省去后面几个小时甚至几天的折腾。我们的目标是爬取黑猫投诉平台上的投诉列表数据。首先我们手动打开网站进入投诉列表页。2.1 核心数据接口定位打开浏览器的开发者工具F12切换到Network网络面板然后刷新页面或点击翻页。此时你会看到瀑布般流下的各种请求document、stylesheet、script、image、xhr、fetch等等。我们的目标是找到那个返回了结构化投诉列表数据的请求通常是XHR或Fetch类型。经过筛选和查看响应预览我们很容易找到一个类似https://tousu.sina.com.cn/api/index/...的接口。它的响应体是JSON格式里面包含了data、list等字段正是我们需要的投诉标题、内容、时间、状态等信息。成功了一半但别急直接复制这个请求的cURL命令到Python里重放你很可能会得到一个错误响应比如{code: 400, msg: 参数错误}。2.2 可疑参数识别与初步判断为什么直接重放不行我们需要仔细检查这个成功请求的细节尤其是Headers和Payload。查看请求头Headers重点关注Cookie、User-Agent、Referer等。对于黑猫平台Cookie通常是必需的它代表了你的会话状态。但Cookie可以通过模拟登录或使用session维持来获取这不是本次逆向的重点。查看请求参数Payload/Query String这是关键所在。在Payload标签页如果是POST请求或URL的查询字符串中GET请求我们需要寻找那些“看起来不对劲”的参数。时间戳参数如t、_、ts其值通常是一串13位数字Unix时间戳毫秒级。这种参数很容易用Python的time.time() * 1000生成一般不是加密重点。加密参数如sign、token、key、_signature。它们的值通常是一长串由字母和数字组成的字符串可能是hex或base64编码看起来没有明显规律。这个sign或类似名称参数往往就是服务器用来验证请求是否由合法前端生成的核心凭证。在黑猫投诉平台的案例中我们通常能找到一个名为sign的参数。它的值类似于a1b2c3d4e5f6...。服务器端会有一套相同的算法根据请求的其他参数有时还包括一个固定的密钥或salt来计算这个sign值。如果客户端计算出的sign与服务器计算的不匹配请求就会被拒绝。我们的任务就是找到浏览器中计算这个sign值的JavaScript代码并用Python将其还原。注意网站的前端代码可能会更新加密参数的名字和位置在headers里还是query里也可能变化。但逆向的思路是通用的找到那个随请求变化且无法直接预测的参数然后去追踪它的生成源头。3. 逆向核心定位与解析签名函数找到了可疑参数sign接下来就是“狩猎”它的生成地。这个过程像是在庞大的JS代码森林里寻找一把特定的钥匙。3.1 搜索与断点两种核心定位方法全局搜索法在开发者工具的Sources源代码面板按CtrlShiftFWindows/Linux或CmdOptFMac打开全局搜索。直接搜索参数名比如sign或sign:。你可能会得到几十甚至上百个结果。需要结合上下文筛选优先查看在ajax请求、fetch请求或明显是参数赋值如params.sign ...附近的代码。如果sign值是一个固定前缀加变量比如sign: abc_ signValue那么搜索abc_可能更直接。XHR/Fetch断点法推荐这是更精准的方法。在Sources面板找到右侧的XHR/Fetch Breakpoints。点击号输入包含核心接口URL部分的关键字例如index因为我们的接口URL里有index。设置好后任何包含该关键词的XHR或Fetch请求发起前代码执行都会自动暂停。此时查看右侧的Call Stack调用堆栈从下往上点击逐个查看每个栈帧对应的代码你就能一步步回溯到最初发起请求、并添加sign参数的地方。这通常是最快找到加密函数入口的途径。3.2 黑猫平台sign参数逆向实例以一次典型的分析为例通过断点或搜索我们最终可能会定位到一段类似下面的JS代码代码已做简化示意function getSign(params) { var k Object.keys(params).sort(); // 1. 对参数名按字母排序 var s []; for (var i 0; i k.length; i) { var key k[i]; var value params[key]; if (value ! null value ! ) { s.push(key value); // 2. 拼接成 k1v1k2v2 格式 } } var str s.join(); str str key固定的密钥字符串; // 3. 拼接一个固定的密钥 var md5Sign hex_md5(str); // 4. 对拼接后的字符串进行MD5加密 return md5Sign.toUpperCase(); // 5. 将MD5结果转为大写 } // 在发起请求的地方 var requestParams { page: 1, size: 20, t: Date.now() }; requestParams.sign getSign(requestParams); // 调用函数生成sign逆向逻辑拆解参数排序将所有待传参数如page,size,t的键key按字母顺序排序。这是为了防止参数顺序不同导致sign不同确保服务器和客户端计算一致。字符串拼接将排序后的参数拼接成标准查询字符串格式key1value1key2value2。添加盐值Salt在拼接好的字符串末尾加上一个固定的字符串常被称为key或salt格式是keyxxxxxx。这个key是逆向的关键它通常硬编码在JS文件里。哈希计算将最终拼接好的字符串使用MD5算法也可能是SHA1、SHA256等这里hex_md5是常见的MD5函数计算其哈希值得到一个32位的16进制字符串。格式化输出将得到的MD5值转换为大写.toUpperCase()作为最终的sign参数。实操心得找到hex_md5这样的函数名是好事但你需要确认它是不是浏览器环境自带的通常不是或者是一个被定义好的工具函数。你可以继续搜索hex_md5的定义或者更简单在开发者工具的Console控制台里直接尝试调用hex_md5(test)看是否能返回一个标准的MD5值。如果能说明这个函数在当前页面全局可用我们后续在Python中只需要实现同样的MD5逻辑即可。4. Python复现从JS逻辑到可执行代码理解了算法用Python复现就相对直接了。核心是保证每一步的逻辑与JS代码完全一致。4.1 环境准备与依赖安装我们需要Python的hashlib库进行MD5计算以及requests库来发送HTTP请求。这些都是基础库。# 通常不需要额外安装hashlib它是标准库。 # 只需要安装requests pip install requests4.2 签名生成函数的Python实现根据上面分析出的JS逻辑我们编写对应的Python函数import hashlib import time def generate_sign(params, salt_key这里是逆向找到的固定key): 根据JS逆向逻辑生成sign参数 :param params: dict, 请求参数字典 :param salt_key: str, 从JS代码中找到的固定密钥 :return: str, 计算得到的sign值 # 1. 对参数键进行排序 sorted_keys sorted(params.keys()) # 2. 拼接键值对忽略值为None或空字符串的参数 param_list [] for key in sorted_keys: value params[key] if value is not None and str(value) ! : # 注意所有值都需要转换为字符串进行拼接 param_list.append(f{key}{value}) # 3. 用连接并拼接盐值 param_str .join(param_list) sign_str param_str key salt_key # 拼接格式必须与JS完全一致 # 4. 计算MD5哈希并转为大写 m hashlib.md5() m.update(sign_str.encode(utf-8)) # 必须指定编码通常为utf-8 md5_hex m.hexdigest().upper() # 转为大写 return md5_hex # 示例用法 if __name__ __main__: # 模拟请求参数 request_params { page: 1, size: 20, t: int(time.time() * 1000) # 13位时间戳 } # 假设我们从JS中逆向得到的key是 固定的密钥字符串 secret_key 固定的密钥字符串 sign_value generate_sign(request_params, secret_key) print(f生成的sign: {sign_value}) request_params[sign] sign_value print(f完整的请求参数: {request_params})关键细节与避坑指南排序一致性sorted(params.keys())默认按字母顺序升序排序这与JS的Array.sort()对字符串数组的排序结果一致。值处理JS中value ! 的判断在Python中要注意value可能是整数需要先转为字符串再判断是否为空。使用str(value) ! 更安全。拼接格式key1value1key2value2keysecret这个格式必须一模一样包括的位置和key这个单词。有时JS代码里可能是 key secret本质相同。编码问题hashlib.md5()需要传入bytes类型。str.encode(utf-8)是最常见的编码方式必须与JS端保持一致现代Web前端默认也是UTF-8。如果JS端对字符串做了特殊编码处理罕见这里也需要对应处理。盐值Key的保密性这个salt_key是逆向的核心成果。但请注意网站可能会不定期更换这个Key或整个签名算法。因此你的爬虫需要具备一定的容错机制或者定期检查签名是否失效。4.3 构建完整请求与数据抓取有了签名生成函数我们就可以像构造普通请求一样来抓取数据了。import requests import json def fetch_complaints(page1, size20): base_url https://tousu.sina.com.cn/api/index/... secret_key 从JS代码中找到的固定密钥字符串 # 替换为真实的key # 1. 构造基础参数 params { page: page, size: size, t: int(time.time() * 1000), # 当前时间戳 # 可能还有其他固定参数需要从抓包中观察补充 # type: ..., } # 2. 生成签名并添加 params[sign] generate_sign(params, secret_key) # 3. 设置请求头从浏览器复制至少需要User-Agent和Content-Type headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., Referer: https://tousu.sina.com.cn/, Accept: application/json, text/plain, */*, # Cookie: ..., # 如果接口需要登录态则需要有效的Cookie } # 4. 发送请求 # 如果是GET请求参数在URL中 # response requests.get(base_url, paramsparams, headersheaders) # 如果是POST请求以form-data或x-www-form-urlencoded格式参数在data中 response requests.post(base_url, dataparams, headersheaders) # 5. 处理响应 if response.status_code 200: try: data response.json() if data.get(code) 200: # 根据接口实际的成功码判断 complaints data.get(data, {}).get(list, []) for comp in complaints: print(f标题: {comp.get(title)}, 时间: {comp.get(created_at)}) return complaints else: print(f接口返回错误: {data.get(msg)}) except json.JSONDecodeError: print(响应不是有效的JSON格式) print(response.text) else: print(f请求失败状态码: {response.status_code}) return [] # 抓取第一页 fetch_complaints(1, 20)5. 进阶挑战与调试技巧成功跑通基础流程只是开始。在实际逆向过程中你会遇到更复杂的情况。5.1 应对代码混淆与压缩生产环境的JS代码通常是压缩和混淆过的变量名变成a、b、c函数名变成_0x123abc逻辑被拆分打乱。这增加了阅读难度。使用Pretty Print在Sources面板找到混淆的JS文件点击底部{}图标Pretty print代码会变得有格式易于阅读。关注核心逻辑不要试图理解所有代码。聚焦于你找到的签名函数附近。即使变量名是a、b你也能通过赋值a ...、函数调用b md5(a)来推断其作用。利用Console调试在代码关键行设置断点然后在Console中打印当前变量的值观察其变化过程这是理解混淆代码最有效的方法。5.2 动态密钥与环境依赖有些网站的key不是硬编码而是由另一个接口动态返回的。或者签名算法依赖浏览器环境的某些特性如window对象、document属性。追踪key的来源如果key是变量在代码中搜索它被赋值的地方如var key ...看它是来自一个变量、一个函数返回值还是一个网络请求的响应。如果是网络请求你需要先模拟这个请求获取key。补环境如果JS代码运行时报错提示window is not defined或navigator is not defined说明它依赖浏览器环境。在Python中我们可以使用PyExecJS、js2py或更强大的Node.js环境来执行这段JS代码。但作为入门更推荐的方法是仔细分析算法看它是否真的需要这些环境变量。很多时候window.xxx只是用来获取一个固定值如window.navigator.userAgent你完全可以在Python中直接定义这个值并传入JS上下文或者更彻底地将依赖环境变量的部分逻辑用Python重写。5.3 使用PyExecJS执行复杂JS对于无法轻易用Python重写的复杂加密逻辑如包含大量位操作、自定义的加密函数可以使用PyExecJS来调用一个JS执行环境如Node.js直接运行原版JS代码。import execjs # 1. 读取包含加密函数的JS文件 with open(heimaosign.js, r, encodingutf-8) as f: js_code f.read() # 2. 创建JS上下文 ctx execjs.compile(js_code) # 3. 调用JS函数 params {page: 1, size: 20, t: 1648886400000} sign ctx.call(getSign, params) # 假设JS中函数名是getSign print(sign)注意事项PyExecJS需要系统安装有JS运行时如Node.js。确保你的JS代码是自包含的或者手动将依赖的函数/变量都提取到同一个字符串中。6. 工程化思考与反爬策略应对一个稳定的爬虫不能只满足于今天能跑通。6.1 签名失效与算法更新网站运维人员不是摆设他们也会更新反爬策略。监控与告警在你的爬虫脚本中加入健康检查。定期运行一个测试用例如果连续多次无法获取数据或sign验证失败则触发告警发邮件、发消息。快速定位当签名失效时第一时间回到浏览器重复第2、3步的抓包和逆向流程。对比新旧JS文件看是key变了拼接逻辑变了还是哈希算法变了比如从MD5换成了SHA256。通常变化不会天翻地覆。代码抽象将签名生成算法封装成独立的函数或类。当算法更新时你只需要修改这一个地方。6.2 请求频率与IP管理即使解决了签名过于频繁的请求也会导致IP被封。添加延迟在请求间使用time.sleep(random.uniform(1, 3))添加随机延迟模拟人类操作。使用代理IP池对于大规模抓取必须使用代理IP。可以使用付费代理服务或者自建代理池。requests库使用代理很简单proxies {http: http://ip:port, https: https://ip:port}。设置合理的请求头User-Agent、Referer、Accept-Language等头信息要模拟得真实可以准备一个列表随机选择。6.3 数据解析与存储拿到数据后的处理也同样重要。健壮的解析使用response.json()解析时用try...except包裹。访问字典数据时使用.get(key, default)方法避免KeyError。结构化存储根据数据量选择存储方式。少量数据可以用JSON或CSV文件。大量数据建议存入数据库如SQLite、MySQL、MongoDB。使用pandas库可以方便地进行数据处理和导出。增量抓取如果只抓取最新数据可以记录已抓取条目的ID或最大时间戳下次请求时作为参数传入避免重复。JS逆向就像一场与网站开发者的智力游戏。黑猫投诉平台提供了一个近乎完美的入门战场。它让你经历了完整的流程抓包定位、参数识别、代码搜索、逻辑分析、Python复现。这个过程中最重要的不是记住某个具体的key或算法而是掌握“观察-假设-验证”的方法论。下一次当你遇到一个带有sign、token的请求时你不会再感到无从下手而是会熟练地打开开发者工具沿着网络请求的调用栈一步步揭开前端加密的面纱。这才是爬虫工程师从“脚本小子”走向“解决方案专家”的关键一步。在实际项目中更复杂的混淆、动态执行、WebAssembly加密等挑战会接踵而至但底层思路是相通的——理解数据流动还原计算过程。
JS逆向入门:从黑猫投诉平台sign参数破解到Python爬虫实战
1. 从“请求失败”到“参数逆向”一个爬虫工程师的日常今天想聊一个几乎所有爬虫工程师都会遇到的经典场景你兴致勃勃地打开一个网站打开开发者工具准备像往常一样复制请求头、提取接口URL却发现数据根本不在你熟悉的XHR或Fetch标签页里。页面内容明明加载了但Network里就是找不到那个返回核心数据的请求。或者你找到了请求但里面有几个关键的参数比如sign、token、_signature看起来像是一串毫无规律的乱码直接复制下来用一次就失效。恭喜你你遇到了前端JavaScript简称JS加密也就是我们常说的需要“JS逆向”的网站。“黑猫投诉平台”就是一个非常典型的、用于练习JS逆向的“新手村”级目标。它不像一些大型电商或社交平台那样有极其复杂的混淆和反调试机制但其核心数据接口恰恰使用了需要逆向才能获取的动态参数。这正好符合我们“入门”的定位——既有挑战性又不至于让人一开始就望而却步。通过这个案例你将不再对Network里那些看不懂的参数感到恐惧而是能清晰地理解它们从何而来又如何用代码去复现这个生成逻辑。这个过程就是JS逆向的核心让机器重新学会人类浏览器执行JS代码的过程。2. 逆向目标分析与抓包初探在动手写一行代码之前细致的观察和分析能省去后面几个小时甚至几天的折腾。我们的目标是爬取黑猫投诉平台上的投诉列表数据。首先我们手动打开网站进入投诉列表页。2.1 核心数据接口定位打开浏览器的开发者工具F12切换到Network网络面板然后刷新页面或点击翻页。此时你会看到瀑布般流下的各种请求document、stylesheet、script、image、xhr、fetch等等。我们的目标是找到那个返回了结构化投诉列表数据的请求通常是XHR或Fetch类型。经过筛选和查看响应预览我们很容易找到一个类似https://tousu.sina.com.cn/api/index/...的接口。它的响应体是JSON格式里面包含了data、list等字段正是我们需要的投诉标题、内容、时间、状态等信息。成功了一半但别急直接复制这个请求的cURL命令到Python里重放你很可能会得到一个错误响应比如{code: 400, msg: 参数错误}。2.2 可疑参数识别与初步判断为什么直接重放不行我们需要仔细检查这个成功请求的细节尤其是Headers和Payload。查看请求头Headers重点关注Cookie、User-Agent、Referer等。对于黑猫平台Cookie通常是必需的它代表了你的会话状态。但Cookie可以通过模拟登录或使用session维持来获取这不是本次逆向的重点。查看请求参数Payload/Query String这是关键所在。在Payload标签页如果是POST请求或URL的查询字符串中GET请求我们需要寻找那些“看起来不对劲”的参数。时间戳参数如t、_、ts其值通常是一串13位数字Unix时间戳毫秒级。这种参数很容易用Python的time.time() * 1000生成一般不是加密重点。加密参数如sign、token、key、_signature。它们的值通常是一长串由字母和数字组成的字符串可能是hex或base64编码看起来没有明显规律。这个sign或类似名称参数往往就是服务器用来验证请求是否由合法前端生成的核心凭证。在黑猫投诉平台的案例中我们通常能找到一个名为sign的参数。它的值类似于a1b2c3d4e5f6...。服务器端会有一套相同的算法根据请求的其他参数有时还包括一个固定的密钥或salt来计算这个sign值。如果客户端计算出的sign与服务器计算的不匹配请求就会被拒绝。我们的任务就是找到浏览器中计算这个sign值的JavaScript代码并用Python将其还原。注意网站的前端代码可能会更新加密参数的名字和位置在headers里还是query里也可能变化。但逆向的思路是通用的找到那个随请求变化且无法直接预测的参数然后去追踪它的生成源头。3. 逆向核心定位与解析签名函数找到了可疑参数sign接下来就是“狩猎”它的生成地。这个过程像是在庞大的JS代码森林里寻找一把特定的钥匙。3.1 搜索与断点两种核心定位方法全局搜索法在开发者工具的Sources源代码面板按CtrlShiftFWindows/Linux或CmdOptFMac打开全局搜索。直接搜索参数名比如sign或sign:。你可能会得到几十甚至上百个结果。需要结合上下文筛选优先查看在ajax请求、fetch请求或明显是参数赋值如params.sign ...附近的代码。如果sign值是一个固定前缀加变量比如sign: abc_ signValue那么搜索abc_可能更直接。XHR/Fetch断点法推荐这是更精准的方法。在Sources面板找到右侧的XHR/Fetch Breakpoints。点击号输入包含核心接口URL部分的关键字例如index因为我们的接口URL里有index。设置好后任何包含该关键词的XHR或Fetch请求发起前代码执行都会自动暂停。此时查看右侧的Call Stack调用堆栈从下往上点击逐个查看每个栈帧对应的代码你就能一步步回溯到最初发起请求、并添加sign参数的地方。这通常是最快找到加密函数入口的途径。3.2 黑猫平台sign参数逆向实例以一次典型的分析为例通过断点或搜索我们最终可能会定位到一段类似下面的JS代码代码已做简化示意function getSign(params) { var k Object.keys(params).sort(); // 1. 对参数名按字母排序 var s []; for (var i 0; i k.length; i) { var key k[i]; var value params[key]; if (value ! null value ! ) { s.push(key value); // 2. 拼接成 k1v1k2v2 格式 } } var str s.join(); str str key固定的密钥字符串; // 3. 拼接一个固定的密钥 var md5Sign hex_md5(str); // 4. 对拼接后的字符串进行MD5加密 return md5Sign.toUpperCase(); // 5. 将MD5结果转为大写 } // 在发起请求的地方 var requestParams { page: 1, size: 20, t: Date.now() }; requestParams.sign getSign(requestParams); // 调用函数生成sign逆向逻辑拆解参数排序将所有待传参数如page,size,t的键key按字母顺序排序。这是为了防止参数顺序不同导致sign不同确保服务器和客户端计算一致。字符串拼接将排序后的参数拼接成标准查询字符串格式key1value1key2value2。添加盐值Salt在拼接好的字符串末尾加上一个固定的字符串常被称为key或salt格式是keyxxxxxx。这个key是逆向的关键它通常硬编码在JS文件里。哈希计算将最终拼接好的字符串使用MD5算法也可能是SHA1、SHA256等这里hex_md5是常见的MD5函数计算其哈希值得到一个32位的16进制字符串。格式化输出将得到的MD5值转换为大写.toUpperCase()作为最终的sign参数。实操心得找到hex_md5这样的函数名是好事但你需要确认它是不是浏览器环境自带的通常不是或者是一个被定义好的工具函数。你可以继续搜索hex_md5的定义或者更简单在开发者工具的Console控制台里直接尝试调用hex_md5(test)看是否能返回一个标准的MD5值。如果能说明这个函数在当前页面全局可用我们后续在Python中只需要实现同样的MD5逻辑即可。4. Python复现从JS逻辑到可执行代码理解了算法用Python复现就相对直接了。核心是保证每一步的逻辑与JS代码完全一致。4.1 环境准备与依赖安装我们需要Python的hashlib库进行MD5计算以及requests库来发送HTTP请求。这些都是基础库。# 通常不需要额外安装hashlib它是标准库。 # 只需要安装requests pip install requests4.2 签名生成函数的Python实现根据上面分析出的JS逻辑我们编写对应的Python函数import hashlib import time def generate_sign(params, salt_key这里是逆向找到的固定key): 根据JS逆向逻辑生成sign参数 :param params: dict, 请求参数字典 :param salt_key: str, 从JS代码中找到的固定密钥 :return: str, 计算得到的sign值 # 1. 对参数键进行排序 sorted_keys sorted(params.keys()) # 2. 拼接键值对忽略值为None或空字符串的参数 param_list [] for key in sorted_keys: value params[key] if value is not None and str(value) ! : # 注意所有值都需要转换为字符串进行拼接 param_list.append(f{key}{value}) # 3. 用连接并拼接盐值 param_str .join(param_list) sign_str param_str key salt_key # 拼接格式必须与JS完全一致 # 4. 计算MD5哈希并转为大写 m hashlib.md5() m.update(sign_str.encode(utf-8)) # 必须指定编码通常为utf-8 md5_hex m.hexdigest().upper() # 转为大写 return md5_hex # 示例用法 if __name__ __main__: # 模拟请求参数 request_params { page: 1, size: 20, t: int(time.time() * 1000) # 13位时间戳 } # 假设我们从JS中逆向得到的key是 固定的密钥字符串 secret_key 固定的密钥字符串 sign_value generate_sign(request_params, secret_key) print(f生成的sign: {sign_value}) request_params[sign] sign_value print(f完整的请求参数: {request_params})关键细节与避坑指南排序一致性sorted(params.keys())默认按字母顺序升序排序这与JS的Array.sort()对字符串数组的排序结果一致。值处理JS中value ! 的判断在Python中要注意value可能是整数需要先转为字符串再判断是否为空。使用str(value) ! 更安全。拼接格式key1value1key2value2keysecret这个格式必须一模一样包括的位置和key这个单词。有时JS代码里可能是 key secret本质相同。编码问题hashlib.md5()需要传入bytes类型。str.encode(utf-8)是最常见的编码方式必须与JS端保持一致现代Web前端默认也是UTF-8。如果JS端对字符串做了特殊编码处理罕见这里也需要对应处理。盐值Key的保密性这个salt_key是逆向的核心成果。但请注意网站可能会不定期更换这个Key或整个签名算法。因此你的爬虫需要具备一定的容错机制或者定期检查签名是否失效。4.3 构建完整请求与数据抓取有了签名生成函数我们就可以像构造普通请求一样来抓取数据了。import requests import json def fetch_complaints(page1, size20): base_url https://tousu.sina.com.cn/api/index/... secret_key 从JS代码中找到的固定密钥字符串 # 替换为真实的key # 1. 构造基础参数 params { page: page, size: size, t: int(time.time() * 1000), # 当前时间戳 # 可能还有其他固定参数需要从抓包中观察补充 # type: ..., } # 2. 生成签名并添加 params[sign] generate_sign(params, secret_key) # 3. 设置请求头从浏览器复制至少需要User-Agent和Content-Type headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., Referer: https://tousu.sina.com.cn/, Accept: application/json, text/plain, */*, # Cookie: ..., # 如果接口需要登录态则需要有效的Cookie } # 4. 发送请求 # 如果是GET请求参数在URL中 # response requests.get(base_url, paramsparams, headersheaders) # 如果是POST请求以form-data或x-www-form-urlencoded格式参数在data中 response requests.post(base_url, dataparams, headersheaders) # 5. 处理响应 if response.status_code 200: try: data response.json() if data.get(code) 200: # 根据接口实际的成功码判断 complaints data.get(data, {}).get(list, []) for comp in complaints: print(f标题: {comp.get(title)}, 时间: {comp.get(created_at)}) return complaints else: print(f接口返回错误: {data.get(msg)}) except json.JSONDecodeError: print(响应不是有效的JSON格式) print(response.text) else: print(f请求失败状态码: {response.status_code}) return [] # 抓取第一页 fetch_complaints(1, 20)5. 进阶挑战与调试技巧成功跑通基础流程只是开始。在实际逆向过程中你会遇到更复杂的情况。5.1 应对代码混淆与压缩生产环境的JS代码通常是压缩和混淆过的变量名变成a、b、c函数名变成_0x123abc逻辑被拆分打乱。这增加了阅读难度。使用Pretty Print在Sources面板找到混淆的JS文件点击底部{}图标Pretty print代码会变得有格式易于阅读。关注核心逻辑不要试图理解所有代码。聚焦于你找到的签名函数附近。即使变量名是a、b你也能通过赋值a ...、函数调用b md5(a)来推断其作用。利用Console调试在代码关键行设置断点然后在Console中打印当前变量的值观察其变化过程这是理解混淆代码最有效的方法。5.2 动态密钥与环境依赖有些网站的key不是硬编码而是由另一个接口动态返回的。或者签名算法依赖浏览器环境的某些特性如window对象、document属性。追踪key的来源如果key是变量在代码中搜索它被赋值的地方如var key ...看它是来自一个变量、一个函数返回值还是一个网络请求的响应。如果是网络请求你需要先模拟这个请求获取key。补环境如果JS代码运行时报错提示window is not defined或navigator is not defined说明它依赖浏览器环境。在Python中我们可以使用PyExecJS、js2py或更强大的Node.js环境来执行这段JS代码。但作为入门更推荐的方法是仔细分析算法看它是否真的需要这些环境变量。很多时候window.xxx只是用来获取一个固定值如window.navigator.userAgent你完全可以在Python中直接定义这个值并传入JS上下文或者更彻底地将依赖环境变量的部分逻辑用Python重写。5.3 使用PyExecJS执行复杂JS对于无法轻易用Python重写的复杂加密逻辑如包含大量位操作、自定义的加密函数可以使用PyExecJS来调用一个JS执行环境如Node.js直接运行原版JS代码。import execjs # 1. 读取包含加密函数的JS文件 with open(heimaosign.js, r, encodingutf-8) as f: js_code f.read() # 2. 创建JS上下文 ctx execjs.compile(js_code) # 3. 调用JS函数 params {page: 1, size: 20, t: 1648886400000} sign ctx.call(getSign, params) # 假设JS中函数名是getSign print(sign)注意事项PyExecJS需要系统安装有JS运行时如Node.js。确保你的JS代码是自包含的或者手动将依赖的函数/变量都提取到同一个字符串中。6. 工程化思考与反爬策略应对一个稳定的爬虫不能只满足于今天能跑通。6.1 签名失效与算法更新网站运维人员不是摆设他们也会更新反爬策略。监控与告警在你的爬虫脚本中加入健康检查。定期运行一个测试用例如果连续多次无法获取数据或sign验证失败则触发告警发邮件、发消息。快速定位当签名失效时第一时间回到浏览器重复第2、3步的抓包和逆向流程。对比新旧JS文件看是key变了拼接逻辑变了还是哈希算法变了比如从MD5换成了SHA256。通常变化不会天翻地覆。代码抽象将签名生成算法封装成独立的函数或类。当算法更新时你只需要修改这一个地方。6.2 请求频率与IP管理即使解决了签名过于频繁的请求也会导致IP被封。添加延迟在请求间使用time.sleep(random.uniform(1, 3))添加随机延迟模拟人类操作。使用代理IP池对于大规模抓取必须使用代理IP。可以使用付费代理服务或者自建代理池。requests库使用代理很简单proxies {http: http://ip:port, https: https://ip:port}。设置合理的请求头User-Agent、Referer、Accept-Language等头信息要模拟得真实可以准备一个列表随机选择。6.3 数据解析与存储拿到数据后的处理也同样重要。健壮的解析使用response.json()解析时用try...except包裹。访问字典数据时使用.get(key, default)方法避免KeyError。结构化存储根据数据量选择存储方式。少量数据可以用JSON或CSV文件。大量数据建议存入数据库如SQLite、MySQL、MongoDB。使用pandas库可以方便地进行数据处理和导出。增量抓取如果只抓取最新数据可以记录已抓取条目的ID或最大时间戳下次请求时作为参数传入避免重复。JS逆向就像一场与网站开发者的智力游戏。黑猫投诉平台提供了一个近乎完美的入门战场。它让你经历了完整的流程抓包定位、参数识别、代码搜索、逻辑分析、Python复现。这个过程中最重要的不是记住某个具体的key或算法而是掌握“观察-假设-验证”的方法论。下一次当你遇到一个带有sign、token的请求时你不会再感到无从下手而是会熟练地打开开发者工具沿着网络请求的调用栈一步步揭开前端加密的面纱。这才是爬虫工程师从“脚本小子”走向“解决方案专家”的关键一步。在实际项目中更复杂的混淆、动态执行、WebAssembly加密等挑战会接踵而至但底层思路是相通的——理解数据流动还原计算过程。