☰
羊毛党批量注册拦截实战:从接码平台原理到风控接口设计
2026/10/14 20:06:34 网站建设 项目流程

正文

摘要:一次新客立减活动上线三天,注册量翻了 6 倍,优惠券核销率却不到 4%。排查后发现,绝大部分注册手机号来自接码平台。本文复盘这次事件,讲清楚接码平台的运作原理、为什么短信验证码挡不住,以及我们如何用手机号风险画像在注册链路做拦截——包含完整的 Python / Java 接入代码、超时降级设计和分级处置策略。

一、问题:注册量暴涨,但一个真实用户都没有

活动规则很简单:新用户注册即送 30 元无门槛券,满 39 元可用。

上线第三天,运营同学找过来,说数据不对劲:

指标

活动前均值

活动中

变化

日注册量

1,200

7,800

+550%

次日留存

38%

3.1%

-92%

优惠券核销率

21%

3.7%

-82%

下单客单价

128 元

41 元

-68%

注册量涨了 6 倍,留存和核销几乎归零。典型的虚假注册特征。

我拉了一批账号出来看,发现几个共性:

  • 注册时间集中在凌晨 2:00 - 5:00,间隔均匀(脚本批量跑的)
  • 手机号号段高度重合,集中在某几个虚拟运营商号段
  • 大量账号使用同一批 IP 段,但 IP 分布在全国各地
  • 收货地址格式高度相似,部分手机号是空号

结论:这批号是从接码平台买来的。


二、接码平台是怎么运作的

在讲方案之前,先说清楚对手,否则后面的技术选型没有依据。

2.1 黑产的三层供给
  1. 卡商 + 猫池:一台猫池设备插几十上百张 SIM 卡,接上电脑和卡商端软件,就能同时"养"住成百上千个号码。号码来源包括物联网卡、虚拟运营商卡、实名不规范的卡。
  1. 接码平台:把号码池以 SaaS 形式开放出去。下游提交手机号,平台转发该号码收到的短信验证码。价格通常在几毛钱一个号,随用随弃。
  1. 秒拨 IP + 改机工具:解决 IP 和设备指纹的限制。ADSL 拨号 IP 池秒级切换,配合清 Cookie、伪造 MAC,绕过频次和地域规则。
2.2 为什么短信验证码失效

很多人有一个误解:短信验证码能证明"人"的真实性。

其实它只能证明"这个号码此刻收到了一条短信"。而这个号码的真实持有人,可能正在接码平台上把它租出去。

更麻烦的是,语音验证码、人机验证(滑块/点选),接码平台和打码平台都有对应的解决方案。提高验证码复杂度,只是提高黑产的成本,无法拦住。


三、方案选型:自建还是外采

明确了问题,接下来是方案。可选的路径有三条:

方案 A:自建黑名单库(爬虫监控接码平台)

思路:爬取主流接码平台的号码,沉淀成黑名单。

问题:

  • 接码平台数量多、页面结构经常变,爬虫维护成本高
  • 号码是动态的,抓取有延迟,覆盖率难以保证
  • 误杀问题:黑号被运营商回收后可能落到正常用户手里
  • 需要专人持续投入,对小团队不划算
方案 B:基于业务数据自建模型

思路:用历史订单、设备、行为数据训练风险模型。

问题:冷启动困难。没有足够的黑样本,模型训练不出来。适合有一定数据积累的中大型平台。

方案 C:接入第三方手机号风险服务

思路:直接调用成熟的号码风险库,按查询次数付费。

评估维度:

维度

关注点

覆盖度

号码库量级、接码平台号段覆盖率、更新频率

性能指标

单次查询耗时、并发上限、服务可用性

输出能力

是否分级(而非只有黑白名单),能否配合 IP 联合判断

接入成本

是否有标准 API,SDK 支持情况

成本

单次查询价格,有无免费额度

我们最终选了方案 C,用的是羊毛盾(yangmaodun.com)的手机号风险检测接口。

选型理由:团队没有专职风控人力,方案 A 的维护成本扛不住;业务处于早期,方案 B 缺黑样本。方案 C 可以当天接入、当天见效,等业务量起来、数据积累够了,再考虑叠加自建模型。

说明一下:下面会以羊毛盾的接口为例讲实现,但设计思路是通用的,换成其他服务商或自建库,架构同样适用。


四、接口设计:在注册链路的哪个位置拦

4.1 调用位置

用户提交注册
↓
参数校验(手机号格式)
↓
【手机号风险检测】← 新增
↓
发送短信验证码 ← 高危号在这里就出不去,省下短信成本
↓
验证码校验
↓
注册成功 / 发放新客券

关键点:把检测放在发送短信验证码之前。

这样高危号直接拦截,连短信费都省了。以 0.03 元/条计算,拦截 1 万个黑号就是 300 元,加上被薅走的券,收益很明显。

4.2 接口协议

标准 RESTful 接口:

POST /api/v1/phone/riskcheck HTTPS/1.1 Host: yangmaodun.com X-API-Key: sk_your_api_key Content-Type: application/json { "phone": "13800138000", "ip": "8.8.8.8", "timestamp": 1720000000 }

响应返回high/medium/low三级风险等级。

注意ip参数必传。服务会结合 IP 做联合判断,如果传内网 IP(10.x / 192.168.x / 172.x)会影响判定结果。这一点后面踩坑部分会展开。


五、代码实现

5.1 Python 版本
import time import logging import requests logger = logging.getLogger(__name__) RISK_API = "https://www.yangmaodun.com/api/v1/phone/riskcheck" API_KEY = "sk_your_api_key" # 风险等级常量 LEVEL_HIGH, LEVEL_MEDIUM, LEVEL_LOW = "high", "medium", "low" def check_phone_risk(phone: str, ip: str = "", timeout: float = 0.8) -> str: """ 查询手机号风险等级。 设计原则:风控服务不可用时降级放行,绝不能拖垮主流程。 """ try: resp = requests.post( RISK_API, headers={ "X-API-Key": API_KEY, "Content-Type": "application/json", }, json={ "phone": phone, "ip": ip, "timestamp": int(time.time()), }, timeout=timeout, ) resp.raise_for_status() data = resp.json().get("data", {}) level = data.get("level", LEVEL_LOW) if level not in (LEVEL_HIGH, LEVEL_MEDIUM, LEVEL_LOW): level = LEVEL_LOW return level except requests.exceptions.Timeout: logger.warning("risk check timeout, phone=%s", phone[:7] + "****") return LEVEL_LOW except Exception as e: logger.error("risk check failed: %s", e) return LEVEL_LOW

几个设计要点:

  1. 超时时间设为 800ms。风控是旁路,不能让用户等。超时直接降级。
  1. 异常一律返回low。宁可放过一部分,也不能因为风控抖动导致正常用户注册失败——这是接入第三方风控最容易踩的坑。
  1. 日志脱敏。手机号属于个人信息,日志里必须打码。
5.2 分级处置策略

拿到风险等级后,不要一刀切。我们的处置策略:

等级

处置动作

理由

high

拒绝注册,不发送短信验证码

直接止损,省下短信成本

medium

允许注册,但暂缓发放新客券;触发滑块验证

避免误杀,增加黑产成本

low

正常放行

—

为什么medium不直接拒绝?

风控永远有误判。一个正常用户被误杀的体验损失,远大于放过几个可疑号的业务损失。中等风险用"延迟发券 + 增强验证"更稳妥——真实用户能完成验证拿到奖励,黑产脚本过不了。

5.3 存量用户批量巡检
import time from concurrent.futures import ThreadPoolExecutor def batch_scan(phones, workers=16, qps_limit=50): """批量巡检存量手机号,控制 QPS 避免打满接口配额""" results = {"high": [], "medium": [], "low": []} interval = 1.0 / qps_limit counter = [0.0] def task(phone): counter[0] += interval time.sleep(interval) return phone, check_phone_risk(phone) with ThreadPoolExecutor(max_workers=workers) as pool: for phone, level in pool.map(task, phones): results[level].append(phone) return results if __name__ == "__main__": phones = load_users_registered_last_30_days() r = batch_scan(phones) print(f"高危 {len(r['high'])} 个,可疑 {len(r['medium'])} 个") # 高危账号:冻结优惠券、标记人工复核

除了注册环节的实时拦截,存量用户也需要定期巡检——有些号码是注册后才进入黑产号码库的。

六、上线效果与踩过的坑

6.1 效果

接入一周后的数据(对比活动期间):

指标

接入前

接入后

日注册量

7,800

1,450

次日留存

3.1%

35%

优惠券核销率

3.7%

19%

短信费用/日

234 元

41 元

注册量回到正常水位,留存和核销恢复正常。短信成本下降 82%——因为高危号在发短信前就被拦掉了。

6.2 四个坑

坑 1:传了内网 IP

第一版上线后误杀率偏高。排查发现,服务部署在 Nginx 后面,代码里直接拿request.getRemoteAddr(),拿到的是 Nginx 的内网 IP。

修复:从X-Forwarded-For取真实客户端 IP,并过滤掉内网段。

坑 2:没有做缓存

同一个号码在短时间内被反复查询(比如用户重复提交注册),白白消耗接口配额。加了一层 Redis 缓存,TTL 设 1 小时,调用量下降约 40%。

坑 3:超时时间设太长

一开始设了 3 秒,高峰期风控接口抖动时,注册接口整体响应被拖慢。改成 800ms + 降级放行后,主流程不再受影响。

记住一条原则:风控是旁路,它的可用性不能绑架主流程的可用性。

坑 4:没有申诉通道

上线后有少量真实用户被误判为高危。我们补了一个申诉入口:用户可以提交身份验证材料,人工复核后加入白名单。任何风控策略都要留兜底的人工通道。


七、适用场景扩展

除了注册环节,这个检测还可以用在:

场景

检测时机

动作

领券/秒杀

领取前

high 拒绝领取

邀请拉新

被邀请人注册时

high 不计入邀请奖励

| 登录/支付 | 敏感操作前 | high 触发二次验证 | | 存量巡检 | 定时任务 | high 冻结权益、人工复核 |

核心思路是一致的:在成本发生之前,先把风险识别出来。

短信费、优惠券、补贴,这些都发生在用户进入系统之后。越早拦截,止损越彻底。


八、小结

  1. 短信验证码防不住羊毛党,它只能证明号码收到了短信,证明不了持有人身份。
  1. 手机号是黑产链路里相对较难替换的资源,从这一层切入性价比更高。
  1. 接入第三方风控时,设计重点是降级策略:超时放行、异常放行、日志脱敏,绝不能让风控抖动影响主流程。
  1. 分级处置优于一刀切,medium用延迟发券 + 增强验证,兼顾止损和误杀率。
  1. 留出人工申诉通道,任何模型都有误判。

手机号风控只是业务安全的一环。完整的体系还需要叠加设备指纹、行为分析、关系图谱等维度。这篇文章讲的,是成本较低、见效较快的第一道防线。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询