1. 项目概述与核心目标最近在分析一些消费投诉数据时遇到了一个典型的场景需要从某个知名的第三方投诉聚合平台我们姑且称之为“x猫投诉平台”上批量、自动化地获取投诉列表、详情以及相关的处理进展。手动复制粘贴显然不现实而平台本身并没有提供公开的API接口。这时候逆向工程就成了实现数据采集目标的唯一可行路径。逆向听起来很高深但针对这类Web平台其核心往往是理清前端与后端的数据交互逻辑找到那个关键的请求并模拟它。这篇文章我就以2025年5月30日这个时间点的“x猫投诉平台”为例手把手带你走一遍完整的Web逆向分析流程。目标很明确通过逆向分析构造出能够稳定获取投诉列表数据的合法请求。整个过程不涉及任何破解、攻击或干扰平台正常运营的行为纯粹是技术层面的协议分析与模拟用于学习与研究。为什么选这个案例因为它非常典型。这类平台的前端通常由JavaScript驱动数据通过Ajax动态加载并且会带有一些基础的防爬措施比如参数签名、Token验证等。搞定它你就能掌握一套应对类似Web端数据接口的通用分析方法。无论你是做数据分析、市场调研还是单纯对技术好奇这套方法都能给你提供清晰的思路和可复现的步骤。我会尽量用通俗的语言把每个环节的“为什么”和“怎么做”讲清楚包括工具的选择、抓包的技巧、参数的分析以及代码的模拟。放心即使你之前没有逆向经验跟着步骤走也能看懂个七八成。2. 逆向分析的核心思路与准备工作逆向一个Web应用本质上是在扮演一个“诚实”的浏览器。浏览器知道如何与服务器正确对话以获取数据我们的目标就是学会这套“语言”。这套语言的核心载体就是HTTP/HTTPS请求。因此我们的逆向工作流可以概括为观察 - 分析 - 模拟。2.1 核心思路拆解观察真实交互使用浏览器正常访问目标页面同时利用开发者工具DevTools的网络Network面板捕获浏览器发出的所有网络请求。我们的目标是找到那个真正返回了我们需要的数据比如JSON格式的投诉列表的请求。分析请求特征找到目标请求后我们需要像侦探一样审视它的每一个细节URL请求的地址是什么是固定的还是动态生成的请求方法是GET还是POST请求头Headers特别是Cookie、User-Agent、Referer以及一些自定义的头部如X-Token,X-Sign等。这些往往是服务端用于验证客户端身份和请求合法性的关键。请求参数Query String / Payload对于GET请求参数在URL问号后面对于POST请求参数可能在表单Form Data或请求体Request Payload中常见格式有x-www-form-urlencoded或JSON。我们需要找出哪些参数是固定的哪些是动态变化的动态参数的值又是如何生成的。模拟关键请求在Python等编程环境中使用requests等库按照我们分析出的规则构造出与浏览器行为一致的HTTP请求从而获取数据。2.2 工具准备工欲善其事必先利其器。以下是本次分析需要用到的核心工具它们都是免费且广泛使用的浏览器与开发者工具推荐使用Google Chrome或Microsoft EdgeChromium内核。按F12即可打开开发者工具我们主要使用“网络Network”和“源代码Sources”标签页。抓包/调试代理工具虽然浏览器自带的工具很强但专业的代理工具能提供更强大的过滤、断点、重放和修改功能。这里我强烈推荐Charles或Fiddler Everywhere。它们可以作为系统代理捕获所有经过它的HTTP/HTTPS流量对于分析移动端App或复杂的页面跳转尤其有用。本次案例以Web端为主浏览器DevTools足够但了解这些工具很有必要。编程环境Python 3.x。我们将使用requests库来发送HTTP请求json库来处理数据。可能还会用到hashlib用于计算MD5、SHA等签名、time生成时间戳、execjs执行JavaScript代码等库。建议使用PyCharm、VSCode或Jupyter Notebook作为开发环境。代码编辑器与格式化工具在“源代码Sources”面板中我们经常需要查看被压缩minified的JavaScript代码。Chrome DevTools自带“代码美化Pretty Print”功能点击{}图标能将混乱的代码格式化得易于阅读。2.3 目标页面与数据定位首先我们打开“x猫投诉平台”的网站找到投诉列表页面。通常这类页面是分页加载的比如https://tousu.someplatform.com/complaint/list?page1。打开Chrome DevToolsF12切换到“Network网络”面板。务必在打开面板后再刷新页面或点击“加载更多”这样才能捕获到所有网络请求。在请求列表中我们需要寻找返回数据是JSON格式的请求。可以通过以下方法快速筛选筛选XHR/Fetch请求在Network面板顶部筛选器中选择“XHR”或“Fetch”这些通常是Ajax请求返回JSON数据的概率很大。预览响应内容点击某个请求在右侧的“Preview”或“Response”标签页查看返回内容。如果看到结构化的、包含list、total、page等字段的JSON数据那很可能就是我们的目标。假设我们找到了一个请求POST https://tousu.someplatform.com/api/complaint/v1/list其Response正是我们需要的投诉列表JSON。3. 关键请求的深度分析与参数破解找到目标请求只是第一步接下来是逆向分析中最核心、也最考验耐心的部分——参数分析。我们需要把这个请求的所有组成部分拆解得明明白白。3.1 请求头Headers分析点击目标请求查看“Headers”选项卡。我们需要重点关注以下几项User-Agent标识客户端浏览器和操作系统。服务器可能用它来做基础的风控或兼容性处理。模拟时我们需要设置一个常见的UA例如Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36。Referer表示请求是从哪个页面发起的。对于防止跨站请求伪造CSRF和简单的防盗链有一定作用。通常需要设置为列表页面的URL。Cookie这是维持会话状态的关键。在首次访问网站登录或浏览后服务器会通过Set-Cookie响应头下发Cookie浏览器后续请求会自动带上。我们的爬虫需要维护这个Cookie。注意有些Cookie可能具有时效性如登录态过期后需要重新获取。自定义头部这是重灾区。很多平台会自定义一些头部用于签名或传递令牌例如X-Token: eyJhbGciOi...,X-Sign: a1b2c3d4e5...,X-Timestamp: 1745945600。X-Token可能是登录后颁发的身份令牌而X-Sign很可能是对请求参数进行某种加密或哈希计算后得到的签名用于防止请求被篡改。我们的主要攻坚对象往往就是这些自定义头部特别是签名Sign。实操心得在分析初期可以尝试在Python脚本中只复制User-Agent、Referer和Cookie从浏览器直接复制然后发送请求。如果返回错误如“签名错误”或“非法请求”那就证实了存在其他验证机制需要我们进一步分析X-Sign之类的参数是如何生成的。3.2 请求参数Payload分析对于POST请求查看“Payload”或“Form Data”选项卡。这里通常包含了分页、筛选条件等参数。例如{ pageNo: 1, pageSize: 20, searchKeyword: , complaintStatus: 0, timestamp: 1745945600123 }我们需要分析pageNo,pageSize分页参数通常直接明文传递。searchKeyword,complaintStatus搜索和筛选条件。timestamp时间戳。这是一个非常常见的动态参数用于防止请求重放Replay Attack。服务器会校验这个时间戳如果与服务器时间相差太大可能会拒绝请求。它通常也是生成签名的一部分。关键问题timestamp和sign或其他签名参数是如何关联的签名算法通常是客户端的一个JavaScript函数它将请求参数可能还包括一个固定的密钥、时间戳、随机数等按照特定规则拼接成一个字符串然后进行MD5、SHA256或HMAC等哈希运算最终得到签名值。服务器端用同样的算法和密钥再算一遍如果一致则认为请求合法。3.3 定位签名生成算法这是逆向的“临门一脚”。我们需要在前端JavaScript代码中找到生成签名的函数。搜索关键词在DevTools的“Sources源代码”面板中使用全局搜索CtrlShiftF。搜索关键词可以是sign、encrypt、md5、sha、hmac、timestamp、key、secret等。也可以直接搜索请求URL的一部分如/api/complaint/v1/list看看是哪个JS文件发起了这个请求。断点调试如果搜索无果或者函数被混淆obfuscated得难以阅读可以使用“XHR/Fetch Breakpoints”功能。在Network面板中右键点击目标请求的URL选择“Breakpoint” - “URL contains”。然后刷新页面或触发请求代码执行会在发起该请求前暂停此时调用栈Call Stack会显示是哪个函数发起的请求我们可以一步步跟进去找到参数组装和签名计算的地方。格式化与追踪找到疑似计算签名的代码块后利用“Pretty Print”功能格式化。然后仔细阅读代码逻辑。常见的模式是function generateSign(params) { // 1. 对参数对象按照键名排序 let keys Object.keys(params).sort(); let str ; for (let key of keys) { str key params[key] ; } // 2. 拼接一个固定密钥 str secret_keyYOUR_SECRET_HERE; // 3. 进行MD5哈希 let sign md5(str).toUpperCase(); // 或 .toLowerCase() return sign; }你需要找出secret_key是什么以及拼接的顺序和规则。有时密钥是硬编码在JS里的有时是通过其他接口动态获取的。注意事项现代Web应用普遍使用Webpack等打包工具代码会被分割成许多模块chunks。签名函数可能被封装在某个模块里。这时在Network面板中找到那个主要的app.xxxx.js或vendor.xxxx.js文件进行搜索和调试会更有效。另外有些平台会定期更新前端代码和签名算法所以逆向得到的方案可能在一段时间后失效需要重新分析。4. 使用Python模拟请求与数据获取在成功分析出请求的构成特别是签名算法后我们就可以用Python来模拟这个请求了。4.1 环境搭建与基础请求首先安装必要的库pip install requests如果签名算法涉及复杂的JS代码我们可能需要execjs库来执行JS片段pip install PyExecJS # 同时确保本地有JavaScript运行时环境如Node.js。ExecJS会自动检测。基础请求框架如下import requests import time import json from urllib.parse import urlencode # 1. 基础配置 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://tousu.someplatform.com/complaint/list, Content-Type: application/json;charsetUTF-8, # 根据实际情况调整 # Cookie 初始为空可能需要先访问首页获取 # Cookie: your_cookie_here } # 2. 获取初始Cookie如果需要 session requests.Session() # 访问一下首页让服务器设置Cookie home_resp session.get(https://tousu.someplatform.com, headersheaders) # 此时 session 会自动管理 cookies # 3. 构造请求参数 def construct_params(page_no1, page_size20, keyword): params { pageNo: page_no, pageSize: page_size, searchKeyword: keyword, complaintStatus: 0, # 关键生成时间戳毫秒级 timestamp: int(time.time() * 1000) } return params # 4. 生成签名这是核心需要根据逆向结果实现 def generate_sign(params): 假设我们逆向出的签名算法是 1. 将所有参数包括timestamp按键名升序排序 2. 拼接成 key1value1key2value2... 的格式 3. 在末尾拼接上固定字符串 key固定的密钥 4. 对整个字符串进行MD5并转为大写 # 注意这里的密钥fixed_secret是逆向分析出来的仅作示例实际需要替换。 fixed_secret abc123def456 # 排序并拼接 sorted_items sorted(params.items(), keylambda x: x[0]) sign_str .join([f{k}{v} for k, v in sorted_items]) sign_str fkey{fixed_secret} # 计算MD5 (Python 实现) import hashlib md5 hashlib.md5() md5.update(sign_str.encode(utf-8)) sign md5.hexdigest().upper() return sign # 5. 组装最终请求 def get_complaint_list(page_no1): # 构造基础参数 params construct_params(page_nopage_no) # 生成签名 sign generate_sign(params) # 将签名放入请求头根据实际情况也可能是放入参数中 request_headers headers.copy() request_headers[X-Sign] sign # 如果还有X-Token也需要从某处获取并放入 # request_headers[X-Token] get_token() # 发送POST请求 url https://tousu.someplatform.com/api/complaint/v1/list # 注意如果服务器接收JSON就用json参数如果是form就用data参数。 response session.post(url, jsonparams, headersrequest_headers) # 检查响应 if response.status_code 200: try: data response.json() if data.get(code) 200 or data.get(success): # 根据实际响应结构判断 return data.get(data, {}) # 返回数据部分 else: print(f请求失败返回码: {data.get(code)}, 信息: {data.get(msg)}) return None except json.JSONDecodeError: print(响应不是有效的JSON格式) return None else: print(fHTTP请求失败状态码: {response.status_code}) return None # 6. 执行并解析数据 if __name__ __main__: data get_complaint_list(1) if data: complaints data.get(list, []) total data.get(total, 0) print(f共获取到 {len(complaints)} 条投诉总计 {total} 条) for idx, comp in enumerate(complaints[:5]): # 打印前5条看看 print(f{idx1}. 标题: {comp.get(title)}, 投诉人: {comp.get(complainant)})4.2 处理复杂签名调用JavaScript如果签名算法非常复杂或者直接移植成Python代码困难例如使用了特定的JS加密库我们可以使用execjs来直接执行我们找到的原始JS函数。假设我们在一个叫sign.js的文件里提取出了签名函数// sign.js function complexSign(params, secretKey) { // 一段复杂的、可能依赖其他JS库的签名算法 const CryptoJS require(crypto-js); // 假设用了CryptoJS // ... 复杂的计算逻辑 return sign; }在Python中可以这样调用import execjs # 读取JS文件 with open(sign.js, r, encodingutf-8) as f: js_code f.read() # 创建JS上下文 ctx execjs.compile(js_code) # 调用JS函数 params {pageNo: 1, timestamp: 1745945600123} secret_key 逆向得到的密钥 sign ctx.call(complexSign, params, secret_key) print(sign)实操心得使用execjs时环境配置可能是个小坑。确保安装了Node.js并且JS代码中依赖的库如crypto-js在Node环境下可用。有时直接将浏览器中格式化后的、包含所有依赖的IIFE立即执行函数表达式代码块保存下来让execjs执行是最快最准的方法。5. 数据解析、存储与反反爬策略成功拿到数据只是第一步如何稳定、高效、持久地获取数据并处理可能遇到的反爬机制是更实际的挑战。5.1 数据解析与清洗接口返回的JSON数据可能嵌套很深结构复杂。使用Python的json库解析后需要根据数据结构提取所需字段。建议先打印出第一条数据的完整结构做到心中有数。import json # 假设data是接口返回的完整响应字典 sample_item data[data][list][0] print(json.dumps(sample_item, indent2, ensure_asciiFalse))然后针对性提取例如def parse_complaint_item(item): return { id: item.get(id), title: item.get(title), content: item.get(content, )[:200], # 截取部分内容 company: item.get(targetCompany, {}).get(name), status: item.get(statusDesc), create_time: item.get(createTime), update_time: item.get(updateTime), view_count: item.get(viewCount, 0), }5.2 数据存储根据数据量和使用场景可以选择不同的存储方式CSV文件适合中小规模数据易于用Excel打开查看。import csv def save_to_csv(data_list, filenamecomplaints.csv): if not data_list: return keys data_list[0].keys() with open(filename, w, newline, encodingutf-8-sig) as f: # utf-8-sig解决Excel中文乱码 writer csv.DictWriter(f, fieldnameskeys) writer.writeheader() writer.writerows(data_list)数据库如SQLite/MySQL适合大规模、结构化数据便于复杂查询和分析。JSON文件适合保存原始响应或中间结果便于调试。5.3 应对基础反爬策略平台可能会实施一些基础的反爬措施我们的脚本需要足够“礼貌”和“像人”。请求频率控制在循环翻页请求时务必在请求间添加随机延时避免高频请求导致IP被封。import time import random def random_delay(min_s1, max_s3): time.sleep(random.uniform(min_s, max_s))User-Agent轮换准备一个UA池每次请求随机选取一个。user_agents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 ..., # ... 更多UA ] headers[User-Agent] random.choice(user_agents)IP代理池如果单个IP频繁访问被限制需要考虑使用代理IP。可以使用付费代理服务或者自建代理池。在requests中使用代理很简单proxies { http: http://your-proxy-ip:port, https: http://your-proxy-ip:port, # 注意很多http代理也支持https } response session.post(url, jsonparams, headersheaders, proxiesproxies)会话Session保持使用requests.Session()可以自动处理Cookie保持登录状态比单次请求更稳定。处理动态Token有些平台除了签名还会在每次页面加载时通过一个隐藏的接口或内联脚本返回一个一次性的Token需要先获取这个Token再用于后续的数据请求。这就需要分析页面HTML或额外的初始化请求。6. 常见问题排查与实战技巧在实际操作中你几乎一定会遇到各种问题。下面是一些常见错误和排查思路。6.1 常见错误码与原因错误现象可能原因排查思路返回403 Forbidden或400 Bad Request1. 请求头不完整或错误缺Referer,Cookie。2. 签名Sign错误或缺失。3. 请求参数格式错误如应是JSON却用了Form Data。1. 对比浏览器请求头逐一补全。2. 重点检查签名算法确认密钥、拼接顺序、大小写、编码是否正确。3. 检查Content-Type请求头并确认requests的json/data参数使用正确。返回{code: 1001, msg: 签名错误}签名验证失败。这是最典型的情况。1.确认参数完整性服务器参与签名的参数可能比你看到的多例如包含固定参数。2.确认拼接顺序是否按字母排序是否包含URL中的查询参数3.确认哈希算法是MD5、SHA1还是SHA256输出是十六进制还是Base64是否要转大写/小写4.确认密钥密钥是否找对是否有时效性返回{code: 1002, msg: 令牌失效}Token过期或无效。1. Token是否有有效期需要定时刷新。2. 获取Token的流程是否正确可能需要先模拟登录。返回{code: 1003, msg: 请求过于频繁}触发了频率限制。1. 增加请求间隔时间加入随机延迟。2. 考虑使用代理IP。返回数据为空或只有第一页分页逻辑有误或接口有访问限制。1. 检查分页参数名是否正确是page还是pageNo。2. 有些接口通过limit和offset分页。3. 查看响应中是否有totalPage或hasNext字段来判断是否结束。4. 是否登录后才能查看更多页6.2 高级调试技巧对比法用你的脚本和浏览器同时或先后发两次请求捕获两者的完整请求包括头、参数进行逐字节对比。差异点就是问题所在。Charles/Fiddler的重放Repeat和对比Compare功能非常好用。日志记录在签名函数中把每一步生成的中间字符串特别是最终待签名的字符串都打印出来。用浏览器的Console执行相同的签名函数对比两个中间字符串是否完全一致注意不可见字符和编码。Hook大法对于更复杂的、运行在浏览器环境中的加密函数如果静态分析困难可以考虑使用浏览器插件或Frida针对WebView进行Hook直接打印出函数输入输出。但这属于更高级的技巧需要一定的JS功底。逆向更新平台前端更新是常态。如果某天脚本突然失效不要慌。重复本文的抓包分析流程重点对比新老请求的差异。很可能只是签名算法中增加了一个新的固定参数或者密钥换了。6.3 安全与合规边界最后必须强调所有技术都应在法律和道德框架内使用。遵守robots.txt检查目标网站的robots.txt文件尊重网站禁止爬取的目录。控制访问频率你的爬虫不应该对目标网站服务器造成显著负担。设置合理的延迟避免DDoS式的请求。尊重数据版权与隐私获取的数据应用于个人学习、研究或合法合规的分析。不得用于商业倒卖、侵犯个人隐私或从事其他非法活动。不绕过核心付费墙如果数据是平台的核心资产并明确需要付费获取逆向其接口可能涉及法律风险。逆向工程是一个不断与系统设计者“斗智斗勇”的过程充满了挑战和乐趣。通过“x猫投诉平台”这个案例我们完整地实践了从抓包、分析、破解到模拟的Web逆向流程。最关键的是培养了那种“顺藤摸瓜”、通过现象看本质的思维习惯。下次当你遇到没有API但又需要数据的情况时不妨打开开发者工具从第一个网络请求开始你的探索。记住耐心和细致是逆向工程师最重要的品质。