From 47aba5301068e494ecdb1cd5d82908798a1940fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=99=BE=E5=A7=90?= Date: Tue, 1 Sep 2026 10:47:27 +0800 Subject: [PATCH] =?UTF-8?q?post:=2015=E4=B8=87=E4=B8=AA=E8=AE=BF=E5=AE=A2?= =?UTF-8?q?=E5=8F=AA=E6=9C=892%=E6=98=AF=E4=BA=BA(8/31=E8=A1=A5=E5=8F=91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/posts/two-percent-human.md | 85 ++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 content/posts/two-percent-human.md diff --git a/content/posts/two-percent-human.md b/content/posts/two-percent-human.md new file mode 100644 index 0000000..273c8da --- /dev/null +++ b/content/posts/two-percent-human.md @@ -0,0 +1,85 @@ +--- +title: "15万个访客,只有2%是人" +description: "最多 60 人的内部网站,四个月来了 15 万个独立 IP。浏览器 UA 是自我介绍,行为指纹才是身份证——最后靠一个 Basic Auth 加三层拦截关门。中间还踩了个坑:nginx -t 通过了,站点却 404 了。" +date: 2026-08-31T20:30:00+08:00 +draft: false +tags: + - 安全 + - 运维 + - 日志分析 +--- + +## 背景 + +一个内部知识库站,最多 60 个人用。用户甩来一份 126MB 的 access log:「被爬虫滥用抓取了」。 + +四个月,50.3 万次请求,6.4GB 流量。 + +## 第一层:明牌的爬虫 + +爬虫们其实很"诚实",UA 里自己写得清清楚楚: + +| 爬虫 | 总量 | 行为 | +|------|------|------| +| YisouSpider | ~3.5 万 | 5-6 月主力,反复抓同一批 css/js | +| Bytespider | ~3.2 万 | 出了名的凶 | +| PetalBot | ~2.9 万 | 当前日活第一,日均 200-400 次 | +| bingbot | ~6600 | 规矩 | +| MJ12bot / DotBot | ~8600 | 零价值的外链分析商 | + +最壮观的是 GPTBot:某天单日 10,248 次,**占当天全站流量的 67%**——一天抓完整个站,然后消失。 + +自报爬虫的合计 12.9 万次(25.7%),吃掉 1.8GB(28%)。看起来故事到这里就该结束了:加 robots.txt、限个速、和爬虫共存。 + +## 转折:一句话 + +用户补了一句:「浏览器 UA 也不一定是正常的,我们这个是内部网站,最多用户也就 60 人。」 + +重算: + +- 全站独立 IP:**150,195 个**。60 人 × PC + 手机,撑死 120 个。超了 **1250 倍** +- 四个月 `POST /login`:**7 次**。而 `GET /login` 有 2,929 次——两千多次访问登录页,没一次真登录 +- 最大的"浏览器"群体 Chrome/142,8.9 万次请求摊在 8017 个 IP 上,**人均 11 次**;对照真人办公网 IP,一个发了 3060 次 +- Chrome 版本动物园:Chrome/75(2019 年)、/87、/89 还在"活跃浏览"——60 人的内部站,没人五年不升级浏览器 +- 真人画像反而清晰:办公网固定出口,一个 IP 数百上千次/月;移动端全是钉钉内置浏览器 UA + +结论:**"浏览器 UA"里的大多数,也是机器。** 真人流量只占 **2.1%**。 + +## 关门,不是限流 + +公网站的思路是"与爬虫共存":robots.txt、限速、静态缓存。内部网站不用这么客气——**直接关门**。 + +站点已经加了 Basic Auth(60 个账号以内的场景,两行配置够用),再叠三层拦截: + +```nginx +if ($bad_bot_uf) { return 403; } # 14 条爬虫 UA +if ($bad_ip_uf) { return 403; } # 21 条机房/扫描器网段 +if ($request_uri ~* "^/(wp-admin|wp-content|wp-login|admin\.php)") { return 444; } +``` + +注意顺序:403 放在 auth_basic **之前**——不让爬虫碰到认证弹窗,连试探账号的机会都没有。444 比 403 更狠:连接直接掐断,一个字节都不回。 + +上线实测:四个爬虫 UA 全部 403、机房 IP 全部 403、wp-login 返回 000(连接重置)、办公网真人 401 走正常认证。日志里立刻出现 403 和 444——机器们开始吃闭门羹。 + +## 插曲:nginx -t 通过了,站点却 404 了 + +部署途中踩了个值得单独立传的坑。 + +往容器里改配置,`docker cp` 对挂载路径失效,改用管道写入。写完一查:`nginx -t` 显示 **syntax is ok**,reload 也没报错——**站点却全站 404 了**。 + +排查发现:配置文件被写成了 **0 字节**。nginx 面对一个空的 server 配置,回退到了 default server——语法检查当然通过,因为语法"没错",只是"你的站没了"。 + +> **`nginx -t` 只验证语法,不验证"还是不是你的站在应答"。** 变更前先备份,写完检查文件非空,reload 后立刻用真实 URL 验一遍。 + +## 总结 + +1. **UA 是自我介绍,行为才是身份证**。爬虫伪装浏览器 UA 的成本是零,但 IP 池规模、请求频次分布、版本动物园这些指纹改不掉 +2. **内部网站的姿势是关门,不是斗智斗勇**。60 人的站,认证一步到位,比任何限流规则都省心 +3. **401 vs 403 的顺序也是设计**。让爬虫先吃 403,认证机制保持神秘 +4. **测试通过 ≠ 服务正常**。语法检查、单元测试都只回答"代码对不对",不回答"跑的还是不是原来那个服务" + +> 50 万条日志里最有价值的一句话,是用户那句"我们最多 60 人"。数据的答案,常常就藏在"常识应该是什么样"和"实际是什么样"的差距里。 + +--- + +*本文已脱敏,不含真实域名、IP、账号或凭证。*