
简介资源面向需要逆向Shopee泰国站点API、构建跨境电商数据采集能力的开发者提供一套可运行的全套源码与说明文档。压缩包共23个文件包含Frida Hook核心JavaScript脚本、Python服务端与爬虫客户端、JSON参数配置、数据库文件及辅助说明整体仅43KB结构清晰。方案通过Frida Hook动态获取APP加密参数利用RPC将参数生成逻辑暴露给外部调用实现请求参数的实时复用并配套执行流程优化、反爬对抗策略、风险控制及扩展功能建议。已有518人学习适合具备一定逆向基础、希望快速搭建可维护爬虫系统的工程师参考使用。 做了两年多东南亚电商数据分析我最大的体会是数据能拿到多少分析就能做多深。尤其是做Shopee泰国站相关业务时这个感受更深。前阵子接了一个竞品动态监控项目需要持续跟踪一批店铺的价格、销量、评价变化。最开始我想走正路——去Shopee开放平台申请API。结果翻遍文档开放能力基本围绕商家后台的订单、商品上下架、物流协同这些场景商品历史价格、评价内容、销量趋势这类分析型数据官方根本不对个人开发者开放就算有企业资质审批流程也够你喝一壶。这条路堵死之后我才正式启动了API逆向方案。所谓Shopee泰国API逆向简单说就是绕过客户端用自己的程序去请求App背后的接口把签名、token、请求头完整复刻出来实现类似App的数据获取能力。我花了大概三周时间从抓包、反编译、签名还原到搭出一套可运行的Python工程现在这台机器上每天还在稳定跑。这篇东西我尽量把思路讲透而不是贴一堆跑不了的代码。适合三类人看做选品和价格监控的数据分析师、想研究现代电商API签名机制的后端工程师、以及刚接触安卓逆向想找一个完整案例的初学者。如果你以为拿到源码就能永久躺平那趁早打消这个念头这类方案永远在维护的路上。1. 先弄清一个问题官方API为什么给不了你想要的数据1.1 开放平台到底开放了什么Shopee开放平台并非没有API相反它有一套非常完整的接口体系覆盖店铺管理、商品上新、订单出库、客服会话等商家运营场景。文档写得相当规范鉴权流程、请求示例、错误码表一应俱全用起来比很多国内平台顺手。问题从来不在接口做得好不好而是它允许你碰什么。你翻到接口列表就会发现不少接口标注着邀请制或白名单点进去只有一句请联系您的客户经理。个人开发者连申请入口都找不到更别提拿到密钥了。以我当时的需求来说商品详情接口能拿到价格和库存但只返回当前值没有历史曲线评价接口只开放给部分高等级卖家普通账号根本申请不下来销量趋势这类数据干脆就没有对应接口。换句话说官方API是给运营者用的不是给观察者用的。当你想做跨店铺、跨品类的数据观察时开放接口的作用就很有限了。你要么接受拿不到数据的现实要么换一条路。1.2 什么场景才需要走逆向我接触到的需求基本可以归成三类。第一类是竞品监控持续跟踪对手店铺的价格调整、上新节奏、历史销量变化这是电商运营里最普遍的需求。第二类是比价与选品工具市面上不少做东南亚市场的选品SaaS数据源就是这些App接口。第三类纯粹是技术研究想理解现代电商客户端的签名体系、风控策略是怎么设计的毕竟Shopee这类平台的客户端安全强度在行业内算比较有代表性的。需要提醒的是这三类场景并不都理直气壮。自己做数据分析和学习研究灰色程度相对低如果是商业化工具你需要对自己的数据来源、使用合规性做完整评估。我见过有人把这类方案包装成收费服务卖给商家结果接口一变动就售后爆炸既折腾自己也给平台添麻烦。如果你只是写个脚本自己用把频率和边界控制好风险会小很多。1.3 合规边界先划清楚我个人的原则是三条只碰自己账号有权限看的数据比如自己的店铺信息、自己发起的搜索请求不动用户隐私数据不做批量采集用户信息的事不用来刷单、批量注册、伪造评论这类直接影响平台生态的操作。逆向本身是一种技术手段工具没有原罪但用在哪里完全由人决定。这篇文章讲的思路是以学习研究和个人数据获取为前提的具体场景合规性请自行判断。2. 泰站请求骨架抓包看到的那一堆参数谁是谁2.1 抓包环境与SSL Pinning处理动手第一步先把客户端流量看清楚。我用的组合是mitmproxy加一台Pixel安卓真机模拟器也不是不行但真机在风控眼里更接近正常用户后面遇到的干扰会少一些。Charles我也用过两者在抓包层面差别不大选mitmproxy主要是因为它可以命令行跑、方便集成到自动化流程里。这里有个绕不开的坎现代Android App基本都做了SSL Pinning证书校验不通过HTTPS流量直接断掉。解决办法有两个方向。一个是给系统证书目录安装自定义CA证书对Android 7以上系统需要在Magisk里用证书模块处理另一个是用Frida把App里的证书校验函数hook掉强制信任用户证书。我实际用的是后者本质是替换TrustManager的checkServerTrusted实现让它直接放行。几十行脚本网上模板很多但要注意不同Android版本对应的hook点略有差异得自己调试几次。2.2 从一次真实请求看参数分布流量通了之后随便触发一个商品搜索你会发现每个请求都带着一大堆字段。刚开始看很懵别急着研究每一个参数先分个类。明文参数一般包括时间戳、分页参数、搜索关键词、区域标识这类业务字段。加密或签名字段通常集中在请求头和个别body字段里。在泰站这边请求头里有几个字段是你必须重点观察的一个用于标识设备指纹一个携带登录态凭证还有一个就是签名常见命名是anti-token这一类。Body内部往往还嵌套了一层数据签名值是一段定长的十六进制字符串肉眼根本读不出业务含义。字段类别典型字段说明业务明文关键词、分页、区域标识直接读用于定位数据时间相关timestamp每次请求都会变参与签名设备相关设备指纹、启动ID首次生成后基本固定会话相关登录凭证、Cookie过期后需重新登录签名字段anti-token、body签名定长十六进制或Base64最难还原2.3 动态字段与固定字段的区分方法面对几十个参数最快的切入方式不是猜而是对比。同一个操作在相同设备上连续发三次请求把三个请求的参数逐一对齐。你会发现大部分业务参数是固定的变化的就那么几个时间戳、随机串、签名值、会话令牌。把每次都在变的字段挑出来逆向的范围一下子就缩小了。我当时用了一个很笨但很有效的方法把三次请求的参数整理成三列Excel按字段名逐行标绿标红。绿色是固定的红色是变化的。最后剩下大概五六个红色字段再逐个去APK里搜它们的生成逻辑。这个环节省不了因为每个字段背后都是一段代码定位顺序决定了你的工作效率。先处理时间戳这种一眼能看懂的最后啃签名心态会稳很多。3. 签名算法还原从Jadx反编译到Frida验证的完整路径3.1 用Jadx反编译定位签名入口拿到APK我习惯先扔进Jadx。这个工具会把DEX字节码还原成可读的Java代码官方源码里搜不到的东西在这里基本都能找到。泰站App的包体积不小反编译需要等一会儿电脑内存最好给足否则会卡死。搜索关键词的顺序很重要。先用签名相关的高频词anti、sign、token、secret、salt定位到候选类再用业务相关的关键词比如请求路径的片段、参数名追到具体的构造方法最后用调用链往上翻看这个值是在哪个类、哪个方法里被计算出来的。Jadx搜索时记得勾选搜索所有节点有些字符串会被拆开拼接直接搜原始字符串搜不到。这一通操作之后你会碰到大量混淆过的类名看起来像a.b.c这种。混淆很讨厌但有个规律真正参与签名的逻辑往往集中在一两个util类里而且方法内部肯定会出现字符串拼接、字节数组转换、Hex输出这类操作。按这个特征去筛候选范围能缩到很小。3.2 一个典型的签名生成流程我把定位到的那套逻辑简化之后大概是这个模式把所有参与签名的业务参数按字典序排列拼成一个长字符串再加入一个固定盐值最后做哈希运算转成十六进制。伪代码如下import hashlib import time def generate_sign(path: str, params: dict, salt: str) - str: # 排序拼接是签名算法里最常见的套路 items [f{k}{params[k]} for k in sorted(params.keys())] raw path .join(items) salt str(int(time.time())) return hashlib.sha256(raw.encode(utf-8)).hexdigest()注意这不是泰站当前版本的完整算法只是我用来演示的通用骨架。真实算法在拼接顺序、盐值来源、参与字段范围上一定有差异但思考路径是一样的。你按这个思路去还原自己抓到的包很快能对上号。另外不同地区版本可能有不同盐值来源我就遇到过泰国版和印尼版用的是不同拼接顺序的情况别拿一套参数硬套全部站点。3.3 签名之外服务端还会校验什么拿到签名规则只是拿到了入场券不代表你能畅通无阻。服务端还会校验几个东西时间窗口签名里的时间戳如果和服务器时间差超过几十秒直接拒绝随机数去重同一个nonce短时间内不能重复出现防止重放攻击设备指纹同一套签名换一个设备标识风控等级会骤升。设备指纹通常会在首次启动时生成存在本地存储里之后每次请求从存储读取。你直接在代码里伪造一个不存在的设备标识短期内没问题长期一定会触发风控。我最初以为把签名还原出来就完事了结果用Python直接请求连续被拒。后来才发现签名只是整个安全链路的一环前面那几个校验点一个都不能漏。这也是为什么很多网上的逆向方案跑几天就失效他们只还原了最表层的一部分。签名、设备、时间、会话四者必须像一个真实用户那样协同工作。4. 源码工程怎么组织签名器、请求层与频率控制的落地写法4.1 模块划分与目录结构逆向方案和普通业务代码最大的区别是它会被平台更新冲掉。所以工程结构必须以方便维护为第一原则把易变的部分集中隔离。我最终的目录结构是这样shoppe-th-api/ ├── config/ │ └── config.yaml # 盐值、请求路径、版本号等易变配置 ├── signer/ │ ├── base.py # 签名器抽象接口 │ └── v1_impl.py # 某个客户端版本的签名实现 ├── client/ │ ├── session.py # 会话管理、Cookie保持 │ └── api_client.py # 业务请求封装 ├── parser/ │ └── response_parser.py # 响应解析 └── main.py # 调度入口核心思路就一句话签名算法独立成模块版本号写进目录名配置全部外置。以后客户端升级新写一个v2_impl.py就行不动其他代码。配置外置解决了一个很现实的问题当平台更换盐值或接口路径时不需要重新发布代码改一下yaml就行。我第一次没做配置隔离结果平台换了个接口路径我翻了半天代码才找到拼接URL的地方。4.2 签名器与请求客户端的对接代码签名器我做成抽象接口方便不同版本替换class BaseSigner(ABC): abstractmethod def sign(self, method: str, path: str, params: dict, timestamp: int) - dict: 返回需要注入请求头或body的签名字段 pass请求客户端在发送前调用签名器把结果合并进请求头class ApiClient: def __init__(self, signer: BaseSigner, session: requests.Session): self._signer signer self._session session def get_product(self, item_id: str): path /api/v2/product/get params {item_id: item_id} timestamp int(time.time()) sign_fields self._signer.sign(GET, path, params, timestamp) headers {x-time: str(timestamp), **sign_fields} resp self._session.get( https://shopee.co.th path, paramsparams, headersheaders, ) return resp.json()注意一个细节签名的计算必须在发送前那一刻完成不能提前几秒算好放缓存里因为时间戳字段一旦过期服务端校验直接不通过。另外签名器里参与签名的字段列表要明确写出来不要用字典遍历所有params服务端对字段顺序和遗漏很敏感少一个字段算出来的签名就是错的。4.3 会话保持、频率控制与重试策略请求层最容易被忽略却又最关键的是频率控制。我见过很多人把签名搞定后用for循环疯狂发请求结果跑了十分钟账号就被风控了。正确做法是所有请求走同一个requests.Session登录态的Cookie集中管理每次请求之间加随机延迟基础间隔1到3秒再叠加一个随机抖动遇到429、503这类响应用指数退避重试最多三次退避间隔从1秒开始每次翻倍。这样既保证了数据时效性又不至于触发风控。会话方面泰站登录态的过期策略比较激进我遇到过几次请求返回419的情况。这个状态码基本代表会话失效处理方式很直接重新走一遍登录流程更新Cookie再继续任务队列里未完成的请求。Session的User-Agent、Accept等头要尽量和真实客户端一致我见过只改了User-Agent但漏了Accept的结果被风控识别成脚本请求全返回验证码。这些细节平时不起眼一旦触发风控排查起来很头疼。5. 运行实测避坑签名失效、会话过期与版本升级的应对5.1 高频报错对照表跑了几个月把遇到的高频问题整理了一下报错或现象可能原因处理方式401 Unauthorized签名错误或token过期检查签名拼接顺序和时间戳403 Forbidden设备指纹异常或触发风控降低请求频率检查设备标识是否被标记419 Page Expired登录会话失效重新登录刷新Cookie429 Too Many Requests请求频率过高增大延迟指数退避签名无效但不报错返回空数据时间戳偏差校准本地时间检查时区换算5.2 一次签名无效的完整排查实录说一个我印象很深的坑。某天脚本突然大面积报签名无效我第一反应是平台更新了算法。于是重新抓包、对比参数、检查代码折腾了两个小时最后发现是我服务器的系统时间出问题了。为什么时间会影响签名因为签名里包含时间戳服务端校验时会对比签名里的时间戳和服务器当前时间。我服务器时区设置成了UTC本地代码里却按东八区生成了时间戳两者差了整整八个小时。签名算出来是昨天的服务端当然拒绝。那次之后我在配置里加了一个时钟同步检查启动任务前先校准本地时间再校验与目标站点的时区偏移。泰站用的是UTC7这个偏移量也写进了配置。这个排查过程也让我养成了一个习惯遇到签名失效先看时间再看代码。具体来说先用NTP同步服务器时间确认系统时区配置再检查代码里时间戳生成的时区参数最后才去怀疑算法本身。顺序反了很容易浪费大量时间。5.3 客户端版本升级后的维护策略平台客户端每隔一段时间就会强制升级升级之后旧的接口路径大概率还能访问但签名算法的细节可能悄悄变化。维护策略就一条核心原则不要追新先存量后增量。新版本发布后先别急着升级自己的工程。旧版本App只要还能用就让线上任务继续跑同时用备用设备装新版本抓包对比新旧请求的差异。确认哪些字段变了、签名算法变了多少再写新的签名器实现小流量切换验证没问题最后才全量替换。另外版本升级时最大的变动往往不是签名算法本身而是参与签名的字段集合变了比如多了一个category_id或者少了一个region字段。这种情况排查起来比算法变更更隐蔽因为你光看签名代码发现不了问题必须逐字段比对请求参数。这套流程看起来慢但能保证线上任务不断档。我吃过一次亏当时贪快直接换新算法结果漏了一个字段导致一整个任务队列的数据全部作废重跑花了三天。从那以后任何算法变更都走灰度流程。最后分享两个小经验。一个是抓包数据一定要留档每次逆向的请求参数、签名结果、对应版本号都存下来哪天算法变了翻旧数据比对差异效率极高。另一个是别把所有逻辑堆在一个文件里我一开始图省事签名、请求、解析全写在main.py里后面维护的时候想死的心都有。重构之后换一个客户端版本只需要新增一个签名器实现类改几行配置其他代码基本不动这才是可运行源码该有的样子。本文还有配套的精品资源点击获取