
做淘客返利APP这几年最难的不是功能迭代而是每天半夜都在跟各种抓取脚本打交道。说实话只要你的商品库和返利规则有价值就一定有人盯上他们会写爬虫把你的价格、优惠券、佣金比例批量搬走做成竞品或者黑灰产数据源。这篇文章要聊的就是我们自己从零搭建的一套爬虫对抗技术体系核心是动态令牌、设备环境检测和对抗样本生成三者通过前后端协同方案串成一条完整防线。适合所有做返利电商、导购平台、价格监控类产品的开发者和风控同学参考尤其适合那些已经意识到“光加个登录验证根本没用”的团队。1. 项目从哪来返利APP到底在跟谁对抗1.1 商品数据就是真金白银淘客返利APP的数据资产不只是商品标题和主图真正的价值在价格波动、优惠券库存、佣金比例、商家补贴策略。这些数据一旦被批量抓走坏处有三层。第一层同行可以直接复制你的选品库运营成本几乎为零你的选品团队辛辛苦苦筛出来的品别人拿去直接上架你的竞争壁垒直接被抹平。第二层黑灰产可以通过历史价格和返利变化做套利比如你在大幅调佣时被他们大量囤号薅走佣金平台补贴变成了羊毛党的提款机。第三层数据泄露本身会伤害商家合作商家发现自己补贴信息出现在外部渠道就会质疑你的数据安全能力合作关系就可能受影响。所以做爬虫对抗不是技术洁癖而是实打实的生死问题。我们早期吃过亏商品接口只做了简单登录校验结果上线一周就被抓走了几百万条数据那时候才意识到单纯靠登录态和鉴权根本挡不住爬虫。1.2 一场攻防三个战场做了一段时间之后我把爬虫对抗总结成三个战场这也是整套方案的基本盘。第一个战场是请求层核心是动态令牌机制。它要解决的是“这个请求是不是某个用户主动发出的”这件事。第二个战场是设备层核心是设备环境检测。它要解决的是“发起请求的设备是不是真实手机还是一片模拟器农场”。第三个战场是模型层核心是对抗样本生成用持续生成的扰动样本喂给检测模型解决“你的风控规则会不会很快就被人摸透”的问题。三个战场不是三条平行线而是层层递进的关系。请求层把住第一道门设备层识别异常环境模型层负责对付那些会变形的攻击请求。把这三层单独拆开做每个方案都不完整只有把它们叠在一起爬虫才会觉得成本够高、不值得继续打。1.3 为什么必须前后端协同一开始我也踩过坑以为前端做好签名、后端做个校验就能挡住爬虫结果是前端规则三天被逆向一次后端误杀一大堆正常用户。后来想明白一个道理纯前端方案扛不住逆向因为客户端的逻辑只要运行在用户手机上就能被分析、被Hook、被动态调试。但纯后端方案也有缺点就是被动服务端只能看到一堆IP和请求头缺少对设备真实状态的感知。只有前后端协同才能形成一套动态博弈闭环客户端持续采集设备环境证据打包上传给服务端服务端统一做风险判断把判断结果回传给客户端客户端再根据服务端的指令调整下一步的令牌策略和交互方式。这条闭环跑通之后爬虫面临的不再是一个静态规则而是一个会根据它行为不断变化的系统。2. 动态令牌让每个请求都有“临时身份”2.1 固定Token为什么挡不住批量抓取很多刚起步的团队喜欢用固定Token登录后拿到一个token就一直用过期了再重新登录一次。这样做对正常用户很友好但对爬虫来说也友好得过分只要抓一次包把token和请求格式复制下来换个IP和参数就能无限刷。固定Token最致命的问题是没有时间维度服务端无法区分“同一用户正常翻页”和“同一用户用脚本高频拉取”。爬虫拿到一个合法token之后理论上可以一直用直到token过期这段时间足够他们把整个商品库翻个底朝天。动态令牌的思路就是让每个请求都绑定时间、签名和一次性随机数。即使爬虫抓到一个合法令牌也没法把它变成批量工具因为下一个请求需要重新计算签名而签名依赖客户端持有的私密密钥和当前请求参数。只要爬虫拿不到密钥复制请求包就永远少一个核心字段。2.2 动态令牌的生成与校验逻辑我们的实现分四步走。第一步客户端从服务端获取短期令牌有效期15分钟令牌里包含uid、rand、expireTime三个字段。这个令牌本身不参与业务数据查询只是证明“会话还在有效期内”。第二步每次请求前客户端把URL参数、Body摘要、时间戳、rand拼成一个字符串用约定好的HMAC-SHA256做签名。注意这里一定不能用AES加密整体token这种方案因为加密是可逆的一旦密钥被逆向出来整个体系就崩了而HMAC是单向签名服务端只校验签名是否匹配不涉及解密安全性高一个档次。第三步签名和令牌一起放进自定义请求头服务端用同源信息重新计算签名比对结果并校验时间戳窗口前后各允许5分钟偏移。第四步服务端校验通过后把这个rand写入Redis并设置5分钟过期同一个rand再次出现直接拒绝。这一步是为了挡住重放攻击一个请求只能被真正执行一次。客户端签名示例我用伪代码表示// 客户端签名示例 const payload [ ts${ts}, uid${user.uid}, rand${session.rand}, path${path}, bodyHash${sha256(body)} ].join() const sign hmacSHA256(payload, secretKey) // 请求头: X-Token: session.token // 请求头: X-Time: ts // 请求头: X-Rand: session.rand // 请求头: X-Sign: sign服务端拿到请求头之后先查Redis里有没有这个rand有就直接拒绝没有则继续校验签名和时间戳。校验通过后如果令牌剩余有效期只剩2分钟服务端会在响应头下发一个新的token客户端自动续期。这样用户全程无感但令牌从不过期。2.3 服务端签发、客户端续签的协同细节这里有个容易被忽略的协同细节动态令牌最好由服务端控制签发节奏而不是客户端想换就换。我们规定令牌有效期15分钟但允许续签的前提是当前请求本身的签名合法、且设备环境评分没有触发高危。一旦触发高危服务端返回一个特殊状态码客户端收到后必须进入挑战流程比如弹滑块验证、短信验证或者二次登录。这个设计把“令牌策略”和“设备检测结果”耦在了一起。环境越可信令牌窗口越宽松正常用户几乎感觉不到任何打断环境越可疑令牌窗口越短爬虫还没攒够数据就被踢出去重新验证了。另外签发令牌时要用安全的随机数发生器生成rand不能直接用时间戳。爬虫如果发现rand等于当前时间或者时间戳加固定偏移它就能预测下一次rand的值进而构造合法的签名请求。这个细节我们是在一次复盘时发现的只差一步就被拖库了。2.4 防重放与时钟偏移的坑动态令牌最容易踩的坑就是用户手机时间不准。如果校验完全依赖客户端时间戳会有大量误杀特别是那些从来不校时的安卓机。我们曾经试过把时间窗口收窄到1分钟结果线上误杀率飙升到8%用户反馈“我什么都没干就被登出了”。后来我们的处理办法是允许前后5分钟偏移窗口同时由服务端在响应头下发服务器时间戳客户端本地记录与服务器的时间差值下次请求用当前时间加偏移量作为ts。这样即使设备本地时间不准签名也能落在窗口内。第二个坑是重放。只有时间窗口是不够的同一请求在窗口内重复发送依然能通过所以必须用rand的一次性约束。我把rand的Redis过期时间设为5分钟比签名时间窗口略短确保“时间窗口还在但rand已失效”的状态不会让重放得逞。这套令牌机制上线后接口被批量刷的情况明显少了很多但光有令牌还不够因为爬虫完全可以逆向客户端假装自己是正常用户发请求。要识别这种行为就得靠设备环境检测。3. 设备环境检测识别“机器味”3.1 设备指纹这么采才有效设备检测的基础是指纹采集但这里有个容易走偏的点不能只采集IMEI、MAC这类硬编码信息因为现在的爬虫工具会伪造这些字段而且这类敏感标识采集还涉及合规风险动不动就踩监管红线。我们采用“多源弱特征组合”策略。采集销售渠道UA是否魔改、屏幕分辨率、系统版本、内存大小、电池状态、传感器列表、时区、语言、字体列表、Home目录结构等。单个特征看起来都没什么杀伤力但组合起来就有辨识度。举例来说一个“分辨率1920x1080但内存只有512MB”的设备真实机型中几乎不存在。再比如真实手机的传感器列表通常包含加速度计、陀螺仪、磁力计而在模拟器里这些传感器不是缺失就是数值恒定为0。把这些弱特征拼在一起就能形成一台设备的稳定画像。指纹计算建议在客户端生成稳定的deviceId并绑定账号不要每次请求都重新计算。deviceId本身不存明文而是用哈希摘要保存服务端定期给deviceId打风险分。3.2 风险特征从模拟器到自动化框架我们把设备环境分成四档正常、可疑、高风险、已废弃。下面这些特征都是我们在防御侧重点看的信号。第一类是模拟器特征包括Build.HARDWARE、Build.FINGERPRINT出现可疑厂家标识GPU渲染型号缺失传感器列表异常电池温度恒定等。第二类是Hook框架痕迹比如进程列表里存在常见Hook框架相关包名。第三类是无障碍服务滥用很多自动化脚本会借无障碍权限模拟点击这个特征和正常用户行为有明显的统计差异。第四类是自动化测试框架比如某些控件点击框架会留下特定包名或辅助功能服务标识。注意我这里提到这些特征是为了说明防御侧如何识别和拦截滥用实际采集时必须严格遵循用户授权和隐私合规要求。我们不会把原始敏感字段上传只上传脱敏后的特征编码服务端也只保存哈希值和风险评分不保存原始内容。3.3 服务端如何给环境打分前端采集原始特征后不直接在客户端判断因为客户端判断永远可以被绕过。正确做法是把特征打包成设备信息对象上传给服务端由服务端的风险评分引擎统一打分。打分采用规则加权加模型打分双轨制。规则体系命中高风险特征比如存在Hook框架、设备指纹被篡改直接进黑名单。模型输出一个0到1的欺诈概率这个阈值我们调过很多次最终定在0.8以上拦截、0.5到0.8进入滑验、0.5以下放行。不要小看这个阈值选择它直接影响误杀率。我们上线初期阈值定0.6结果误杀率飙升到9%大量正常用户被弹验证码气得要卸载APP。后来我们把正常用户样本和爬虫样本重新标注统计了两种样本在模型输出值上的分布曲线再把阈值提到0.8误杀率才降到2%以下。3.4 设备检测的合规边界这块必须单独说因为它比技术更容易翻车。设备环境检测天然涉及个人信息所以有两条铁律。第一采集前必须弹窗说明提供撤回授权入口不能偷偷采集。第二不做跨应用追踪不上传与业务无关的联系人、短信、位置等数据。简化的特征也要加密传输服务端只保留hash后的设备ID不保留原始隐私字段。如果你用的是第三方风控SDK同样要确认对方的合规流程。我们在调研第三方SDK时发现有些厂商会把采集到的设备信息共享给其他合作方这种跟业务目标没有关系的数据流转我们直接筛掉宁可自研采集模块也不留安全隐患。合规不是耽误技术反而是保护技术不会因为监管问题被紧急下架。我们曾经因为某个地方法规调整紧急下线过一个采集字段就是因为当初没有留好开关代码改了两天。后来所有采集字段都做成了配置化服务端可以随时下发开关关闭采集。4. 对抗样本生成用假敌人练出真火眼4.1 为什么不能用真实攻击数据训练对抗样本生成的目标是让风控模型不只在旧样本上有高准确率也能应对没见过的伪装手法。如果只用真实攻击数据训练会有两个问题。一是真实攻击数据其实很稀缺而且特征分布会随时间快速漂移今天有效的方法明天随着爬虫工具的更新就失效了。二是靠收集真实攻击数据训练等于被动挨打等数据攒够了攻击者早就换了新套路。所以我们选择主动生成对抗样本在有限的工程成本内构造出高扰动、低成本的模拟样本集用于持续验证和增强模型。这套方法的本质是通过模拟敌人来加速自身进化和军事上“红蓝对抗”的思路很像。4.2 对抗样本生成的具体做法对抗样本生成不是一个神奇算法而是一套特征扰动流水线。核心思路是把正常请求作为基底然后按一定概率混入可疑特征生成一批混合样本。我们内部做了一个合成器把所有可能出现的风险特征做成可配置的特征项每一轮生成时按照随机概率组合。合成器主要生成三类样本基线负样本纯正常用户行为用于保证模型召回不失控防止模型学歪了把正常用户全部拦掉。对抗正样本人为混入高危特征但尽量接近真实流量用于提升模型识别能力是训练集里的主力。极端样本把所有可疑特征叠加用于测试规则是否会漏放虽然极端场景不常见但一旦出现就是灾难。每类样本都打上标签灌入模型做训练和回测。实际执行时我们还会对生成的样本做漂移校验比如计算样本特征分布和真实流量分布的KL散度如果偏离太远就人工检查调整保证合成样本不会偏离真实分布太多。4.3 样本生成与模型迭代的回测闭环样本生成不是一次性的事而是融入每周发布的检测模型迭代闭环。我们的流程是生产环境有新的攻击特征时先把它特征化再由合成器生成10000条对抗样本接着用这批样本跑离线回测统计模型的查全率和误报率达到指标后再把这批样本按比例混进训练集重新训练模型并做灰度验证。跑过几轮之后你会发现最值钱的不是生成流程本身而是那个持续更新的回测看板。看板上记录了每一版模型在不同样本集上的表现方便我们判断升级是进步还是倒退。有一次我们把最新攻击特征加进去之后模型对正常用户的误报率涨了3个百分点如果回测看板不透明这个问题可能就直接上生产环境了。5. 前后端协同方案的落地实现5.1 完整请求链路是怎么跑的把所有模块拼起来后一条完整的请求链路是这样的用户启动APPSDK初始化并采集设备信息生成设备指纹对象。用户登录服务端签发短期动态令牌。每次业务请求客户端生成签名并携带令牌、设备信息摘要。后端网关先校验令牌签名和时效再查询设备风险评分。如果评分安全正常转发业务如果触发中风险下发挑战验证如果高风险直接返回403并踢下线。服务端异步地把本次请求的样本写入日志供后续对抗样本生成和回测使用。这套链路的好处是令牌校验是强校验设备检测是软判断两者叠加后对正常用户影响很小但爬虫只要有一环没过就被挡在外面。5.2 后端网关选型与性能开销令牌校验放在API网关层统一处理比在每个业务服务里重复实现要省心得多。我们当时对比了几种方案最终用OpenResty在Nginx层做签名校验。选择OpenResty的原因有三个一是可以直接处理原始请求头无需经过业务容器转发二是基于Lua能快速迭代校验逻辑不用每次调整规则都重新发布Java服务三是性能损耗低每请求只增加几毫秒对整体链路没压力。设备风险评分如果每次都实时计算网关的CPU必然扛不住所以我们做了两级缓存第一级缓存设备风险得分5分钟过期第二级缓存黑名单uid与deviceId1小时过期。实测下来网关平均处理耗时增加不到3毫秒用户完全感知不到。5.3 灰度发布与监控指标任何风控规则都不能全量一把梭这是我们从多次事故里换回来的教训。我们规定所有新规则先跑影子模式只记录判定结果不实际拦截跑一天观察误杀率。误杀率低于阈值后切到5%流量灰度确认正常再逐渐放量到全量。监控面板上重点盯三个指标正常用户被拦截率也就是误杀率真实爬虫的请求通过率令牌校验失败率。特别是令牌校验失败率它如果突然升高大概率不是攻击而是自己签名的算法或时间同步出了问题一定要第一时间看告警。我们有一次灰度新签名逻辑上线两小时校验失败率飙升到30%就是因为网关缓存没刷新旧密钥还在校验新签名请求这个坑排了很久才发现。5.4 技术栈与实施成本清单很多团队会问这套方案到底要投入多少资源。按一个小型返利APP的体量估算初期开发包括自研设备指纹采集SDK、网关签名校验插件、风险评分服务和对抗样本生成器大致需要三到四个人投入两个月。如果不想自研全套可以把SDK采集部分外包给第三方风控厂商服务端做标准化接入省一半时间但要接受对方的数据隐私策略和报价。我们的部署形态是一台Nginx网关加两个风险评分服务实例一个用于实时查询一个用于离线训练和回测。数据库方面Redis存rand和评分缓存MySQL存日志和样本标签。整体资源占用不大一个小型服务器集群就能跑起来成本可控。6. 实战问题排查与避坑技巧6.1 三大高频事故按照我自己的经验最容易翻车的事故集中在三处。第一令牌误杀。用户手机时间偏移超过5分钟正常请求直接被拦。解决办法是放宽到7分钟同时引入NTP校准让客户端记住与服务器的时间差值。这个问题在海外用户特别明显因为跨时区环境下手机时间经常不准。第二设备检测漏放。只用UA和分辨率规则的时候模拟器特征不够明显很多真正跑脚本的机器反而能混过去。后来把传感器列表和GPU信息加进特征组合漏放率才明显改善。看指标不能只看拦截了多少爬虫还要看是不是有该拦的没拦住。第三对抗样本污染模型。合成样本生成太极端会导致线上误报率飙升一个本来训练得很好的模型反而把正常用户拦了。解决办法是每批样本都要做漂移校验和人工审核不能无脑灌进训练集。6.2 排查工具与方法排查这类问题我建议把风控命中的日志单独打通字段至少包括请求ID、uid、deviceId、风险分、命中规则、令牌校验结果。排查时先看全链路时序图确定是令牌层还是设备层出问题。如果是令牌层用同一个请求在测试环境重放看签名计算是不是稳定密钥是不是同一个。如果是设备层把设备信息对象打印出来逐一比对特征看到底是哪个字段被判定成了可疑。我们经常用临时开关做A/B对比把某条规则关闭后对比误杀率变化很快就能定位是谁引发的问题。上线初期我们甚至给每条规则都配了单独的独立开关方便快速应急。6.3 几条实操心得最后分享几条我踩过很多坑之后沉淀下来的经验。动态令牌的密钥要定期轮换最好支持两套密钥灰度切换。我们已经做到每三个月轮换一次每次轮换前灰度一部分用户验证新规则没问题再全量切换避免换密钥导致老用户大规模掉线。设备检测结果要允许用户申诉解封。申诉流程不只为了用户体验申诉记录本身就是很好的对抗样本来源。很多被误杀的用户其实行为特征和爬虫很接近但他们申诉时你可以拿到人工复核数据正好拿来补强模型。对抗样本生成器要和风控模型放在同一个版本管理里否则样本越来越脏模型自然越来越差。我们把样本生成配置、特征字典、模型权重都放到同一个代码仓库每次发布一起打包保证可复现。所有规则都要考虑回滚能力。上线前就必须想好如果误杀率飙升怎么一键降级。我们针对性做了配置中心所有风控字段支持动态开关真出事时不用改代码直接关闭对应规则就能恢复服务。