86 lines
4.5 KiB
Markdown
86 lines
4.5 KiB
Markdown
---
|
||
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、账号或凭证。*
|