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

如何用 awk 命令快速统计蜘蛛抓取日志?

juming
juming 初级会员超兽战士
发布于 2026-10-12 08:32 ·3 浏览 ·3 回复
内容摘要

介绍用 awk 从 Nginx 日志统计百度、Google、必应等蜘蛛的抓取总量、状态码分布与每小时频次,需按双引号切分提取完整 UA 并用 tolower 兼容不同 awk,无需额外分析工具。

学完这篇,你能只用 awk 一条命令,就从几百万行的 Nginx 原始日志里统计出百度、Google、必应等蜘蛛的抓取总量、抓取时段、热门 URL 和状态码分布,不用装任何额外分析工具。

第一步:确认日志格式,记住字段编号

先看清楚日志长什么样,awk 才能取对字段。执行:

head -n 1 /var/log/nginx/access.log

如果输出是 Nginx 默认 combined 格式,大概长这样:

66.249.66.1 - - [12/Mar/2024:10:23:45 +0800] "GET /article/123 HTTP/1.1" 200 5321 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"

awk 默认按空格切分,于是:$1 是客户端 IP,$4 是日期加时间(带左方括号),$7 是请求路径,$9 是状态码,$10 是响应字节数。

注意:UA 字符串里带空格,用空格切分取不到完整 UA。要加 -F'"' 改成按双引号切分,此时 $2 是完整请求行,$6 是完整 UA。

第二步:统计各家蜘蛛的抓取总量

这一步的输出是一张排名表:谁抓得最多。命令如下:

awk -F'"' '{ua=tolower($6)
  if (ua ~ /googlebot/) print "Googlebot"
  else if (ua ~ /baiduspider/) print "Baiduspider"
  else if (ua ~ /bingbot/) print "bingbot"
  else if (ua ~ /yandexbot/) print "YandexBot"
  else if (ua ~ /sogou/) print "Sogou"
  else if (ua ~ /360spider/) print "360Spider"
  else if (ua ~ /bytespider/) print "Bytespider"
  else if (ua ~ /bot|spider|crawler/) print "其他"
}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

tolower() 把 UA 转成小写,就不用担心大小写不一致。

注意:很多教程用 BEGIN{IGNORECASE=1},那是 gawk 的扩展,macOS 自带的 BSD awk 和部分精简版 awk 不支持,会静默失效。用 tolower() 最稳。

第三步:看某个蜘蛛的状态码分布

想知道百度蜘蛛抓到的页面有没有大量报错,把 baiduspider 换成你要查的名字即可:

awk '$0 ~ /Baiduspider/ {print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

这里用默认空格切分,$9 就是状态码。正常情况下 200 占绝大多数。

第四步:统计蜘蛛每小时的抓取频次

判断抓取是否过于集中、会不会压垮服务器:

awk -F'"' 'tolower($6) ~ /baiduspider/ {
  split($1, t, ":")
  c[t[2]]++
}
END { for (h in c) printf "%s时 %d次\n", h, c[h] }' /var/log/nginx/access.log | sort

$1 里包含 [12/Mar/2024:10:23:45,按冒号切分后 t[2] 就是小时。

第五步:找出被反复抓取的 URL

这一步能帮你发现蜘蛛是不是在爬一堆无意义的参数页:

awk -F'"' 'tolower($6) ~ /baiduspider/ {split($2, r, " "); print r[2]}' \
  /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

$2 是 GET /article/123 HTTP/1.1,切分后 r[2] 就是路径。

第六步:列出蜘蛛抓到的 404 / 5xx 页面

这些是要修的死链和故障页:

awk -F'"' 'tolower($6) ~ /baiduspider/ {
  split($3, s, " ")
  if (s[2] ~ /^[45]/) print s[2], $2
}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

$3 是 200 5321 这种形式,s[2] 即状态码。

注意:日志被切割或压缩过时,用 zcat /var/log/nginx/access.log.*.gz | awk ...。日志很大时前面加上 LC_ALL=C 能明显提速。

小结

  • combined 格式下取 UA 必须用 -F'"',UA 是 $6;
  • 跨 awk 版本兼容用 tolower(),别依赖 IGNORECASE;
  • 默认空格切分时状态码是 $9,按双引号切分后要用 split($3,s," ") 取 s[2];
  • 统计套路固定为「awk 打印目标 → sort → uniq -c → sort -rn」,再加 head 看 Top N;
  • 大日志加 LC_ALL=C,压缩日志用 zcat。
版权声明:本文来自 GJ站长论坛《如何用 awk 命令快速统计蜘蛛抓取日志?》
原文链接:https://www.gj0.com/thread-1709.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 3

liulian
liulian 初级会员超兽战士 1楼 2026-10-12 08:34

思路是对的,-F'"' 取 $6 拿完整 UA 也没问题;但第四步的时段统计别在 -F'"' 里硬拆时间,换回默认分隔符更干净。

combined 格式按空格切时 $4 是 [12/Mar/2024:10:23:45,从第 14 个字符取 2 位就是小时,蜘蛛匹配直接对整行做小写后匹配即可:

awk 'tolower($0) ~ /baiduspider/ {print substr($4,14,2)}' access.log | sort | uniq -c | sort -k2

这样比在 -F'"' 下再从 $1 里二次拆时间稳得多,也避开了 UA 带空格的老问题。

另外建议把总量、状态码、时段做成单次 awk 遍历,用数组在 END 里输出,几 GB 的日志读一遍就够了,别反复扫同一个文件:

awk '{ua=tolower($0); h=substr($4,14,2)
  if (ua ~ /baiduspider/) {c++; st[$9]++; hr[h]++}}
END{for(k in st) print "状态", k, st[k]; for(k in hr) print "时段", k, hr[k]}' access.log

坑有两个:一是日志被 logrotate 压成 .gz 后 awk 读不了,得用 zcat access.log.*.gz | awk ...;二是 UA 可以随便伪造,只看 UA 统计出来的“百度蜘蛛”里常混着采集器,要坐实身份还得反查 IP(Googlebot 官方 JSON 段、百度也有公开 IP 列表)。顺带一提,Bytespider 抓取量往往大得离谱且不带来流量,很多站长是直接在 Nginx 层封掉的。

域名注册
域名注册 初级会员资深会员超兽战士 👑年卡会员 #550 2楼 2026-10-12 08:36
liulian:思路是对的,`-F'"'` 取 `$6` 拿完整 UA 也没问题;但第四步的时段统计别在 `-F'"'` 里硬拆时间,换回默认分隔符更干净。 combined…

对,时段统计换回默认分隔符用 substr($4,14,2) 最干净,在 -F'"' 里二次拆时间纯属自找麻烦,而且不只是时间——-F'"' 下状态码也不在 $9 了($2 是请求行,状态码实际落在 `

周末
周末 正式会员超兽战士 #551 3楼 2026-10-12 08:39
域名注册:对,时段统计换回默认分隔符用 `substr($4,14,2)` 最干净,在 `-F'"'` 里二次拆时间纯属自找麻烦,而且不只是时间——`-F'"'` 下状态…

对,你后半句被截断了,补全就是:在 -F'"' 下,状态码和响应字节数一起落在 $3(形如 " 200 5321 "),$2 是整行请求,$4 是 referer,$5 是那个夹在引号之间的空格,$6 才是完整 UA。成对的双引号把字段切开后,这套映射和默认空格切分完全不是一回事,所以谁要是在 -F'"' 里还去取 $9,拿到的就是字节数或者干脆是空。

真要取状态码得再切一刀,注意 $3 开头带一个空格,直接 split 会多冒出一个空字段:

awk -F'"' 'tolower($6) ~ /baiduspider/ {split($3,a," "); print a[2]}' access.log

更偷懒的是写 $3+0,awk 做数字转换时会自动跳过前导空白、读到非数字为止,结果正好是 200,字节数则用 a[3]。

其实换个角度更清楚:如果一条命令里只做蜘蛛识别、状态码和时段统计,那压根不需要 -F'"',默认空格切分配 tolower($0) 整行匹配就够了;-F'"' 的唯一价值是拿完整 UA 去做聚合或原样输出。两种模式混在一条管道里,是最容易出错的写法,建议一条管道只选一种。

顺带一提,有些 Nginx 配置里 referer 为 "-"、UA 为空时是 "",$6 可能压根不存在,匹配前加个 if ($6 != "") 判空会更稳。