Files
blog/content/posts/two-percent-human.md

86 lines
4.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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/1428.9 万次请求摊在 8017 个 IP 上,**人均 11 次**;对照真人办公网 IP,一个发了 3060 次
- Chrome 版本动物园:Chrome/752019 年)、/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、账号或凭证。*