首页博客实战笔记我的小站被爬虫刷崩了:一次「筛选迷宫」爬虫洪水的排查与加固实录
实战笔记

我的小站被爬虫刷崩了:一次「筛选迷宫」爬虫洪水的排查与加固实录

📅 2026年7月7日 ✏️ xiaoheiyun 🕐 1 分钟阅读

那天下午,我的网站没了。

不是变慢,是彻底没了——网页打不开、SSH 连不上、连 ping 都没有回应,整台服务器像被拔了电。我第一反应是被黑了。但真相更荒诞:它是被”正常的访问”活活刷死的。

这是一次真实的排查实录:一个没多少流量的小站,怎么会被爬虫刷到整机瘫痪?我一步步挖下去,挖到一个很多自建站的人根本没设防的东西——“筛选迷宫” + 分布式假爬虫。如果你也自己搭站、跑服务器,这篇能帮你在崩之前就把门关上。

一、症状:一台”满血”服务器,突然集体失联

崩的方式很有辨识度:

  • 网页先是 502,接着直接超时;
  • SSH 卡死、面板进不去、ping 无响应;
  • 重启能缓几分钟,然后又崩
  • 好不容易挤进去看一眼,load average 冲到 130+(正常是个位数)。

这个画像很关键:它不是”被一次性打挂”,而是资源被持续吃光——CPU 和内存被占满,正常请求连排队的机会都没有。是洪水,不是炸弹

二、第一个反直觉:不是 DDoS,是爬虫

翻访问日志,我以为会看到某个 IP 的请求量暴涨——没有。请求量不算离谱,UA 大多还是”正常浏览器”。真正扎眼的是 URL:满屏都是带一长串筛选参数的组合,像这样:

/tools/?scene=写作&cat=对话&price=free&speed=fast&sort=hot
/tools/?scene=写作&cat=对话&price=free&speed=fast&sort=new
/tools/?scene=编程&cat=对话&price=paid&speed=fast&sort=hot
...(无穷无尽)

这就是经典的“筛选迷宫”(faceted navigation trap):一个列表页,挂上几个筛选维度,就能裂变出天文数字的 URL。

一个列表页 /tools/ ,挂上 5 个筛选维度: 场景 ×8 × 分类 ×12 × 价格 ×5 × 速度 ×4 × 排序 ×6 = 11,520 个「不同」URL 每一个组合,爬虫都当成一个新页面去抓 而这些页面没法缓存(组合无穷),每次抓取都要 PHP + MySQL 现算 /tools/?scene=a&cat=b&price=free&speed=fast&sort=hot /tools/?scene=a&cat=b&price=free&speed=fast&sort=new …×11518 更多
图1:一个页面,怎么裂变成一万多个”新页面”

三、致命的一环:这些页面缓存不了

裂变出上万个 URL 还不是最要命的。要命的是——它们没法被缓存。

普通页面第一次访问后可以进缓存,后面直接吐缓存、几乎不耗资源。但筛选组合是无穷的,缓存命中率约等于零。于是每一个组合请求,都要实打实地跑一遍 PHP、查一次数据库

而每个 PHP 请求会占用进程池里的一个”工人”。爬虫把上万个组合并发地砸过来,进程池瞬间被占满 → 后面的真人请求全部排队 → 排队又占内存 → 内存吃穿 → 整机雪崩。崩的不是某个页面,是整台机器

四、更阴的一层:假 Mac 分布式爬虫

我进一步分析这波流量,发现两个特征:

  • UA 清一色是 Mac 上的 Safari——整整齐齐,像复制粘贴出来的;
  • 但它们来自几百个不同的 IP,分散在各地。

真人不会这样。这是典型的分布式爬虫 / 住宅代理:租一大批家用宽带 IP,套上一个正常 UA,专门绕开”单 IP 限速”这种传统防护——你封了一个 IP,它换一个接着来。这波流量连续十几天,占了我两到五成的总请求。它不在乎你死活,只想把你的数据爬干净。

这就带出一个两难:我不能简单按 UA 或 IP 一刀切。因为真正的 Googlebot、Bingbot 也自称 bot,我要是把它们误伤了,等于自断收录。得先能分清真假

五、怎么分清”该放的搜索引擎”和”该拦的假爬虫”:反向 DNS

答案是反向 DNS 双向校验——这是 Google、Bing 官方都推荐的验真方法,假爬虫伪造不了:

一个自称 Googlebot 的请求 IP 203.0.113.45 · UA: Googlebot ① 反向解析 (rDNS):IP → 主机名 查这个 IP 的 PTR 记录 ② 把主机名再正向解析 → 看是否回到原 IP 且域名是否属于官方(*.googlebot.com / *.search.msn.com) ✓ 正反一致 + 官方域名 真 Googlebot —— 放行 ✗ 查不到 / 域名不符 / 对不上 假爬虫 —— 444 掐断
图2:反向 DNS 双向校验——真假搜索引擎一验就现形

关键点:假爬虫可以伪造 UA,但伪造不了它 IP 的反向 DNS 记录。它反查要么没记录,要么域名根本不属于官方,正反一对就露馅。真搜索引擎则永远对得上。

六、加固三板斧(可直接抄作业)

① 行为拦截(治本):不看你是谁,看你在干嘛

与其纠结”这是谁”,不如看”它在干嘛”。真人和真搜索引擎都不会去枚举无穷的筛选组合,所以:

# nginx:带 4 个及以上筛选参数的请求,直接 444(连响应都不给,掐断连接)
if ($request_uri ~ "^/[^?]+\?([^&]*&){3,}") { return 444; }

# 非人类 UA + 带 ? 参数的筛选页,也 444
if ($http_user_agent ~* "bot|spider|crawler|python|scrapy") { set $bad "1"; }

444 是 nginx 的特殊返回码:不回任何内容、直接关闭连接,比 403 更省资源——连一个字节都不浪费给它。

② 按 URL 特征限并发(缓冲)

分布式爬虫会换 IP,但它要抓的 URL 特征躲不掉。所以别只按 IP 限速,要给”筛选类 URL”设一个全局并发上限

# 给带筛选参数的请求设全局并发上限 + 限速,无论来自哪个 IP
limit_conn  faceted  20;
limit_req   zone=dyn burst=10 nodelay;   # 从 burst=20 收到 10

这样即使一万个 IP 一起冲筛选页,能同时在跑的也就那么几个,其余排队或被拒,后端永远不会被打穿

③ 进程池:max_children 不是越大越好

这条最反直觉。我的 php-fpm max_children 之前设成了 300,以为”能扛更多并发”。真相是:它反而是崩站的帮凶。

算笔内存账你就懂了:

max_children × 单进程内存 = 峰值内存需求 结果
300 ~60 MB ~18 GB 被打满 → 内存吃穿 → OOM 整机瘫
160 ~60 MB ~9.6 GB 扛得住 → 满了就”拒绝服务”,不拖垮全机

要点:max_children 应该设成“内存扛得住的上限”,而不是”我希望的并发数”。设对了,最坏情况是它开始拒绝新请求;设太大,最坏情况是整台机器一起死。前者可恢复,后者要重启。

七、结果 & 你的自查清单

三板斧上齐后,load 从 130+ 掉回个位数。之后再有爬虫来冲筛选迷宫,全被 444 挡在门外,后端稳如泰山。

如果你的站也莫名卡死、崩溃,花十分钟按这个清单查:

  1. 看是”被吃满”还是”被打挂”load/内存是不是被占光。是洪水不是炸弹。
  2. 翻日志看 URL:是不是全是带一堆筛选参数的组合。
  3. 查 UA 分布:是不是一种 UA + 几百个 IP(分布式特征)。
  4. 反向 DNS 抽查:随手挑几个”自称 Googlebot”的,验证是不是真的。
  5. 上三板斧:444 拦无限筛选 + 按 URL 限并发 + max_children 调到内存扛得住。

这次崩站教会我最重要的一件事:威胁不一定长着”攻击”的样子。把你刷崩的,可能就是一堆套着正常 UA、慢慢啃你数据的爬虫。它们不违法、不报警,却能让你的站在某个下午毫无征兆地黑屏。

如果你的站也遇到莫名卡死、502、负载爆高、疑似被爬虫刷,或者你想在崩之前就把这些门关好——可以找我聊。这套”筛选迷宫 + 分布式爬虫”的坑,我刚刚踩透,也把加固方案跑通了。

x
xiaoheiyun
NavXD 内容团队成员

获取更多 AI 工具洞察

订阅 NavXD Pro,每月获取完整趋势报告、深度分析和独家数据

了解 Pro →