Nginx 用 map 指令屏蔽恶意爬虫的写法。
用 http 块中的 map 将 $http_user_agent 映射为 0/1 变量,并在 server 块中以 if 返回 403,实现整站统一拦截恶意爬虫;变量只计算一次且可入日志,正则用 ~ 并排除合法爬虫以避免误封。
用 Nginx 屏蔽恶意爬虫最省事的写法是:在 http 块里用 map 把请求头 $http_user_agent 映射成一个 0/1 变量,再在 server 块里用 if ($变量) { return 403; } 统一拦截。整站只需计算一次变量,所有 location 复用同一份结果,比在每个 location 里重复写 if ($http_user_agent ~* ...) 更清晰,性能也更好。
map 屏蔽爬虫的完整配置怎么写
完整可复制的写法如下,map 必须放在 http 块内、server 块之外:
http {
map $http_user_agent $bad_bot {
default 0;
"" 1;
~*(Bytespider|SemrushBot|AhrefsBot|MJ12bot|DotBot) 1;
~*(python-requests|Go-http-client|Scrapy|curl/) 1;
}
server {
listen 80;
server_name example.com;
if ($bad_bot) {
return 403;
}
# 其余 location 配置
}
}
改完执行 nginx -t 校验语法,通过后 nginx -s reload 平滑生效,老连接不断开。default 0 表示没命中的 UA 一律放行,这一行必须写,否则没命中时会得到空字符串。
为什么用 map 而不是在每个 location 写 if
结论:map 的匹配结果在请求进入时计算一次并缓存,后续所有 if、log_format、limit_req 都直接读取这个变量,而 if ($http_user_agent ~* ...) 写在多个 location 里会重复执行正则。
map 内部实现是哈希表加正则列表:精确字符串(不带 ~、~*)走哈希,O(1) 命中;正则按书写顺序逐个尝试,第一个命中的生效。所以把最常见的恶意 UA 写成精确字符串、把模糊匹配放后面,能省正则开销。
另外一个实际好处是变量可以进日志。在 log_format 里加一个 $bad_bot 字段,就能统计每天被拦了多少请求:
log_format main '$remote_addr - [$time_local] "$request" '
'$status ua_block=$bad_bot "$http_user_agent"';
先观察一周日志再决定封哪些,比凭感觉拍脑袋加正则稳。
UA 正则怎么写才不误封
结论:必须用 ~* 做大小写不敏感匹配,并且把合法爬虫显式排除,否则容易把搜索引擎一起封掉。
三个要点:
~*是大小写不敏感的正则前缀,~是大小写敏感。爬虫 UA 大小写经常变(Bytespider/bytespider),一律用~*。- 别用过于宽泛的关键词。写
bot会把Googlebot、Bingbot一起封掉;要封就封具体名字。 - UA 为空通常是扫描器或伪造请求,
"" 1;这一条能拦掉相当一部分无效流量,但部分老旧客户端(如某些 SDK、监控探针)也会带空 UA,上线前先用日志确认。
如果只想降低频率而不是直接拒绝,把 return 403 换成配合 limit_req 限速更合适:map 里返回 1 表示恶意、0 表示正常,再用 limit_req_zone $bad_bot zone=bot:10m rate=1r/s; 按变量分桶限速。
return 403 和 return 444 该选哪个
结论:对明确的恶意爬虫用 return 444,对普通访客可见的拦截用 return 403。
444 是 Nginx 自定义的非标准状态码,含义是直接关闭连接、不回任何响应。爬虫拿不到状态码和响应体,抓取成本更高,也不会在你的访问日志里刷出一堆 403。403 会正常返回一个错误页,适合“这个 IP/UA 确实不该访问,但需要留痕”的场景。
注意 if 在 server 块里只放 return 和 rewrite 是安全的,这是官方文档明确认可的两个用法;不要在 if 里写 proxy_pass、add_header 这类指令,会遇到 Nginx 著名的 “if is evil” 问题。
怎么验证屏蔽是否生效
用 curl 指定 UA 直接测,两条命令对比:
curl -I -A "Bytespider" https://example.com # 期望 403 或连接被关闭
curl -I -A "Mozilla/5.0 (compatible; Googlebot/2.1)" https://example.com # 期望 200
如果返回 403 但页面仍然被抓,检查两点:一是 map 是否真的写在 http 块里(写在 server 里会直接报 "map" directive is not allowed here);二是 CDN 或负载均衡是否改写了 UA,如果前面挂了 CDN,Nginx 拿到的 $http_user_agent 是 CDN 透传的值,需要在 CDN 层同步配置。
整套方案的核心就三步:map 定义 UA 到 0/1 的映射,server 块用 if + return 拦截,日志加变量做灰度验证。配置量不到 10 行,命中判断只执行一次,是性价比最高的反爬第一道防线。
转载请注明出处,版权归原作者所有。