Nginx 日志分析:用 awk 统计 PV/UV
博客上线之后我一直在用自己写的访问统计插件看数据。但那套东西是"存进数据库再查",而服务器上其实还有一份更原始、更完整的记录——Nginx 的访问日志。
这篇笔记整理怎么用几个命令直接分析日志。不需要装任何软件,Linux 自带的 awk、sort、uniq 就够了。
日志长什么样
Nginx 默认的访问日志格式(combined)每一行长这样:
1.2.3.4 - - [19/Sep/2026:19:45:06 +0800] "GET /index.php/archives/26/ HTTP/1.1" 200 13193 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 ..."按空格切分,各字段依次是:
| 字段 | 含义 |
|---|---|
$1 | 客户端 IP |
$4 | 时间戳(含时区) |
$6 $7 $8 | 请求方法、路径、协议 |
$9 | HTTP 状态码 |
$10 | 响应体字节数 |
$11 | 来源页面(Referer) |
$12 起 | User-Agent(中间有空格,会占多个字段) |
记住这个位置表,后面所有命令都是围绕"取第几列"展开的。
我这份日志今天一天有 1997 行,来自 220 个不同的 IP。
六个常用统计
1. 总访问量(PV)
wc -l /var/log/nginx/access.log最简单的一个——日志一行就是一次请求,行数就是 PV。
2. 独立访客数(UV)
awk '{print $1}' /var/log/nginx/access.log | sort -u | wc -l拆开看这三步:
awk '{print $1}' 取出所有 IP;sort -u 排序并去重(-u 是 unique);wc -l 数行数。
注意这里统计的是"独立 IP 数",不是严格意义上的独立访客——同一个办公室里多台设备共用一个出口 IP 会被算成 1 个。这一点的讨论我写在统计插件那篇里了。
3. 访问量最高的页面
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20这个管道链是日志分析的经典套路,值得逐段理解:
awk '{print $7}' 取出请求路径
sort 先排序——这一步不能省,因为 uniq 只能合并相邻的重复行
uniq -c 去重并统计每项出现次数
sort -rn 按数字倒序(-n 数字排序,-r 降序)
head -20 只看前 20 条
如果漏掉第一个 sort,uniq -c 会把同一个路径算成好多个不同的组——因为相同的行没有挨在一起。这是新手最常见的错误。
4. 状态码分布
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn只看 $9 换成状态码就行。输出大概是:
1712 200
130 404
58 301
22 403
15 500这份数据里值得注意的两点:
404 有 130 次。对于一个只有十几个页面的小站,这个数字偏高。查一下是哪些路径——大部分应该是扫描器在探测不存在的文件(下面会说到),但也有可能是站内真的有坏链接。
500 有 15 次。5xx 是服务端错误,说明 PHP 执行出问题了,需要去看 PHP 的错误日志。这个必须查。
5. 流量来源
awk '{print $11}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10$11 是 Referer。直接访问的会显示 "-"。
这个统计能回答"访客是从哪来的"——搜索引擎、还是某个外链。
6. 响应体总流量
awk '{sum += $10} END {print sum/1024/1024 " MB"}' /var/log/nginx/access.log把所有响应的字节数累加,换算成 MB。END 块在 awk 处理完所有行后执行一次,用来输出汇总结果。
这个数字对按流量计费的服务器很有用——我的阿里云 ECS 就是按流量计费的,超了要额外掏钱。定期看一眼这个数,心里有底。
异常排查:从日志里发现扫描器
这是我觉得日志分析最有价值的地方——能看到访客看不到的东西。
我随手翻日志的时候注意到几行很可疑的记录:
1.2.3.4 - - [19/Sep/2026:19:41:37 +0800] "GET //images/images/images/images/images/images/cache.php HTTP/1.1" 301 178 "www.google.com" "Mozlila/5.0 (Linux; Android 7.0; SM-G892A ...)"几个破绽:
一、路径畸形。//images/images/images/images/images/images/cache.php——重复了六次的目录名。正常用户不会访问这种地址,这是扫描器在尝试路径穿越攻击的典型特征。
二、Referer 是伪造的。"www.google.com" 缺少协议头(正常应该是 https://www.google.com/)。这是想伪装成从谷歌点进来的。
三、User-Agent 有明显的拼写错误。Mozlila——"Mozilla"拼错了字母顺序。这是扫描工具的常见特征。
怎么批量发现这类访问
看 UA 里的可疑关键词:
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20这里用了个技巧:-F'"' 把分隔符改成双引号。因为日志里的请求行和 UA 都用双引号包着,用引号切分比空格切分更稳定——UA 字符串中间有空格,用空格切会把它切成好几段。
按引号切分后:$2 是请求行,$4 是 Referer,$6 是 User-Agent。这样就能把完整的 UA 字符串取出来了。
找出 404 最多的 IP,往往是扫描器:
awk '$9 == 404 {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10awk '$9 == 404 {print $1}' 这个写法是 awk 的"模式 + 动作"结构的典型用法:前面 $9 == 404 是过滤条件,只有当条件成立时才执行后面的 {print $1}。
这样能直接列出"哪个 IP 制造了最多的 404"——正常的访客不会产生大量 404,扫描器才会。
需要担心吗
不用太紧张。任何暴露在公网的服务器每天都会收到大量扫描——这是常态,不是"被针对"。
扫描器做的事情就是批量试常见的漏洞路径(cache.php、wp-admin、.env、phpmyadmin 之类),碰运气看有没有没打补丁的系统。只要你的程序本身没有这些漏洞,扫描不会有结果。
但有两件事值得做:
一是定期看 5xx。扫描不会造成 5xx,5xx 说明是你自己的代码出错了。
二是关注异常的 200。如果某个陌生 IP 对同一个 URL 产生了大量 200,可能是有人在爬你的内容。这时候可以在 Nginx 里加访问频率限制。
几个实用参数
统计今天某段时间的访问
awk '/19\/Sep\/2026:1[89]:/ {count++} END {print count}' /var/log/nginx/access.log用正则匹配时间范围。19[89] 匹配 18 点和 19 点两个小时的记录。
注意斜杠要转义(\/),因为日志里的时间格式是 19/Sep/2026。
排除掉自己的访问
自己调试时刷页面会污染统计。把自己的 IP 排除掉:
awk '$1 != "你的IP"' /var/log/nginx/access.log | wc -l这在用日志做统计时是必须的一步——否则你开发一下午,日志里全是自己的记录。
实时看访问
tail -f /var/log/nginx/access.log-f 是 follow 模式,新写入的日志会实时滚动显示。调试的时候开着这个窗口,另一头访问网站,能立刻看到请求打进来。
这一篇记两个最有用的东西就够了。
一个是 sort | uniq -c | sort -rn 这个套路。不管是统计页面、IP、状态码还是 UA,都是同一套链子换个字段号——记住它就不用背命令了。里面最容易漏的是第一个 sort,少了它 uniq -c 会把同一个值算成好几组,因为相同的行没挨在一起。
另一个是日志视角的独特性。统计插件记的是"正常访问",而日志把扫描器的畸形请求、伪造的 Referer、拼错的 UA 全都原样留着。想知道站点有没有被盯上,只能看日志。
用 awk 的局限也很明显:每次都要重新扫整个文件,日志攒大了会越来越慢,也看不了跨天的趋势。这也是两个工具不能互相替代的原因——临时查一次具体问题用 awk 最快,要做长期统计还是得落库。真正干活的时候,两种方式都会用上。
暂无评论