跳到主内容
快讯直播
AI智模界
教程

给站点加一层钱包反击:识别 AI 爬虫、限速与高成本响应

适用场景

站点内容被大量自动化抓取器批量搬运,服务器带宽和 CPU 被吃满,而对方只承担极低的成本。这套方案适合有独立服务器或可控 Nginx 的站长、运维,通过「识别 → 分级限速 → 高成本响应 → 计费墙」四层动作,让合规的普通读者不受影响,让大规模抓取方付出时间与带宽代价。方案不依赖任何商业爬虫管理服务,全部在 Nginx 内部完成。

环境与前置条件

  • 操作系统:主流 Linux 发行版均可(Debian/Ubuntu、Rocky/Alma、CentOS Stream 等),需要 root 或 sudo 权限。
  • Web 服务器:Nginx,以官方文档当前版本为准。需要确认内置模块 http_limit_req_module、http_limit_conn_module、http_map_module、http_geo_module、http_realip_module 均已编译进去,主流发行版软件包默认都带。用 nginx -V 2>&1 | tr ' ' '\n' | grep -E 'limit_req|limit_conn|http_map|http_geo|realip' 检查。
  • 辅助工具:curl 用于验证,jq 用于解析日志(可选但强烈建议)。
  • 资源建议:识别与限速本身几乎不占资源;真正消耗在「高成本响应」上,因为慢速传输会长时间占用连接。按每 1000 个并发慢速连接预留约 2MB 内存估算,并把 worker_connections 调到能承受的量级。磁盘上为投喂文件预留几十 MB 即可,不要生成超大文件。
  • 前置确认:如果站点前面有 CDN 或负载均衡,必须先配好 real_ip 模块,否则所有请求的 $remote_addr 都是同一个代理 IP,限速会误伤全体用户。

分步骤部署

第 1 步:把访问日志改成可解析的 JSON

先让日志能算账,后面所有分析和封禁都靠它。新建 /etc/nginx/conf.d/ai-wallet-log.conf(确认它被 http {} 块 include 到):

```nginx

log_format wallet escape=json

'{"ts":"$time_iso8601","ip":"$remote_addr","m":"$request_method",'

'"u":"$request_uri","s":$status,"bytes":$body_bytes_sent,'

'"ua_class":$ua_class,"head_class":$head_class,"rt":$request_time,'

'"ua":"$http_user_agent"}';

```

escape=json 必须写,否则 User-Agent 里出现引号会让整行 JSON 失效。

第 2 步:按 User-Agent 识别「自报家门」的智能体

新建 /etc/nginx/conf.d/ai-wallet-map.conf。注意 map 只能写在 http 块级别,不能写进 server 或 location:

```nginx

map $http_user_agent $ua_class {

default 0;

"" 2; # 完全没有 UA,优先级最高

"~*GPTBot" 1;

"~*OAI-SearchBot" 1;

"~*ChatGPT-User" 1;

"~*ClaudeBot" 1;

"~*anthropic-ai" 1;

"~*CCBot" 1;

"~*Bytespider" 1;

"~*AI 词典:Perplexity">PerplexityBot" 1;

"~*meta-externalagent" 1;

"~*Amazonbot" 1;

"~*Applebot-Extended" 1;

"~*cohere-ai" 1;

"~*Diffbot" 1;

"~*ImagesiftBot" 1;

"~*YouBot" 1;

}

```

0 表示未识别,1 表示声明自己是 AI 抓取器,2 表示空 UA。这里的片段只是常见示例,各家 UA 会调整,请以各家官方文档当前的 UA 说明为准,并把这张表当作需要定期更新的配置项。注意不要误伤 Googlebot、Bingbot 这类搜索爬虫,除非你确认要这么做。

第 3 步:用请求头指纹识别「不说实话」的抓取器

伪装 UA 很容易,但完整模拟浏览器要难得多。真实浏览器几乎总会带 Accept、Accept-Language,现代浏览器还会带 Sec-Fetch-* 系列头;而大量脚本的 Accept 是 */* 或干脆为空。

```nginx

map $http_accept $accept_class {

default 0;

"" 1;

"~*^\*/\*\s*$" 1;

}

map "$accept_class|$http_accept_language" $head_class {

default 0;

"1|" 1;

}

```

含义是:Accept 为空或只有 */*,且没有 Accept-Language,就记为可疑。这个组合对正常浏览器几乎不会触发,对命令行脚本触发率较高。

第 4 步:配置限速与并发上限

限速分两类:声明式爬虫直接进计费墙,可疑流量进慢速通道。关键在于只给可疑流量计数,正常用户完全不受影响。

```nginx

key 为空字符串时,Nginx 不计入该 zone,正常用户因此不受限

map $head_class $suspect_key {

0 "";

1 $binary_remote_addr;

}

limit_req_zone $suspect_key zone=ai_suspect:10m rate=20r/m;

limit_conn_zone $suspect_key zone=ai_conn:10m;

limit_req_zone $binary_remote_addr zone=all_req:20m rate=60r/s;

limit_conn_zone $binary_remote_addr zone=all_conn:20m;

```

rate=20r/m 大约每 3 秒一个请求,比正常人类阅读节奏还慢。all_req 是兜底防 CC,正常用户碰不到。

第 5 步:生成投喂文件(高成本响应)

让可疑抓取器每次请求都拿到一个体积不小、内容无关的文本,并限速发送。它要么浪费带宽,要么花更长的解析时间。

```bash

sudo mkdir -p /var/www/tarpit

python3 - <<'PY'

import random

words = ["market","window","harbor","signal","granite","orbit","timber",

"ledger","compass","meadow","quartz","lantern","atlas","bridge"]

with open("/var/www/tarpit/feed.txt", "w") as f:

for i in range(20000):

f.write(f"{i}: " + " ".join(random.choices(words, k=18)) + "\n")

PY

ls -lh /var/www/tarpit/feed.txt

```

文件控制在 1–2MB 即可。配合下面的 8KB/s 限速,单个请求会被拖住两分钟左右,足够劝退大多数低成本爬虫。

第 6 步:在站点 server 块中组合处置策略

```nginx

server {

listen 443 ssl;

http2 on;

server_name example.com;

root /var/www/site;

default_type text/plain;

声明式 AI 抓取器:直接返回计费墙

if ($ua_class = 1) {

return 402 "402 Payment Required\nAutomated retrieval is billed. See /pricing for terms.\n";

}

头部指纹异常:转到慢速投喂

if ($head_class = 1) {

rewrite ^ /tarpit/feed.txt last;

}

location / {

limit_req zone=all_req burst=200 nodelay;

limit_conn all_conn 60;

limit_req_status 429;

try_files $uri $uri/ =404;

}

location = /tarpit/feed.txt {

internal;

limit_rate 8k;

limit_rate_after 16k;

}

location = /robots.txt {

try_files /robots.txt =404;

}

}

```

limit_rate_after 16k 表示先快速发 16KB(让对方的客户端认为连接正常),之后才降到 8KB/s。

第 7 步:在 robots.txt 里把「计费墙」说清楚

```txt

User-agent: GPTBot

Disallow: /

User-agent: CCBot

Disallow: /

User-agent: *

Allow: /

Crawl-delay: 1

```

robots.txt 只是君子协定,但不写它会让「计费墙」缺少说明。同时准备一个 /pricing 页面,写明允许的抓取方式和联系方式,避免误伤合规方。

第 8 步:按日志生成封禁名单

新建 /usr/local/bin/ai-blocklist.sh:

```bash

#!/usr/bin/env bash

set -euo pipefail

LOG=/var/log/nginx/wallet.log

OUT=/etc/nginx/conf.d/ai-blocklist.conf

THRESHOLD=200

cp "$OUT" "${OUT}.bak" 2>/dev/null || true

{

echo "# generated $(date -Iseconds)"

jq -r 'select(.s == 429 or .s == 402) | .ip' "$LOG" \

| sort | uniq -c | awk -v t="$THRESHOLD" '$1 >= t {print $2 " 1;"}'

} > "$OUT"

nginx -t && nginx -s reload

```

配套在 http 块加:

```nginx

geo $blocked_ip {

default 0;

include /etc/nginx/conf.d/ai-blocklist.conf;

}

```

再在 server 块顶部加 if ($blocked_ip) { return 429; }。挂到 cron 里每 30 分钟跑一次。阈值 200 按站点体量调整,先观察一周再收紧。

验证部署是否成功

```bash

sudo nginx -t

sudo nginx -s reload

```

预期输出 syntax is ok 与 test is successful。然后逐条验证:

```bash

1. 声明式 AI 爬虫 → 应返回 402

curl -s -o /dev/null -w '%{http_code}\n' -A 'GPTBot/1.0' https://example.com/

2. 伪装成 curl 但没有 Accept-Language → 应命中慢速通道,耗时应明显变长

curl -s -o /dev/null -w '%{http_code} %{time_total}\n' \

-A 'Mozilla/5.0 (compatible)' -H 'Accept: */*' https://example.com/

3. 模拟真实浏览器 → 应立即返回 200,耗时在百毫秒级

curl -s -o /dev/null -w '%{http_code} %{time_total}\n' \

-A 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' \

-H 'Accept: text/html,application/xhtml+xml' \

-H 'Accept-Language: zh-CN,zh;q=0.9' https://example.com/

4. 连续请求验证限速 → 后续请求应出现 429

for i in $(seq 1 40); do

curl -s -o /dev/null -w '%{http_code} ' -H 'Accept: */*' https://example.com/

done; echo

5. 确认日志字段齐全

tail -n 1 /var/log/nginx/wallet.log | jq .

```

预期结果:第 1 条为 402;第 2 条 time_total 显著大于第 3 条;第 4 条输出中能看到若干 429;第 5 条能正确解析出 ua_class、head_class 字段。

常见报错与解决

报错:nginx: [emerg] "map" directive is not allowed here

→ 原因是 map、limit_req_zone、limit_conn_zone 被写进了 server 或 location 块。

→ 解决:把这些指令移到 http 块级别(放进 conf.d/*.conf,或 nginx.conf 的 http {} 内),然后 nginx -t 确认。

报错:nginx: [emerg] unknown limit_req zone "ai_suspect"

→ 原因是 limit_req 引用的 zone 名与 limit_req_zone 定义的名字不一致,或 zone 定义所在文件没有被 include。

→ 解决:grep -rn "limit_req_zone" /etc/nginx/ 核对名称;确认 nginx.conf 中 include /etc/nginx/conf.d/*.conf; 位于 http {} 内。

现象:正常用户被大量 429,投诉页面打不开

→ 原因一是站点前面有 CDN,$remote_addr 全是回源 IP,所有人共用一个限速桶;原因二是 head_class 规则过宽,把某些 API 客户端或老旧浏览器判成可疑。

→ 解决:先配 real_ip;再临时把 limit_req 的 rate 放宽,用 tail -f 观察 wallet.log 里的 head_class 分布,确认误伤来源后再收紧规则。

现象:站点出现大量超时,worker_connections are not enough

→ 原因是慢速投喂把连接长时间占住,连接池被耗尽。

→ 解决:把 limit_conn 的上限调低、把 feed.txt 缩小、把 limit_rate 调大,或在 http 块提高 worker_connections 并适当增加 worker_processes。

现象:limit_req 触发后返回 503,监控告警「服务不可用」

→ 原因是 Nginx 默认用 503 表示限流。

→ 解决:在 location 中加 limit_req_status 429;,并同步更新监控规则,把 429 单独统计而不是算进 5xx。

后续维护

  • 规则更新:AI 抓取器的 UA 与 IP 段会变,建议每季度对照各家官方文档更新一次 $ua_class 映射表,并保留变更记录,方便回滚。
  • 名单备份:ai-blocklist.conf 由脚本覆盖生成,脚本里已做 .bak 备份;把备份目录纳入日常备份范围,避免误封后无从追溯。
  • 日志轮转:给 wallet.log 配置 logrotate,按天切割、保留 14–30 天并压缩。分析脚本只读当天和前一天的文件,避免日志变大后 jq 拖慢机器。
  • 升级流程:升级 Nginx 前先在测试环境跑一遍完整配置,nginx -t 通过后再灰度。limit_* 与 map 都是稳定模块,但版本间对 if 块内指令的处理有差异,以官方文档当前版本说明为准。
  • 监控指标:重点盯四个数——402 与 429 的占比、平均 request_time、出网带宽、并发连接数。若平均响应时间随 429 一起上涨,说明慢速通道占比过高,需要收紧 limit_conn。
  • 白名单机制:为监控探针、合作方、搜索引擎保留独立放行规则(例如按 Header 中的密钥或固定 IP 段),并在改动策略前先确认白名单仍然生效。
  • 成本核算:每隔一段时间统计投喂通道消耗的带宽,并与抓取量对比。如果对方改用分布式 IP 绕过限速,说明单点限速已经不够,此时应把策略前移到 CDN 或边缘层,配置思路不变。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。