今天聊点实操的东西:淘宝js逆向,以及为什么我把它和咸鱼放在一起当“同类型”项目做。熟悉阿里系前端的朋友应该都有感觉,淘宝、咸鱼(官方叫闲鱼)、天猫、甚至部分卖家后台,底层那套接口体系和加密框架高度同源,搞懂一个,另一个基本上就是按图索骥找差异。最近有个项目需求是抓取淘宝和咸鱼的类目、商品信息,我就沿着“淘宝js逆向”这条路把两边一起打通了,过程中踩了不少坑,也沉淀了一套可复用的调试思路。这篇不写教科书式理论,直接把怎么定位断点、怎么扒接口、怎么适配咸鱼同源接口、以及那些文档里不写的坑一次说清楚,适合准备入门前端逆向、或者被淘宝风控折磨过的人参考。
1. 项目起因:为什么说咸鱼是淘宝JS逆向的同类型项目
1.1 阿里系前端的统一基因
阿里系Web端技术栈有个很明显的特点:几乎所有面向C端的页面都走同一套网关,页面里又统一使用webpack打包。消费者看到的页面叫法不同、按钮文案不同,但打开控制台一看,很多模块加载方式、请求签名格式、Cookie加密手段都是同一个底子。就拿我这次实测的例子来说,淘宝PC端的商品类目接口,和咸鱼APP内嵌H5的推荐接口,走的都是mtop网关,请求参数里同样带sign、t、api、v、appKey这些字段,只是api名字不一样。这句话记下来,后面会反复用到。
这个“同类型”意味着什么?意味着你做淘宝js逆向时总结出来的方法论,可以横向迁移到咸鱼。比如定位加密函数的方式、拦截XHR请求找调用栈的技巧、甚至对webpackJsonp数组的Hook逻辑,在两边几乎一模一样。所以与其把“淘宝逆向”和“咸鱼逆向”当成两个独立项目,不如当成一个项目的两个变体,这样效率翻倍。
1.2 类目ID是最平易近人的切入点
项目里有个搜索热词“淘宝分类id202060801”,这其实是一个很典型的切入点。淘宝的类目体系非常庞大,从顶级类目到叶子类目有几百上千个节点,每个节点都有唯一的分类ID。当我们在网页上点击某个类目时,前端会带着这个ID向后端请求该分类下的商品列表或筛选项。对这个ID做断点追踪,能顺藤摸瓜找到整个分类接口的调用链。
为什么说它“平易近人”?因为分类ID是明文出现在请求参数里的,不像sign和token那样加密,定位起来几乎零门槛。你只要在控制台里用关键字搜索“202060801”,就能定位到携带这个ID的JavaScript变量,再从变量往上追调用链,整个接口的URL和请求参数就浮出水面了。这个方法我建议每个新手都先试一遍,它能在半小时内让你建立起对淘宝/咸鱼接口结构的整体认知。而且同一个ID在咸鱼里也存在对应关系,只不过字段名从categoryId变成了类似categoryId或cateId的变体,这正好印证了“同类型”的判断。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 逆向起步:把工具链和调试环境搭好
2.1 抓包和断点怎么配合
逆向第一步别急着打开大张旗鼓的工具,先把环境理顺。我的个人习惯是:浏览器DevTools的Network面板用于看请求全貌,Source面板用于断点调试,Charles或whistle承担HTTPS解密和请求回放。三个工具各有侧重,但真实操作时,八成时间其实都花在DevTools上。
有人问为什么不用Charles搞全局抓包?因为淘宝这类站点的请求很密,静态资源、日志上报、埋点请求一大堆,你很难在抓包工具里过滤出真正需要的那条业务接口。相比之下,浏览器Network面板自带搜索功能,可以按关键字筛选,还能直接右键“Copy as fetch”把请求转成脚本,整个流程顺畅很多。我通常先用Network确认接口名,然后用Source里的XHR断点或者DOM事件断点去找加密逻辑。
值得注意的是,咸鱼的一些H5页面是从APP里内嵌WebView打开的,普通PC浏览器抓不到,这种情况我一般用手机USB调试配合Chrome Remote Debugging,或者直接在开发者工具的设备模式里通过设置UA伪装成移动端,让页面加载出对应的H5版本。实测下来,用PC浏览器加UA覆盖的方式是最省事的,因为仓库里那套断点工具仍然可以正常工作。
2.2 5分钟定位webpack加载器
阿里系Web页面几乎都是webpack打包的,如果不先定位加载器,你面对的就是动辄几十万行的压缩代码,根本无从下手。我在这里分享一个固定的两步定位法,实践下来几乎屡试不爽。
第一步,在Console里输入Object.keys(window),找到类似webpackJsonp的数组或对象。过去很长一段时间,阿里页面用window.webpackJsonp,随着版本升级,有些页面改成window.webpackChunk_xxx这种带站点标识的变量名,但规律都是“webpack”加上后缀,一眼能认出来。第二步,在Sources里搜索“webpackJsonp”,会看到一段形如function(e){...}([someModuleId, {...}])的代码,这就是模块注册器。真正需要关注的是它内部的__webpack_require__方法,所有模块间的依赖引用都通过它完成,通过Hook这个方法,可以拿到所有模块ID到模块代码的映射关系。
定位到加载器之后,我会给自己写一段嵌入脚本,遍历所有模块号并保存成字典文件,这样后面找加密函数时不用在压缩代码里大海捞针,直接按模块号搜索精确命中。这一段小工程大概30行代码,却能把后续工作量砍掉一半,强烈建议动手前先做。
3. 核心实战:从分类ID到完整接口的一步步调试
3.1 用分类id=202060801演示类目接口定位
前面铺垫了那么多,现在进入正题。我以淘宝PC端搜索页为例,手动在分类导航里点到一个包含“202060801”这个ID的类目,然后跟着接口走一遍完整流程。为了防止描述过于抽象,我把每个步骤的关键动作拆出来。
第一步,打开目标类目页,按F12打开DevTools,切到Network面板,勾选Fetch/XHR过滤类型。这时候页面会发一批请求,不用看全部,直接在过滤框输入“mtop”关键字,因为阿里系业务接口大多集中在mtop网关域名下。第二步,从请求列表里找一个名字里带“category”“search”“list”的接口,点进去看Payload,大概率能看到data参数里含有categoryId: '202060801'。第三步,在Global Search(快捷键Ctrl+Shift+F)里直接搜“202060801”,找到携带该ID的JS片段,查看是哪一段变量或函数把它带进了请求体。
实际操作时,第三步搜索出来的结果可能不止一条,因为埋点代码也会带上类目ID。我的筛选原则很简单:优先看那些位于function内部的字符串,而不是对象字面量里的静态值;优先看被赋值给data或params的地方,这样更接近真实请求构造点。定位到赋值语句后,在那一行打上断点,刷新页面,就能看到完整的调用链,从点击事件到数据组装再到加密签名,清清楚楚。
3.2 签名机制与加密字段的拆解
拿到请求构造点之后,最关键的环节就是处理签名。淘宝和咸鱼的mtop接口在请求头或请求体里都会带一组固定字段,典型结构如下:
json复制{
"api": "mtop.taobao.idle.search.home.recommend",
"v": "1.0",
"appKey": "12574478",
"t": "1733123456789",
"sign": "6e9d2f...",
"data": {
"categoryId": "202060801",
"pageSize": "20",
"pageNum": "1"
}
}
其中appKey对应客户端标识,t是毫秒级时间戳,data是业务参数,sign则是所有关键参数的签名结果。签名算法历来是各家逆向的重头戏,阿里这边也改过好几版。从我最近的实测看,H5场景下的常见做法是:把data里的业务参数按key做字典排序,拼接成query字符串,再混入appKey、t、token、固定的盐值,最后做某种消息摘要。
这里必须强调:具体拼接顺序和摘要算法我不能只凭记忆写死,因为线上版本可能随时调整,最可靠的还是直接在调试器里找到调用签名函数的地方,观察入参和拼接顺序。我一般会在Network面板里复制一个真实请求,然后去Sources里搜索sign:,在赋值语句上打条件断点,比如window._signFlag === true,等触发后手动执行入参拼接的每一步,把中间结果打印出来对比。这个过程看似笨重,但能100%确认算法的当前形态,远比盲猜靠谱。
3.3 用Node.js把请求完整跑通
当签名逻辑确认之后,我会立刻在Node.js环境里写一个最小可复现脚本,目的是把浏览器里的请求逻辑搬到本地,方便后续批量测试。下面是一个示意结构,重点展示如何组织参数并生成签名请求。
javascript复制const crypto = require('crypto');
const axios = require('axios');
function generateSign(params, token) {
// 1. 将业务参数按key排序并拼接
const keys = Object.keys(params).sort();
const baseString = keys.map(key => `${key}${params[key]}`).join('');
// 2. 混入固定token与时间戳(具体以断点调试结果为准)
const raw = `${baseString}&token=${token}&t=${Date.now()}`;
// 3. 做摘要(常见有md5、hmacSha256,务必以当前线上实际为准)
return crypto.createHash('md5').update(raw).digest('hex');
}
async function fetchCategoryList() {
const params = {
categoryId: '202060801',
pageSize: '20',
pageNum: '1'
};
const sign = generateSign(params, 'your_token_here');
const response = await axios.get('https://h5api.m.taobao.com/h5/mtop.taobao.searchdetail.appsearch/1.0/', {
params: {
api: 'mtop.taobao.searchdetail.appsearch',
v: '1.0',
appKey: '12574478',
t: Date.now(),
sign,
data: JSON.stringify(params)
},
headers: {
'User-Agent': 'Mozilla/5.0 ...',
'Referer': 'https://www.taobao.com/'
}
});
console.log(response.data);
}
这个脚本跑通之后,我会再验证一下返回数据里的类目名称字段。例如分类ID 202060801,在真实接口的返回JSON里会有一个节点同时携带ID和名称。我见过的做法是,响应里有个categoryList或sellerCategory节点,每条数据带categoryId和categoryName字段。注意别只盯着中文名做硬编码,线上的名称随时可能改,必须用ID作为唯一键去关联。
还有个小细节,如果脚本在本地跑一直提示缺少某几个Cookie,说明接口依赖登录态或风控Cookie,这时候需要把浏览器里的有效Cookie带到脚本里。但注意Cookie有有效期,建议设置成从配置文件读取,不要写死在代码里,后续维护省心很多。
4. 闲鱼同源场景下的二次适配与反爬感知
4.1 淘宝与咸鱼的差异点
淘宝和咸鱼同源,但不可能完全一致,真正动手适配时,我发现差异主要集中在三块。
第一是域名和网关地址。淘宝的H5接口集中在h5api.m.taobao.com,咸鱼则走h5api.m.goofish.com或api.m.goofish.com,网关虽然也叫mtop,但域名变了,对应的Cookie体系和风控策略也有差别。第二是接口命名。淘宝叫mtop.taobao.searchdetail.appsearch,咸鱼里同一个语义的搜索接口可能叫mtop.taobao.idle.search.home.recommend或mtop.idle.someitem.someapi,名字里多了idle这个标识,因为闲鱼的英文名是Idle Fish。第三是返回结构。咸鱼返回的商品列表字段命名更贴近社区交易场景,比如卖家信用、所在地、是否验货等,字段名和淘宝的通用电商字段差别很大。
适配的时候,我的做法是先把淘宝侧已经调通的签名生成逻辑原封不动搬过去,然后对比咸鱼的接口入参,把api名、业务参数修订一下。签名机制这一层,至少在我测试的版本里是高度相似的,这也印证了“同类型”的核心判断:一旦破解了淘宝的签名体系,咸鱼并不会给你来个全新的算法,最多是token来源不同。
4.2 selenium和PB接口的真实处境
项目过程中有个搜索热词是“淘宝selenium”,另一个是“pb做的淘宝接口”。这两个词放在一起,恰好反映了同行们常遇到的两个痛点。
先讲selenium。很多非逆向方向的人会试图用selenium直接操作浏览器来绕开接口逆向,但淘宝和咸鱼对于WebDriver特征有成熟的检测方案,比如检查navigator.webdriver属性、CDP连接标识、浏览器指纹等。实测下来,裸奔的selenium在打开淘宝首页后很快就会收到验证码或直接无法搜索。这不代表selenium毫无用武之地,我的经验是,把selenium当成“替身”去维护登录态,而不是用来并发抓数据。先用浏览器手动登录,让selenium持久化保存Cookie,再把这些Cookie导出给Node脚本使用,这个组合比纯接口逆向简单,也比纯selenium稳定。
再说“pb做的淘宝接口”。这里的pb是Protobuf(Protocol Buffers)的缩写。我遇到过部分淘宝接口根本不返回JSON,而是返回一串二进制protobuf编码数据,很多分析者一看不是JSON就懵了。解法其实不复杂,先在Network面板里看响应头是否包含application/x-protobuf,然后用pbjs或protobufjs加载对应的.proto文件来解析。关键是.proto文件从哪来,我的经验是从前端加载的JS里找线索,很多proto定义会被打包成.proto.js或嵌入在某个叫proto的模块里,用前面提到的webpack模块字典去搜索“proto”关键字就能定位。注意proto版本可能不匹配,需要看响应里字段编号对应的含义,逐个比对。
4.3 npm镜像与工程依赖安装
写到这里,顺便讲一个和环境相关的细节。搜索热词里出现过“npm淘宝安装最新代理”,我猜不少人是在安装淘宝镜像源相关依赖时碰到了问题。这里要强调的是,现在npm官方仓库和国内镜像源(npmmirror,即原淘宝npm镜像)都支持直接安装依赖,早几年那种“先安装cnpm再装包”的做法已经过时了。如果你只是想在Node脚本里用axios、crypto-js、protobufjs这些库,直接配好npm的registry地址就行:
bash复制npm config set registry https://registry.npmmirror.com
npm install axios crypto-js protobufjs
配好镜像源之后的安装速度会快很多,尤其对包含大量native模块的依赖更加明显。但注意一点:在项目里不要把所有依赖都一股脑装进dependencies,逆向调试时经常要临时试包,用npm install xxx --no-save安装更干净,避免污染项目文件。
5. 常见问题与排查技巧实录
5.1 高频报错速查表
为了便于查阅,我把这次项目中遇到的高频问题整理成了表格,按“现象、原因、解决办法”三列对应。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
接口返回FAIL_SYS_TOKEN_EXOIRED |
携带的token过期 | 重新用浏览器登录并更新Cookie/token |
签名一直不通过,提示SIGN_ILLEGAL |
签名拼接顺序或摘要算法不对 | 回浏览器里打条件断点,打印中间变量逐个对比 |
| 返回数据是二进制乱码 | 接口走的是protobuf编码 | 检查响应头Content-Type,加载对应.proto解析 |
| request被风控拦截,出现验证码 | 请求频率过高或缺少关键Cookie | 降低频率,补充浏览器里的有效Cookie;避免裸奔selenium |
| 本地Node脚本收到跨域错误 | 服务端要求特定Referer/Origin | 在请求头中补齐Referer和Origin |
| 类目ID搜不到结果 | 页面当前分类并非该ID | 确认入口,换用Global Search搜接口名而非ID |
这个表不是死的,环境变化后现象可能不同,但排查思路是通用的:先缩小范围,确认是网络层、签名层还是数据解析层的问题,然后逐层打印中间值。
5.2 我长期踩坑后总结的细节
下面这些是比较杂但特别值钱的经验,不写进表格是因为每条背后都有一次完整翻车经历。
第一,断点不要直接打在压缩后的代码行上。压缩文件里一行几十KB,断点位置难以控制。正确做法是先找webpack模块ID,再在Sources面板通过模块ID搜索,进入模块内部格式化代码,然后再打条件断点。格式化后的代码可读性大幅提升,断点误差小很多。
第二,留意动态Cookie和静态Cookie的区别。淘宝和咸鱼都会动态刷新某些Cookie,比如和风控相关的标记。我之前把浏览器里的Cookie全量复制到脚本里,过半小时就失效。后来做了个自动同步机制:每隔一段时间打开浏览器刷新Cookie文件,脚本启动时动态读取。这样虽然不能完全避免风控,但单次任务的存活时间明显增加。
第三,在咸鱼适配时,不要忽视“页面环境字段”。咸鱼有些接口的data里会包含utdid、x5sec这种设备指纹或者风控参数,缺失时即便签名正确也可能返回空数据。解决办法是,在浏览器里多观察几次请求,看哪些参数是每次请求都会变、哪些是稳定值,把稳定值提取为配置项,变化值用脚本动态生成或继续从Cookie池里取。
第四,警惕“同构陷阱”。前面强调淘宝和咸鱼同类型,但不代表任何字段都可以原样搬运。我曾经直接拿淘宝的参数结构去请求咸鱼接口,结果部分筛选条件失效,返回的数据和页面对不上。后来逐一对比参数,发现有几处是咸鱼独有的,比如交易类型和验货状态。所以“同类型”的正确用法是复用框架、算法、调试路径,而不是机械拷贝字段。
第五,接口名字里带recommend的往往带有个性化参数,不适合直接当稳定数据源。需要测试数据时,优先找名字里带search、list、category这类语义明确的接口,无论在淘宝还是咸鱼,它们的参数更标准,返回结构更适合批量采集。
结尾:给想做同类项目的人一点个人建议
这套流程跑完之后,我最大的感触是:做淘宝js逆向这类活,真正值钱的不是某个固定的sign算法,而是一套“怎么找、怎么断、怎么验”的方法。只要你把webpack定位、断点调试、接口对比、Cookie维护这几板斧练熟,切到咸鱼或者阿里系其他站点,无非就是换个域名和几个字段名的事。另外,把接口数据接进大模型做分析也成了新常态,我现在的做法是先抓商品信息,清洗成结构化JSON,再通过function call喂给大模型做同款比价和标签抽取,逆向链路本身反而成了最底层的一环。最后提醒一句,接口逆向和抓取务必控制频率,用于学习和个人研究没问题,别给线上服务添麻烦。
