Nginx 防爬虫时 access_log 日志格式怎么自定义?
Nginx 防爬虫场景应在 combined 基础上扩展日志格式,补充真实客户端标识、请求耗时与长度、限流命中状态字段,并用 escape=json 输出 JSON,以兼顾爬虫回溯和脚本解析。
结论:Nginx 防爬虫场景下的日志格式,应当在默认 combined 基础上至少补齐三类字段——真实客户端标识($http_x_forwarded_for)、行为成本字段($request_time、$request_length)、限流命中结果($limit_req_status/$limit_conn_status),并用 escape=json 输出成 JSON,才能同时满足“事后回溯爬虫”和“被脚本/AI 直接解析”两个需求。
为什么默认 combined 格式不够用
默认 combined 只记录 $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent",一共 9 个字段。防爬虫最需要的信息它缺三个:其一,走 CDN 或负载均衡时 $remote_addr 是回源 IP,真正的客户端在 $http_x_forwarded_for 里,只看前者等于把上万次请求算成同一个来源;其二,没有 $request_time,就看不出哪些请求是慢速扫描;其三,没有 $request_length,就看不出哪些请求是超大 body 探测。结论:只保留默认格式,等于放弃了事后定规则的数据基础。
自定义 log_format 怎么写才够用
核心是用 log_format 定义一套专用格式,再在 server 或 location 块里用 access_log 引用它。下面这套是实战中够用的最小集合,写在 http 块内:
log_format anti_spider escape=json
'{"ts":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"xff":"$http_x_forwarded_for",'
'"host":"$host",'
'"method":"$request_method",'
'"uri":"$uri",'
'"status":$status,'
'"bytes_sent":$body_bytes_sent,'
'"req_len":$request_length,'
'"req_time":$request_time,'
'"ua":"$http_user_agent",'
'"referer":"$http_referer",'
'"limit_req":"$limit_req_status",'
'"limit_conn":"$limit_conn_status"}';
几个必须注意的点:
escape=json参数从 Nginx 1.11.8 起支持,它会把 UA 里的双引号和不可打印字符转义,不加这个参数爬虫伪造一个带引号的 UA 就能把你的 JSON 日志打断。数字字段($status、$request_time等)不要加引号,否则下游解析要二次转换。$limit_req_status和$limit_conn_status需要 Nginx 1.17.6 及以上版本,取值是PASSED、REJECTED、DELAYED这类字符串,能直接统计“限流到底拦住了多少爬虫”。- 记录
$uri而不是$request_uri,可以避免把?token=xxx这类敏感参数写进日志;确实要看参数时单独加$args。
怎么把爬虫日志单独分流到一个文件
用 map 生成一个布尔变量,再用 access_log ... if= 条件判断,可以把疑似爬虫的请求单独写一份,主日志不膨胀。条件是变量值为空或 "0" 时不记录:
map $http_user_agent $is_bot {
default 0;
"" 1;
"~*(python|curl|wget|scrapy|go-http|okhttp|headlesschrome)" 1;
}
server {
access_log /var/log/nginx/access.log anti_spider buffer=64k flush=5s;
access_log /var/log/nginx/bot.log anti_spider if=$is_bot;
}
buffer=64k flush=5s 从 1.5.9 起支持,能显著降低高并发下的磁盘写入;if= 参数从 1.7.0 起支持。注意 if= 只接受变量,不能写复杂表达式,所以判断逻辑必须放进 map。日志文件被切走后,建议配合 open_log_file_cache max=1000 inactive=60s valid=60s; 避免句柄残留。
改完格式怎么验证没写坏
三步验证,缺一步都可能上线后才发现日志不可解析:
nginx -t检查语法,再nginx -s reload生效;curl -A "" http://你的域名/造一条空 UA 请求,tail -n 3 /var/log/nginx/bot.log看是否落到爬虫文件;- 用
python -c "import json;[json.loads(l) for l in open('/var/log/nginx/access.log')]"逐行校验,确认没有非法 JSON。
如果后续用 GoAccess 做可视化,记得同步指定 goaccess --log-format='{...}',格式字符串要和 log_format 一一对应,否则解析出空字段。
收束
防爬虫的日志格式要服务于两件事:能不能定位到真实来源,能不能证明限流规则生效。把 $http_x_forwarded_for、$request_time、$limit_req_status 这三类字段补齐,配上 escape=json 和 if= 分流,日志才从“记录发生了什么”变成“可以直接拿去定规则”。字段宁少勿滥,每加一个都要问一句:出事时我会不会真的查它。
转载请注明出处,版权归原作者所有。