Bot 流量“假阳性”调优笔记 - 实践
日志里 80% 的 404 都来自同一个 User-Agent,但把它直接拉黑又怕误伤真用户。本文记录了我们如何用 150 行 Python 写出一个实时标记脚本,并把它无缝接入已有的高防 IP 回源链路。
背景
公司运营了一款小游戏,前端每 5 秒会轮询一次 /api/heartbeat 保活。
最近监控发现,该接口 404 率飙到 30%,但把出现 404 的 UA 拉黑后,日活直接掉 5%。显然里面混进了高仿爬虫,规则写太粗就会误杀。
思路:把“行为”抽象成特征,而不是死盯 UA
我们决定用一条极简的机器学习流水线:
- 采集:在高防 IP 回源段上开一个旁路端口,镜像 1% 流量到本地;
- 特征:把一次会话(IP + UA + 5 min 窗口)抽象成 4 个数值特征
ratio_404:404 占比avg_interval:心跳间隔均值entropy_path:访问路径熵is_night:是否凌晨 0-6 点
- 模型:用
sklearn现成的随机森林,正负样本各 1 万条,训练 30 s 搞定; - 动作:模型输出概率 > 0.8 的会话,实时推送到高防 IP 的“自定义封禁 API”,30 秒生效。
关键代码
下面这段脚本跑在回源机房的一台 2C4G 小水管上,占用内存不到 30 MB。
核心依赖只有 pandas + scikit-learn,可以随镜像一起打包。
#!/usr/bin/env python3
import json, time, requests
from collections import defaultdict
from sklearn.ensemble import RandomForestClassifier
from sklearn.externals import joblib
MODEL = joblib.load('bot_model.pkl')
API = 'https://api.anycast-clean.com/v1/ban ' # 高防 IP 提供的封禁接口
TOKEN = 'YOUR_SECRET_TOKEN'
session = defaultdict(lambda: {
'paths': [], 'codes': [], 'ts': []
})
def ingest(line):
# 假设日志格式: 2025-08-19T12:00:00 ip ua path code
_, ip, ua, path, code = line.strip().split(' ', 4)
key = (ip, ua)
session[key]['paths'].append(path)
session[key]['codes'].append(int(code))
session[key]['ts'].append(time.time())
def features(rec):
codes = rec['codes']
paths = rec['paths']
ts = rec['ts']
ratio = codes.count(404) / len(codes)
avg_iv = (max(ts) - min(ts)) / max(len(ts) - 1, 1)
entropy = -sum(p * (p and (p := paths.count(x) / len(paths))) for x in set(paths))
night = 0 <= time.localtime().tm_hour <
6
return [ratio, avg_iv, entropy, night]
def decide():
for key, rec in list(session.items()):
if len(rec['codes']) <
10: # 数据不足
continue
vec = [features(rec)]
prob = MODEL.predict_proba(vec)[0][1]
if prob >
0.8:
ip, ua = key
requests.post(API, json={
'ip': ip, 'ua': ua, 'ttl': 600
}, headers={
'X-Token': TOKEN
})
del session[key]
if __name__ == '__main__':
while True:
line = input()
ingest(line)
if int(time.time()) % 30 == 0:
decide()
部署小贴士
- 旁路流量:用
iptables -j TEE把清洗中心回源口的 1% 流量复制给脚本,不影响主链路; - 模型更新:每天凌晨跑一次离线训练,生成新的
bot_model.pkl,然后热替换; - TTL 设计:封禁 10 min 足够让爬虫换 IP,但真用户即使被误杀,刷新页面即可恢复。
效果
上线 48 h:
- 404 占比从 30% 降到 2.1%;
- 日活不降反升 1.8%(推测是爬虫被挡掉后,服务器响应更快);
- 高防 IP 的账单里,回源流量少 6 TB,意外省下 200 多块。
写在最后
这次实验最大的收获是:与其在规则里“猜”爬虫长什么样,不如把判断逻辑外置到一个能持续学习的边缘节点。群联的清洗中心给出了实时封禁 API,让大家可以把模型结果直接落地,而不用改一行业务代码——这比传统“先打日志、再人工加黑名单”的节奏快了整整一个量级。

浙公网安备 33010602011771号