nginx通过map预定义恶意ua黑名单、if判断并return 403是最安全高效的反爬方式,支持忽略大小写正则匹配、空ua及扫描工具识别,且需在http块中配置map、server中调用,避免location内滥用if。

Nginx 本身不支持类似编程语言的复杂条件判断(如 if-else 嵌套、正则捕获后逻辑处理),但可以通过 if 指令结合 map、return、rewrite 和 deny 等机制,实现对恶意爬虫的识别与拦截。关键在于:**用 map 预定义规则更安全高效,避免在 location 中滥用 if;优先匹配 User-Agent、IP、请求头等特征;拦截动作选 return 403 或 rewrite ... redirect。**
用 map 模块精准识别恶意 User-Agent
map 是 Nginx 处理条件映射最推荐的方式,它在请求解析早期执行、性能好、且规避了 if 在 location 中的诸多限制(比如不能嵌套、某些变量不可用)。
- 在
http块中定义爬虫黑名单映射:
map $http_user_agent $bad_bot {
default 0;
~*python-requests 1;
~*curl/ 1;
~*wget 1;
~*Go-http-client 1;
~*Scanner 1;
~*(sqlmap|nmap|dirb) 1;
}- 在
server或location中拦截:
if ($bad_bot) {
return 403;
# 或重定向:rewrite ^(.*)$ https://example.com/robots.txt?bot=denied redirect;
}注意:map 中的正则以 ~* 开头表示忽略大小写匹配;$http_user_agent 是标准变量,自动获取请求头中的 User-Agent。
基于 IP 或 Referer 的补充拦截策略
单一靠 User-Agent 容易伪造,建议叠加 IP 黑名单或异常 Referer 判断。
- 用
geo或geoip2(需模块)标记可疑地区 IP,再配合map:
geo $bad_ip {
default 0;
192.168.1.100 1;
203.0.113.0/24 1;
}
map $bad_ip $block_request {
1 1;
default 0;
}
if ($block_request) { return 403; }- 拦截无 Referer 或异常 Referer 的请求(防直接爬取图片/API):
if ($http_referer = "") { return 403; }
if ($http_referer !~ "^https?://(www\.)?yourdomain\.com") { return 403; }⚠️ 注意:if 中的字符串比较用 =(精确)或 !~(正则否定),不要混用 == 或 !=(Nginx 不支持)。
返回 403 还是重定向?怎么选
两者目的不同,选法取决于运营策略:
- 返回 403:标准拒绝,日志清晰,适合明确恶意行为(如扫描器、爆破工具),搜索引擎也明确理解为“禁止访问”;
- 重定向到 /robots.txt 或 404 页面:可迷惑爬虫(伪装成正常响应),或引导其读取 robots 协议,但需注意:302/301 会暴露你有拦截逻辑,且可能被绕过;
- 不建议返回 200 内容(如“爬虫禁止”HTML),这会让爬虫误判为成功抓取,反而增加无效请求。
示例重定向写法(带跳转原因):
if ($bad_bot) {
rewrite ^(.*)$ /blocked.html?reason=bot last;
# 或外部跳转:
# return 302 https://example.com/403.html;
}验证与调试技巧
上线前务必测试,避免误杀正常用户:
- 用 curl 模拟爬虫请求验证:
curl -H "User-Agent: sqlmap/1.7" https://yoursite.com/ curl -I https://yoursite.com/ # 查看响应状态码
- 检查 Nginx 错误日志:
tail -f /var/log/nginx/error.log,确认是否有rewrite or internal redirection cycle(重定向循环)等报错; - 开启 access_log 中的变量记录,例如:
log_format main '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_time';,便于回溯可疑请求。
不复杂但容易忽略:所有 map 必须定义在 http 块内,且 reload 后才生效;if 只能在 server 或 location 中使用,不能出现在 upstream 或 events 块里。











