统计 Nginx 日志中 URL 的访问量排行,是接口治理、性能优化和容量评估中非常基础的一项工作。Nginx 的 access_log 会按照配置好的格式记录每一次请求,其中 request 字段完整保存了请求方法、URL 和协议版本。只要把这个字段拆出来,对 URL 做分组计数并倒序排序,就能看到最常被访问的前 N 个地址。不过,原始日志直接统计往往会有噪声,比如同一个接口带不同查询参数会被拆成多条记录,静态资源和健康检查请求也会占据榜单。本文会从日志格式开始,逐步给出命令行、脚本和工具三类方案,让统计结果更贴近真实业务。

先理解 Nginx 日志中的 request 字段
Nginx 的默认访问日志格式通常由 log_format 指令定义,常见的 combined 格式如下:
log_format combined '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"';
上面这段配置中的 $request 变量会输出类似 GET /api/user?id=123 HTTP/1.1 这样的内容,它由请求方法、URL 和协议版本三部分组成。以空格分隔后,$request 是整个日志行的第 6、7、8 个字段:第 6 个是请求方法,第 7 个是我们真正关心的 URL,第 8 个是协议版本。因此使用 awk 时通常取 $7 就能拿到 URL。
例如一条典型的访问日志如下:
192.168.1.10 - - [10/Mar/2025:10:15:32 +0800] "GET /api/user?id=123 HTTP/1.1" 200 432 "https://ipipp.com" "Mozilla/5.0"
这里第 7 个字段是 /api/user?id=123。如果只是想知道哪些路径被访问最多,还需要把问号后面的查询参数去掉,否则 /api/user?id=123 和 /api/user?id=456 会被当成两个不同的 URL。这个细节在后面的清洗部分会继续展开。
用 awk、sort、uniq 三件套快速统计 Top N
最简单的统计命令只需要一行。先假设日志文件为 access.log,不关心查询参数,只取 URL 的第 7 个字段:
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -n 10这条命令的每一步都很明确:awk '{print $7}' 负责提取每行的 URL;sort 把相同 URL 排在一起;uniq -c 统计连续相同行出现的次数;sort -rn 按次数倒序排列;head -n 10 只取前 10 条。实际运行后输出类似这样:
23847 /api/user 15012 /api/order 8730 /api/product 6544 /login 3201 /health
但是这种基础统计会包含所有请求,不管状态码是 200、404 还是 500。对于分析正常业务流量,通常应该只统计成功的请求,例如只取状态码为 200 的记录:
awk '$9 == 200 {print $7}' access.log | sort | uniq -c | sort -rn | head -n 20如果希望把 2xx 和 3xx 都算作正常请求,可以在 awk 中加一个范围判断:
awk '$9 >= 200 && $9 < 400 {print $7}' access.log | sort | uniq -c | sort -rn | head -n 20这里特别注意 >= 和 && 是 HTML 转义后的写法,源代码中对应的就是大于等于号和逻辑与符号。大日志文件下,sort 会使用磁盘临时文件排序,内存占用相对可控,但命令执行时间会随着日志量线性增长。如果每次统计都要全量扫描,可以考虑先对日志做切割,或者采样最近一段时间的访问记录。
过滤查询参数、静态资源和健康检查
直接统计 URL 最常踩的坑是查询参数导致同一接口分裂。假设客户端请求 /api/user?id=1001 和 /api/user?id=1002,从业务角度看它们应该合并成 /api/user,但直接按原始 URL 统计会出现两个条目,排行就不准确。处理方式是在 awk 中用 split 按问号拆分,只保留问号前面的部分:
awk '{ split($7, arr, "?"); print arr[1] }' access.log | sort | uniq -c | sort -rn | head -n 20第二类噪声是静态资源。图片、样式表、字体文件通常会被浏览器频繁请求,但它们不反映核心业务接口的访问热度。可以使用正则把常见静态后缀排除掉:
awk '$7 !~ /\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)(\?|$)/ { split($7, arr, "?"); print arr[1] }' access.log | sort | uniq -c | sort -rn | head -n 20上面的正则中 \. 表示匹配点号,末尾的 (\?|$) 用来兼容带查询参数的静态资源地址。需要注意的是,这条命令先判断原始 URL 是否匹配静态资源,再对非静态资源做查询参数拆分。
第三类需要过滤的是健康检查和内部探活请求。如果服务部署在 Kubernetes 或负载均衡后面,/health、/ping、/ready 这类接口可能每秒被请求很多次,它们会严重挤占 Top N 榜单。可以在 awk 判断条件里直接排除:
awk '$7 !~ /^\/health(\?|$)/ && $7 !~ /^\/ping(\?|$)/ && $7 !~ /^\/ready(\?|$)/ { split($7, arr, "?"); print arr[1] }' access.log | sort | uniq -c | sort -rn | head -n 20实际使用中,过滤规则最好根据业务场景整理成一个脚本,把状态码、静态资源、健康检查、指定路径都做成配置项,避免每次敲一大串命令。对于较大的日志,可以先压缩保留最近 7 天的文件,再对每一天分别统计,最后合并结果。
用 Python 脚本和工具提升统计效率
命令行方式适合快速查看,但如果过滤规则复杂、需要同时输出多个维度的排行,或者日志格式不是默认的 combined,使用 Python 处理会更清晰。下面是一个可以直接运行的脚本,它会读取 Nginx 日志,提取 URL、去掉查询参数、过滤静态资源和指定状态码,最后输出访问量最高的 10 个 URL:
from collections import Counter
import re
STATIC_SUFFIX = re.compile(r'\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)(\?|$)')
EXCLUDE_PATHS = {'/health', '/ping', '/ready'}
def parse_url(request):
parts = request.split()
if len(parts) < 2:
return None
url = parts[1].split('?')[0]
return url
counter = Counter()
with open('/var/log/nginx/access.log', 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
parts = line.split()
if len(parts) < 9:
continue
status = parts[8]
if status not in {'200', '301', '302', '304'}:
continue
url = parse_url(' '.join(parts[5:8]))
if not url or STATIC_SUFFIX.search(url) or url in EXCLUDE_PATHS:
continue
counter[url] += 1
for url, count in counter.most_common(10):
print(f'{count:>6} {url}')
这段代码在逐行处理时先按空格拆分日志,取出状态码和 request 字段,再从 request 中提取 URL。状态码可以根据业务放宽到 2xx 和 3xx,也可以把所有 4xx 排除。Counter 的 most_common 方法内部会自动按次数降序排列,输出结果与 sort -rn 类似。
如果不想写代码,也可以使用现成的日志分析工具。GoAccess 是一个开源的实时日志分析器,支持终端展示和 HTML 报告,能够自动识别 Nginx 日志格式,并直接给出 URL 访问排行、状态码分布、访客来源等指标。ngxtop 则更轻量,可以像 top 命令一样实时查看当前访问最频繁的接口。对于已经接入 ELK、Loki 或 ClickHouse 的团队,更推荐用查询语句完成 Top N 统计,例如在 Kibana 里按 request_path 聚合,这样还可以结合时间筛选和业务字段做多维分析。
无论采用哪种方式,都要先确认日志格式和字段位置是否与假设一致。修改过 log_format 后,awk 的 $7 可能不再指向 URL,脚本中的索引也会变化。建议在统计前先取几行日志做验证,必要时在脚本里通过正则提取 $request 的值,而不是写死第几个字段。这样统计脚本的兼容性会更好。