反爬规则写完就没人碰,UA 换代、IP 段迁移、新路径漏配,拦截率会悄悄归零。本文用 10 个问答讲清反爬规则怎么统计命中、怎么清理僵尸规则、怎么回归验证。
Q:反爬规则为什么会越用越失效?
- 规则不是一次性资产,是有保质期的耗材。失效通常来自五个方向:
- UA 换代:浏览器每四到六周升一次大版本。把 UA 写成「Chrome/120」再写个精确等于,半年后规则全废。
- 蜘蛛 IP 段迁移:引擎会新增和回收网段。白名单不更新,要么误伤真蜘蛛,要么把已经转手的 IP 继续放行。
- 业务新增路径:上了新目录、新接口,规则只写在老的
location里,新路径一条都不生效。 - 规则互相覆盖:Nginx 是从上往下匹配的,前面的规则先命中并
return了,后面写得再漂亮也不会执行。 - 业务变更:换 CDN、加子域、上 HTTPS 之后
set_real_ip_from没跟着补,日志里全是节点 IP,按 IP 做的规则全部失灵。
Q:怎么统计出每条规则到底拦了多少?
- 核心做法只有一步:给规则打标签,把标签落进访问日志,然后按周聚合。没有标签,你永远只知道「总共拦了多少」,不知道「哪一条在干活」。
map是零成本的,命中判断在变量求值时完成,不额外发起子请求。
map $http_user_agent $bot_rule {
default "";
"~*SemrushBot" "ua_semrush";
"~*AhrefsBot" "ua_ahrefs";
"~*MJ12bot" "ua_mj12";
"~*(python-requests|curl)" "ua_tool";
"" "ua_empty";
}
map $bot_rule $bot_blocked {
"" 0;
default 1;
}
log_format bot '$remote_addr - [$time_local] "$request" $status '
'rule=$bot_rule blocked=$bot_blocked rt=$request_time';
- 日志落好之后,一条
awk就能出周报:
awk '{for(i=1;i<=NF;i++){if($i ~ /^rule=/){split($i,a,"="); if(a[2] != "") c[a[2]]++}}}
END{for(k in c) printf "%8d %s\n", c[k], k}' /var/log/nginx/bot.log | sort -rn
- 把结果贴进周报,你立刻能看出:哪条规则一天拦几万次、哪条一周零命中。零命中的那批就是需要复核的对象。
Q:命中 0 的规则一定是废规则吗?
- 不一定,先分清三种情况再动手删:
- 真没流量:低频爬虫几个月来一次。这类规则留着不亏,但要确认它真的会被匹配。
- 被前面的规则先拦了:两条规则覆盖同一批 UA,后一条永远轮不到。这种情况删掉后一条不算损失,但顺序要重新排。
- 规则本身写错:正则漏了转义、大小写没处理、全角空格混进去、
map里用了~却写成~ *。这种规则上线即死,日志里零命中,最容易被当成「没问题」。 - 判别方法:把它临时挪到规则链最前面,并且只记日志不拦截,跑满 24 小时看有没有命中。
Q:哪些规则应该直接删掉?
- 满足下面任意一条,就可以进清理清单:
- 连续四周零命中,且近 90 天 UA 榜单里查不到对应客户端。
- 指名的 UA 已经不存在,或者该爬虫早已停止运营。
- 已经被 CDN 或 WAF 侧的同名规则覆盖,源站再拦一遍属于重复劳动,还会让排查时分不清是谁拦的。
- 两条规则判定条件完全等价,只是写法不同。
- 清理顺序:先注释保留七天,确认拦截量曲线没有变化,再彻底删掉。上来就
rm是给自己挖坑。
Q:蜘蛛 IP 段多久更新一次,能自动化吗?
- 能。Google 和 Bing 都有官方 JSON,可以直接拉;百度没有公开 JSON,要用反向解析 PTR 校验。
# ① Google 官方 IP 段
curl -s https://developers.google.com/search/apis/ipranges/googlebot.json \
| python3 -c 'import json,sys;d=json.load(sys.stdin);print("\n".join(p["ipv4Prefix"] for p in d["prefixes"] if "ipv4Prefix" in p))' \
> /tmp/googlebot.txt
# ② 百度蜘蛛:PTR 必须落在 crawl.baidu.com
for ip in 116.179.32.1 220.181.108.1; do
echo "$ip -> $(dig +short -x $ip)"
done
# ③ 生成 geo 白名单并热加载
{
echo "geo \$realip_remote_addr \$is_engine {"
echo " default 0;"
sed 's/.*/ & 1;/' /tmp/googlebot.txt
echo "}"
} > /etc/nginx/conf.d/engine_geo.conf
nginx -t && nginx -s reload
- 节奏建议:官方 JSON 每天拉一次做 diff,有变化才 reload;PTR 校验每月抽查一批;百度侧每次收到「抓取异常」告警时立刻复核。
- 重点:白名单要按 IP 段而不是按 UA。UA 是客户端自己写的,IP 段你说了不算。
Q:规则改完怎么确认没误伤真蜘蛛?
- 三步回归,缺一步都可能把收录搞掉:
- 抽样请求:拿官方 UA 配真实引擎 IP,对首页、栏目页、详情页各打一次,必须全部 200。用
curl --resolve强制打到源站,绕开 CDN 才验证得到源站规则。 - 灰度只记不拦:新规则先把
$bot_blocked固定为 0,观察 24 到 48 小时的命中量。命中量异常大,说明规则写宽了。 - 上线后看两个指标:引擎抓取频次(站长平台)和源站 403 里来自引擎 IP 的条数。后者大于零就是误伤。
Q:规则越堆越多,会不会拖慢 Nginx?
map里几百条 UA 不会有明显开销,它本质是哈希查找加正则顺序匹配。真正的性能杀手是另外三样:- 每个请求跑一遍 Lua 正则;在
if里写return之外的复杂逻辑;用正则location铺满全站。 - 验证方法很简单:同一台机器,规则全关和全开各压一轮,看 P99 和 CPU 的差值。
- 正则别写
(.*)*这种回溯炸弹,一个恶意 UA 就能让 worker 卡住。
# 规则前后各压一轮,对比 P99 与 CPU
wrk -t4 -c100 -d30s --latency https://www.example.com/ | grep -E "Latency|Requests/sec"
Q:CDN、WAF、源站三层都有规则,怎么避免打架?
- 只维护一份来源:一个 txt 或 yaml,脚本生成三处的规则文件,各自带上版本号。
- 优先级固定:白名单 → 静态资源放行 → 限流 → UA 与 IP 拦截。任何一层都不许打乱这个顺序。
- 每层响应头带一个来源标记,比如
X-Block-By: edge或X-Block-By: origin。用户投诉时一眼就知道该去哪个控制台查。 - 绝对不要三层各维护一份黑名单。三个月后没人说得清哪份是准的。
Q:维护频率和清单怎么定?
- 每周:看命中 Top 10 和零命中清单,处理异常波动。
- 每月:核对官方 IP 段,巡检新上线路径有没有漏配规则。
- 每季度:全量复核,删僵尸规则,做一次压测确认开销可接受。
- 每次变更触发:换 CDN、加子域、上 HTTPS,重新检查
set_real_ip_from和白名单。
Q:还有什么补充建议?
- 每条规则旁边写清楚为什么加、什么时候加、谁加的。半年后接手的人靠这三句注释才能判断能不能删。
- 拦截率曲线要设告警。从 12% 突然掉到 2%,通常不是爬虫变乖了,而是规则失效或者被绕过了。
- 规则文件必须版本化,reload 前备份一份。
nginx -t通过再 reload,别用-s reload赌运气。 - 能靠限速解决的问题不要用黑名单。黑名单是精确打击,覆盖面越小越可靠;拿它当大扫帚用,误伤一定失控。
- 最后一句:反爬的效果不取决于你写了多少条规则,取决于还有多少条在生效。