拦 恶意爬虫 拦不干净,很大一部分原因是它在暗处、你在明处。本文用 9 个问答讲清什么是爬虫陷阱、它凭什么能主动识别恶意爬虫、怎么布置隐藏的陷阱链接、命中之后如何自动封禁,以及怎样避免误伤搜索引擎蜘蛛造成 SEO 事故。
Q:什么是爬虫陷阱?它凭什么能主动识别恶意爬虫?
爬虫陷阱是在站点里放一些正常用户永远看不到、也不会点击的链接和路径,只有按规则遍历链接的爬虫才会走到。它的识别逻辑建立在爬虫的工作方式上:
- 爬虫的工作就是解析 HTML、提取页面里的所有链接、再逐个跟进。你只要藏一个只有它看得见的链接,谁跟进谁就暴露了。
- 合规的搜索引擎蜘蛛会遵守 robots.txt,把陷阱路径写进 Disallow,正规蜘蛛会跳过,不守规矩的爬虫照样会抓。
- 和 UA 黑名单的区别很关键:黑名单是被动挨打,对方换个 User-Agent 就绕过了;陷阱是主动筛选,不管它 UA 写成什么,行为不合法就会被标记。
Q:爬虫陷阱有哪几种常见形态?
- 隐藏链接:页面里放一个用样式位移到屏幕外的链接,正常用户看不见,但页面源码里存在。
- 无入口的深层路径:不放在导航里、不放进 sitemap、不被任何正常页面引用,只在 robots.txt 中声明禁止抓取。
- 诱饵页面:路径名看起来像后台或数据导出(例如
/export/users.csv),实际是个空壳,只负责记录访问者的 IP、UA 与时间。 - 链接黑洞:给可疑爬虫返回一个看似有无限翻页、实际没有内容的假列表页,把它的抓取额度耗在一座迷宫里。
Q:最基础的陷阱怎么布置?
# 1) robots.txt 里声明陷阱前缀,让合规蜘蛛主动跳过
User-agent: *
Disallow: /.trap/
Disallow: /trap-*
# 2) Nginx:标记命中陷阱的请求
map $request_uri $is_trap {
default 0;
"~^/\.trap/" 1;
"~^/trap-[a-f0-9]{8}/" 1;
}
# 3) 陷阱单独一份日志,只记录命中陷阱的请求
log_format trap '$remote_addr - [$time_local] "$request" $status '
'UA="$http_user_agent" REF="$http_referer"';
server {
access_log /var/log/nginx/trap.log trap if=$is_trap;
# 命中即断开连接,不返回任何页面,避免被当成普通 404 反复重试
if ($is_trap = 1) { return 444; }
}
- if 在 Nginx 里不是通用流程控制,里面只能放一条 return 或 rewrite,不要写多行逻辑。
- 444 是 Nginx 的私有状态码,直接关闭连接、不给任何响应,比返回 403 更少泄露信息,也不会被当成一次成功探测。
- 陷阱路径必须随机且不可预测(例如
/.trap/9f3a1c7b/),能被猜中的路径就不算陷阱。
Q:隐藏链接放在哪里最有效?
放在每个页面都会渲染的公共区域,例如页脚模板、侧栏、或者一个用样式移出可视区的容器:
<div aria-hidden="true"
style="position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden">
<a href="/.trap/9f3a1c7b/">网站地图</a>
<a href="/.trap/a7d20e51/">联系我们</a>
</div>
- 用 left:-9999px 位移隐藏,比
display:none覆盖面更广,因为部分爬虫解析器会剔除 display 为 none 的节点。 - 链接文字用「网站地图」「联系我们」这类爬虫最愿意跟进的词,命中率明显更高。
- 链接要出现在模板层,这样新发布的每一页都会自动带上陷阱,不用逐篇加。
Q:命中陷阱之后怎么自动封禁?
# /etc/fail2ban/filter.d/webtrap.conf
[Definition]
failregex = ^<HOST> - \[.*\] "(?:GET|POST) /(?:\.trap/|trap-[a-f0-9]{8}/)
ignoreregex =
# /etc/fail2ban/jail.d/webtrap.local
[webtrap]
enabled = true
filter = webtrap
logpath = /var/log/nginx/trap.log
maxretry = 1
findtime = 86400
bantime = 604800
action = iptables-ipset-proto6-allports[name=webtrap]
- maxretry = 1:命中陷阱就是明确信号,不需要重试次数,一次即封。
- bantime 建议 7 天:恶意爬虫的 IP 多来自代理池与云主机,封几小时等于没封;但如果确认是住宅宽带段(NAT 出口背后可能是真实用户),宁可降为 1 小时再观察。
- 应急时也可以手工批量导入,只封 Web 端口,保留管理通道:
grep -o '^[0-9.]*' /var/log/nginx/trap.log | sort -u > trap_ips.txt
ipset create webtrap hash:ip timeout 604800 maxelem 1000000 -exist
while read -r ip; do ipset add webtrap "$ip" -exist; done < trap_ips.txt
iptables -I INPUT -p tcp -m multiport --dports 80,443 -m set --match-set webtrap src -j DROP
Q:怎么确认命中陷阱的不是搜索引擎蜘蛛?
陷阱机制本身已经做了一道过滤:合规蜘蛛遵守 robots.txt,不会去抓被 Disallow 的路径。但现实中仍有部分引擎(以及被转卖的蜘蛛 IP 段)不完全遵守规则,所以还要做二次校验:
# 反向解析:域名必须与官方一致
dig +short -x 66.249.66.1
# 再与官方公布的蜘蛛 IP 段比对(以 Google 为例)
curl -s https://developers.google.com/search/apis/ipranges/googlebot.json \
| grep -o '"ipv4Prefix": *"[^"]*"' | cut -d'"' -f4
- 校验逻辑是先反解、再正解,正向解析结果必须与原 IP 一致。只做反解是可以伪造的:在自己控制的 DNS 里把某个 IP 反解成搜索引擎域名,就能骗过只做反解的脚本。
- 这一步能救命。把百度或 Google 的蜘蛛误封,等于自己把 SEO 做死,恢复周期往往以月计。
Q:爬虫陷阱有哪些必须避开的坑?
- 不要把陷阱地址提交给搜索引擎,也不要放进 sitemap。陷阱的作用是拦截,一旦它进了索引,性质就完全变了。
- 不要用陷阱做 SEO 作弊:早年那种靠隐藏链接给搜索引擎喂假链接的「蜜罐 SEO」是由黑手法,和本文说的拦截用途不是一回事,别混为一谈。
- 不要做成真正的无限死循环:黑洞只对可疑 UA 返回,如果正常用户也能点到,体验会非常差。
- 不要让陷阱日志污染主日志:单独一份文件,否则会干扰日常的流量与统计口径。
- 不要只放一条路径:单路径容易被跳过,多路径、多形态组合才有覆盖面。
Q:大站点怎么规模化部署?
- 陷阱路径每站随机生成,但统一前缀(如
/.trap/),方便 Nginx 与 fail2ban 用同一条规则匹配。 - 日志集中采集(Filebeat、rsyslog)到一处,统一生成封禁清单,再下发到各节点,避免每个站点各封各的。
- 条件允许时把判断放到 CDN 或 WAF 边缘节点:封禁更及时,也不会给源站增加额外压力。
- 与既有规则分工协作:陷阱负责抓出低频、慢速、伪装成正常用户的爬虫,限速与 UA 规则负责挡住高频粗暴的,两者互补,别指望一招解决全部问题。
Q:还有什么补充建议?
- 陷阱是筛子而不是墙:它的价值是主动发现、留下证据、抬高对方的抓取成本。
- 命中记录里务必保留 UA、IP、Referer 与时间,这些就是后续投诉与取证的原始素材。
- 定期看陷阱日志:命中量突然上涨,通常意味着有人开始批量扫你的站;长期为零也不等于安全,可能只是藏得不够隐蔽。
- 把陷阱和源站隐藏、静态资源分离配合起来,别让爬虫顺着 CDN 回源绕过限制直连源站。
- 一句话总结:爬虫陷阱的核心逻辑是「放一条正常用户走不到、合规蜘蛛不会走的路,谁走进去谁就是恶意爬虫」,命中即封、单独留证、绝不误伤搜索引擎。