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

如何用 Nginx 的 njs 模块动态识别爬虫?

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-21 09:04 ·9 浏览 ·0 回复
内容摘要

用 njs 识别爬虫应将判定逻辑写成纯函数,经 js_set 输出为 Nginx 变量,再由 map、if、limit_req 等原生指令拦截或限速,而不使用 js_content 接管请求;并按 UA 把爬虫分为 declared、too

结论:用 njs 识别爬虫的正确做法,是把「判定逻辑」写成一个纯函数、通过 js_set 输出成 Nginx 变量,再由 map、if、limit_req 这些原生指令执行拦截或限速;不要用 js_content 接管整条请求链路。njs(nginx JavaScript module)是 Nginx 官方的 JavaScript 执行环境,它适合做请求级的轻量判断,不适合做需要 IO 的重业务。

njs 模块需要什么版本,怎么确认装上了?

结论:识别爬虫用到的核心能力是 js_import、js_set,需要 njs ≥ 0.4.0;想用共享字典记计数需要 njs ≥ 0.8.0;想用定时任务做二次校验需要 njs ≥ 0.8.1(对应 nginx ≥ 1.25.4)。

Nginx 官方源已经带了 njs 包:Debian/Ubuntu 装 libnginx-mod-http-js,Alpine 装 nginx-module-njs。装完在 nginx.conf 顶部加载动态模块:

load_module modules/ngx_http_js_module.so;

验证方式:nginx -V 2>&1 | grep -o njs 或直接 njs -v。若是源码编译,./configure --add-module=/path/to/njs。

基础识别:怎么用 UA 把爬虫分成三类?

结论:把爬虫分成 declared(自称搜索引擎)、tool(脚本/命令行工具)、browser 三类,比简单判定「是不是爬虫」更好用,因为三类请求的处置策略完全不同。

/etc/nginx/njs/bot.js:

const DECLARED = /googlebot|bingbot|baiduspider|yandexbot|duckduckbot|gptbot|claudebot|ccbot|perplexitybot|bytespider|petalbot|semrushbot|ahrefsbot/i;
const TOOL = /curl|wget|python-requests|python-urllib|aiohttp|go-http-client|java\/|libwww-perl|scrapy|okhttp|headlesschrome|phantomjs/i;

function classify(r) {
    const ua = r.headersIn['User-Agent'];
    if (!ua) return 'tool';          // 空 UA 基本都是脚本
    if (DECLARED.test(ua)) return 'declared';
    if (TOOL.test(ua)) return 'tool';
    return 'browser';
}

export default { classify };

对应配置:

http {
    js_import bot from njs/bot.js;
    js_set $bot_class bot.classify;

    map $bot_class $bot_reject {
        tool     1;
        default  0;
    }

    limit_req_zone $binary_remote_addr zone=crawler:10m rate=2r/s;

    server {
        if ($bot_reject) { return 444; }
        location /search/ {
            limit_req zone=crawler burst=5 nodelay;
        }
    }
}

两个细节:return 444 直接关连接,比 403/429 少发一个响应体,对高频垃圾流量更省资源;正则字面量写在模块顶层,njs 在加载时编译一次,函数内不要 new RegExp()。njs 的 RegExp 由 PCRE 实现,常用 test/match/replace 都支持,但要避免灾难性回溯的写法。

js_set 里能不能做 DNS 反查验证 Googlebot?

结论:不能。njs 没有同步 DNS 解析函数,而 js_set 的 handler 里做网络请求会把每个请求的延迟拉长到几十毫秒,并占住 worker 的事件循环——这是 njs 使用中最常见的性能陷阱。

可靠验证 Google 爬虫的方式是「rDNS 反查得到 googlebot.com / google.com 域 + 正查回原 IP」,这一步必须异步化:用 js_shared_dict_zone 建一个共享字典缓存验证结果,用 js_periodic 每 10 分钟批量跑一遍反查,js_set 里只读字典,零网络开销。

js_shared_dict_zone zone=botver:1m timeout=1h;


js_periodic bot.refresh interval=10m jitter=60;

结论:js_set 里只允许纯计算(字符串、正则、r.remoteAddress、r.headersIn),任何 DNS、HTTP 调用都必须挪到 js_periodic 或上游服务。

怎么用访问频率辅助判断爬虫?

结论:单纯限速用 limit_req 更快;njs 的价值在于把「UA + 频率 + 路径」组合成复合规则,比如「空 UA 且 60 秒内请求超过 30 次」才判定为爬虫。

njs 0.8.0 起提供共享字典 ngx.shared.<zone>,可在 js_content 里做计数:

function track(r) {
    const key = r.remoteAddress;
    const n = ngx.shared.botstats.incr(key, 1, 0);
    r.return(204, n);
}

结论:共享字典的读写是同步的共享内存操作,单次开销在微秒级,但每个请求写一次仍然有成本,建议只对 tool 类流量计数,browser 类直接放行。

上线前要注意什么?

结论:先只记录不拦截,观察 1–2 周再开拦截,否则极易误伤。用 log_format 把判定结果打出来:

log_format bot '$remote_addr "$request" $status class=$bot_class "$http_user_agent"';

注意 js_set 变量是惰性求值的:只有当变量被 map、if、log_format 引用时,函数才会执行;如果日志里没出现 $bot_class,你的判定逻辑等于没跑。

全文要点:njs 做爬虫识别的定位是「纯函数判别器」——加载 ngx_http_js_module,用 js_import + js_set 输出分类变量,用 map 转成拦截开关,用 return 444 处置脚本流量,用 limit_req 单独限速声明的搜索引擎,用 js_shared_dict_zone + js_periodic 异步缓存 UA 伪造的二次校验结果。判定逻辑不要碰 IO,这是性能底线。

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

全部回复 0

还没有回复,来抢沙发~