⭐ 推荐:社区规则条款 V1.0

Nginx 防爬虫如何避免误封正常的 API 调用?

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-22 20:48 ·7 浏览 ·0 回复
内容摘要

Nginx 防爬虫避免误封正常 API 调用,应改为白名单先放行、按身份而非 IP 限流、超限返回 429 重试,并正确配置 real_ip 获取真实 IP,而非用 IP 或 UA 一刀切封禁。

避免误封的核心不是「把爬虫规则写得更狠」,而是把「封」拆成三层:白名单先放行、限流按身份而非按 IP 计数、超限只返回 429 让人重试——只要改了这三点,Nginx 防爬虫就不再会误伤正常 API 调用。

为什么直接封 IP 会误封正常 API 调用?

结论:凡是拿 $binary_remote_addr 或 User-Agent 做一刀切判断的防爬规则,都会误封至少三类正常流量。

第一类是 CDN、负载均衡和网关的回源流量。用户请求到你服务器时 $remote_addr 是 CDN 节点 IP,一个节点可能承载几千个真实用户,封掉它等于封掉这个地区。第二类是搜索引擎和监控探针,Googlebot、Bingbot 的抓取请求频率天生比人高,而 Uptime 监控、内部健康检查每秒都在打接口。第三类是移动端和 IoT 设备,它们往往走同一个出口 NAT,几十上百个真实用户共享一个公网 IP。

另一类常见误伤来自 UA 判断。if ($http_user_agent ~* "bot") 这种写法会把正常 APP 的内置 WebView(UA 里常带 ; wv)、自动化测试脚本、部分 SDK 一起拦掉,而这些恰恰是 API 的主要调用方。UA 可以被随意伪造,真正的恶意爬虫早就不缺一个像样的 UA 了。

Nginx 防爬虫的正确分层做法是什么?

结论:顺序必须是「白名单放行 → 按身份限流 → 按行为降级」,任何一步都不直接用 deny 永久封禁 IP。

第一层白名单,用 geo 模块把内网段、监控 IP、CDN 回源段标记为空字符串,空 key 不会被 limit_req_zone 计数,等于直接豁免。第二层限流,key 优先选 $http_authorization 或 $arg_api_key,把额度算在「应用/用户」维度上,其次才退回 IP 维度。第三层行为判定,才用 map 给「无 token + 高频 + UA 可疑」的组合打标记,降速而不是拒绝。

返回码也要分开:对可疑爬虫返回 444(直接断连,不消耗带宽)或 403;对限流超限的正常调用返回 429 并带 Retry-After,客户端 SDK 通常自带退避重试。返回 403 会让大量 HTTP 客户端直接判失败,这是"看起来是封禁、实际是误杀"的根源。

拿不到真实 IP 时怎么避免误封?

结论:反向代理场景下不配置 real_ip 模块,限流是按代理 IP 统计的,误封率接近 100%,这一步没有任何替代方案。

标准配置是 set_real_ip_from + real_ip_header,两者必须成对出现:

set_real_ip_from 10.0.0.0/8;
set_real_ip_from 173.245.48.0/20;   # 你的 CDN 回源段,按服务商列表填
real_ip_header X-Forwarded-For;
real_ip_recursive on;

注意三点。一是 set_real_ip_from 只填 CDN 服务商公布的回源段,填错等于允许任何人伪造 X-Forwarded-For 绕过限流。二是 Cloudflare 用 CF-Connecting-IP,real_ip_header 要改成对应字段。三是开启 real_ip_recursive on 后 Nginx 会从右往左剔除可信 IP,配合 $realip_remote_addr 做日志排查更方便。

同理,搜索引擎 IP 不要硬编码写死,Googlebot 和 Bingbot 都提供官方 JSON 段列表(googlebot.json、bingbot.json),定期同步到 include 文件,再配合正向-反向 DNS 校验(FCrDNS),才能既放行真蜘蛛又不放过伪装者。

具体配置怎么写?

结论:四个指令就够——limit_req_zone 定义额度、limit_req 挂到 location、limit_req_status 改返回码、limit_conn 防并发耗尽。

geo $limit_key {
    default          $binary_remote_addr;
    10.0.0.0/8       "";
    192.168.0.0/16   "";
}

map $http_authorization $rate_key {
    default  $limit_key;
    "~.+"    $http_authorization;   # 带 token 的按 token 计数
}

limit_req_zone  $rate_key zone=api:10m rate=20r/s;
limit_conn_zone $limit_key zone=conn:10m;
limit_req_status 429;
limit_conn_status 429;

location /api/ {
    limit_req  zone=api burst=40 nodelay;
    limit_conn conn 20;
    add_header Retry-After 1 always;
}

burst=40 nodelay 的含义是允许瞬时 40 个请求排队且不延迟处理,超过才触发限流——这一步专门吸收客户端启动时的并发峰值,不加 nodelay 会让正常调用被强制排队,表现为"接口变慢"而不是"被封",更难排查。zone=api:10m 的共享内存大约能存 16 万个 key,按 token 计数时够中小站点用。

限流阈值怎么定才不误伤?

结论:阈值不能拍脑袋,先用 limit_req_dry_run on(Nginx 1.17.1 及以上开源版支持)跑 3 到 7 天日志,再按 P99 的 2 到 3 倍取值。

limit_req_dry_run 开启后只记录不拦截,配合自定义 log_format 把 $limit_req_status 打进日志,就能统计出每个 key 的真实 QPS 分布和"会被拦"的比例。上线后盯一个指标:429 响应中携带有效 token 的请求占比,正常应低于 0.1%,超过 1% 说明阈值偏严,需要调大 rate 或 burst。

另外按接口分级:登录、短信、搜索接口从严,静态资源和列表接口放宽,用多个 limit_req_zone(如 login:10m rate=1r/s、api:10m rate=20r/s)分别挂到不同 location,比全站一个阈值安全得多。

正常流量的判定逻辑、日志字段设计、CDN 回源段维护这些细节,在 GJ站长论坛技术交流的「运维」版块里有不少同行的实战记录,配 Nginx 调优、故障排查一起看更省事。

总结一句:Nginx 防爬虫的正确姿势是「白名单豁免 + 按 token 限流 + 429 可重试 + dry-run 校准」,把不可逆的 IP 封禁换成可退避的限流,才是既不误伤正常 API、又能挡住抓取的做法。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2396.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~