站点跑起来之后,大部分站长第一件事是装个统计代码,图省事直接塞第三方脚本。用了两三年之后,问题会慢慢浮现:数据不全(对方的脚本被广告插件拦掉一大半)、口径不可控(对方改一次统计规则,你的同比数据就断了)、以及最要命的一条——那些脚本本身成了首屏的一个阻塞请求。这篇文章讲的是一套完全自建的方案:用 AWStats 从 Nginx 访问日志里算流量报表,配一个每天定时跑的清理与归档脚本,整条链路跑在自己的服务器上,数据不出门,也不给页面增加一个字节的 JS。
一、先想清楚:日志统计和 JS 统计不是同一件事
很多人把这两种方案当成「新旧替代」,其实它们回答的是不同的问题。JS 统计(不管是第三方还是自建埋点)能告诉你「有多少人看了这一页、停留多久、从哪跳来的、在哪个按钮上点了」,它的单位是访客和会话。日志统计的单位是请求:谁在什么时间请求了哪个 URL、返回什么状态码、用了多少字节、UA 和 Referer 是什么。它看不见页面内部的交互,但它有一个 JS 永远拿不到的优势——它连爬虫、连被广告插件拦掉的用户、连 JS 报错没执行完的请求都算进去了。
所以对个人站长,日志统计最实用的三个场景是:判断搜索引擎爬虫的抓取频率、判断某个 URL 是不是真的被外部引用了、以及排查「报表数字和广告后台对不上」时提供一个独立的第三方口径。AWStats 正好擅长这三件事,它就是一个纯日志解析器:读日志文本,按 UA 和 Referer 归类,输出 HTML 报表。它不需要数据库,不需要在页面上插任何代码,也不占用前端性能预算。
这里要先说清楚 AWStats 的定位边界,避免用错地方。它不是实时统计工具,默认是按天更新,看的是「昨天」「上周」这类粒度;它也不擅长大规模日志(单机一天几个 G 的日志解析起来会很慢)。对日均几万到几十万 PV 的个人站点,它刚刚好;再往上就该换 GoAccess 这类流式解析或者直接上专业的日志平台了。
二、APT 装完之后必须先改的三件事
Debian/Ubuntu 下装 AWStats 很简单,一条命令:
apt-get install -y awstats
ls /etc/awstats/
# awstats.conf 是默认模板,所有站点共用它,不要直接改装完不要急着跑,先看三个默认值,它们的默认配置基本都不适合真实站点。
第一件,LogFile 指向的是 /var/log/apache2/access.log,这是给 Apache 准备的。用 Nginx 的站点必须改成自己的日志路径,而且必须让 AWStats 能读到这个文件——它是以 www-data 身份跑的,而 Nginx 日志默认权限是 640 root:adm,直接读会报 Permission denied,报表是空的,但脚本返回码仍然是 0,属于典型的静默失败。
LogFile="/var/log/nginx/www.example.com.access.log"
LogFormat=1第二件,LogFormat。这里最容易踩坑:AWStats 的格式编号对应一整套固定的日志字段顺序,如果你的 log_format 和它假设的不一致,解析出来的字节数、Referer、UA 会整体错位——报表看起来有数据,但每一列都不是你以为的那个东西。判断方法很简单,看日志里第一条真实记录,对照 AWStats 文档里 LogFormat=1 的定义(combined)逐字段比对,不一致就选 4(带 vhost 的 combined)或者用 LogFormat="%host %time1 %methodurl %code %bytesd %refererquot %uaquot" 显式写死字段。显式写死虽然长,但它不会被版本默认值改动影响,是更省心的选择。
第三件,SiteDomain 和 DirData。多站点环境下最省事的做法不是改模板,而是每个站点复制一份:cp /etc/awstats/awstats.conf /etc/awstats/awstats.www.example.com.conf,然后在各自的文件里把 SiteDomain 改成对应域名、DirData 改成独立目录(比如 /var/lib/awstats/example.com)。这样任意一个站点改配置都不会影响其他站点的历史数据,出问题也只需要回滚一个文件。
三、日志轮转与 AWStats 的时序配合
这是自建方案最容易坏的地方,而且坏了以后表现得很隐蔽:日志明明每天在轮转,报表却永远停在几天前。原因是 AWStats 默认只读当前文件,它通过 LastLineHashList 记住「昨天读到第几行」,第二天从那一行继续往下读。一旦日志被 logrotate 压缩成 .gz 并改名,这个「第几行」就失去意义了,AWStats 要么报「日志比上次小,认为发生了轮转,从头开始读」,要么干脆跳过。
正确的时序是「先让 AWStats 读完,再轮转」,两步之间必须串行。用 logrotate 的 postrotate 挂 AWStats 是最稳的写法,因为它保证在压缩之前执行:
cat /etc/logrotate.d/nginx-app
/var/log/nginx/www.example.com.access.log {
daily
rotate 14
missingok
notifempty
compress
delaycompress
create 0640 www-data adm
sharedscripts
postrotate
/usr/bin/awstats_updateall.pl now -configdir=/etc/awstats > /var/log/awstats-cron.log 2>&1
endscript
}几个细节值得说明。delaycompress 必须开,它让「刚轮转出来的那一份」保持未压缩,AWStats 读 .1 文件不会有额外的 gzip 开销。sharedscripts 保证多个日志文件只跑一次 postrotate。create 0640 www-data adm 保证新日志的权限对 AWStats 可读——如果漏了这一行,轮转后的新文件会沿用旧权限,第二天开始突然读不到,报表又一次静默停更。
如果你的站点日志量不大,另一个更省事的办法是不轮转,直接按天切命名:在 Nginx 里配 access_log /var/log/nginx/www.example.com.access.log,用 crontab 每天 00:05 跑 mv 改名成带日期的文件名,然后 kill -USR1 让 Nginx 重开文件,再让 AWStats 读那个带日期的文件。这种「日期命名 + 通配」的方式对 AWStats 最友好,缺点是 LogFile 要写成通配模式,历史上补跑的时候得手工指定日期。
四、报表里的五个数字,各自该信到什么程度
AWStats 的输出页面元素很多,对个人站长真正有用的只有五个。剩下的图表、操作系统分布、屏幕分辨率分布,在今天的浏览器环境下参考价值都很低,不用花时间琢磨。
第一个,按状态码的分布。这是排查事故最好用的一个数。404 突然翻倍,多半是改过一次 URL 结构或者某个静态资源路径写错了;499 占比变高,说明客户端等不及主动断开,通常是后端某个接口慢;503 集中出现,就是 PHP-FPM 池子被打满或者上游挂了。这类信息 JS 统计是拿不到的,因为请求根本没走到页面渲染。
第二个,搜索引擎爬虫的访问量,按天看趋势。AWStats 会把 UA 里含 Googlebot、bingbot、Baiduspider 的记录单独归类。这个数字的意义不在于「爬虫来了多少次」,而在于趋势突然变了:连续几天爬虫请求数归零,说明 robots.txt 写错、或者服务器对爬虫返回了 403/503;爬虫请求数暴涨而页面收录没涨,说明它在反复爬那些不值得收录的页面(分页、筛选参数、搜索页)。发现后者就应该去查这些 URL 有没有 noindex,而不是继续等。
第三个,带宽总量与按文件的 TOPS。很多站点的带宽杀手不是首页,而是某个几十兆的安装包、一段没压缩的高清图、或者一个被反复请求的音频文件。AWStats 的「按字节排行的文件」页面一眼就能看出来。这个数比「我在浏览器里感觉页面挺快」可靠得多。
第四个,外部来源 Referer 排行。这是排查热点和盗链最直接的手段。如果某个陌生域名贡献了大量 Referer,而你的文章并没有授权转载,那基本就是被采集站原文抓取了;如果出现自己域名指向自己域名的异常高 Referer,多半是某个页面里的相对路径写错,形成了自己引用自己的循环。这类问题在 JS 统计里会被算成正常回访,在日志里才能看清楚来源 URL。
第五个,访问时长与单访客页面数。这个数字在日志统计里要打折扣看。因为日志的「会话」是靠 30 分钟无请求来划分的,而现代浏览器会预加载、会后台请求,一条会话很容易被切碎成好几条。所以 AWStats 给出的「平均每次访问页面数」通常偏高,「平均停留时长」通常偏短。这两个数只用来看环比趋势,不要当绝对值。
五、把报表从服务器里拿出来:静态输出 + 基础认证
AWStats 自带 CGI 模式,但那个模式需要跑一个 awstats.pl 的 CGI 进程,PHP-FPM 的环境里要单独配 FastCGI,很别扭,而且每次打开报表都会重新计算(慢、且容易被打)。个人站点更推荐静态输出模式:每天跑完更新之后,把报表直接生成为 HTML 文件,放到一个受保护的目录里,用 Nginx 的基础认证访问。
# 每天跑完更新之后生成静态页
awstats -config=www.example.com -staticlinks -output > /var/www/stats/index.htmlNginx 侧只需要一段很短的配置,把 /stats/ 交给认证,其余路径不受影响:
location ^~ /stats/ {
auth_basic "stats";
auth_basic_user_file /etc/nginx/.htpasswd_stats;
index index.html;
}这里有两个必须留意的点。第一个,^~ 前缀不能省,否则 location ~ [^/]\.php(/|$) 这条正则有可能把 /stats/ 下的请求劫持给 PHP-FPM,导致报表目录里出现一个 PHP 入口——这属于把统计后台暴露成了可执行面,非常危险。第二个,.htpasswd 文件不要在 Web 根目录下,路径写在 /etc/nginx/ 下,避免被人直接下载。
生成密码文件用 htpasswd,一条命令:
htpasswd -c /etc/nginx/.htpasswd_stats statuser再补一条:报表目录一定要加 X-Robots-Tag: noindex 或者干脆在 robots.txt 里 Disallow。很多站长把统计页放在 /stats/ 这种猜得到的路径上,忘了它会被搜索引擎抓走,最后在搜索结果里出现「站点访问统计」这种页面,既不专业也泄露流量数据。
六、日志里的隐私边界:这一步别省
访问日志里天然有 IP、UA、Referer,在某些地区的合规语境下这属于个人信息。自建统计的一个附带好处是你完全掌控这份数据,但前提是你要真的管住它。三条最低限度的做法:
第一,日志保留期明确化,不要「反正磁盘够就一直留着」。最常见的折中是保留 14 天原始日志 + AWStats 的 .txt 汇总数据长期保留——汇总数据里只有统计结果,没有逐条 IP,风险低得多。第二,ANONYMIZE_IP 这类配置能开就开,AWStats 支持把 IP 的最后一段抹掉,代价是「独立访客」的精度下降,但对判断趋势几乎没影响。第三,报表目录一定要有认证,这一点上面已经说了,它不是可选项。
还有一个常被忽略的细节:日志里的查询串可能带上敏感参数。比如某个页面在 URL 里传递了邮箱或者 token,就会被完整写进日志。检查方法很简单:
awk '{print $7}' /var/log/nginx/www.example.com.access.log | grep -E 'mail=|token=|key=' | head -5如果这条命令有输出,说明你的站点正在把敏感参数写进日志,应该改代码(改成 POST 或者一次性短时 token),而不是事后从日志里抹掉。日志是取证和复盘的依据,不该成为泄露源。
七、一次完整的落地顺序
把上面的东西串起来,标准的上线顺序是这七步,每一步都有可验证的产物,不要跳着做:
第一步,装包并确认 perl 依赖齐全,awstats -version 能正常输出。第二步,按站点复制配置文件,改 LogFile、LogFormat、SiteDomain、DirData 四项,其他先不动。第三步,手工跑一次 awstats_updateall.pl now,看输出里有没有 Permission denied 或者 Dropped lines,前者是权限问题,后者是格式不匹配。第四步,生成静态报表,用 curl 确认返回 401(说明认证生效)而不是 200。第五步,挂进 logrotate 的 postrotate,或者写进 crontab,并且第二天回来确认报表的日期确实前进了。第六步,加一个「报表超过两天没更新就告警」的检查,这是整个方案唯一必须自己做的一处监控。第七步,检查敏感查询串的暴露情况,按上面的 awk 命令扫一遍历史日志。
八、常见故障的三行定位法
报表是空的。先看 awstats_updateall.pl 的 stdout 有没有 Permission denied,再看配置里的 LogFile 路径是否拼错(多一个目录名、少一个域名后缀是最常见的)。第二步把 LogFormat 换成显式字段串,重新跑一次。报表停在某一天不动。看 logrotate 的 postrotate 是否有输出错误,以及轮转后新文件的属主是不是变成了 root——如果 create 那一行漏了,第二天起 AWStats 就读不到了。报表数字忽然大幅波动。先怀疑日志格式被改过(有人加了新的 log_format 或换了 main 格式),其次怀疑是不是有 CDN 或负载均衡在前面把真实 IP 换成了内网地址,最后才怀疑 AWStats 本身。
九、什么时候该换掉 AWStats
说句实在话:AWStats 是 2000 年代的软件,界面老旧,按天更新,不支持实时,单个大日志文件解析很慢。它适合的场景是「个人站长想有一个独立口径的日志报表,不想装数据库、不想插前端脚本」。一旦你的日志一天超过 1GB,或者你需要分钟级的实时查看,就应该换 GoAccess(流式解析,一条命令出实时面板)或者更重的一套。判断标准不是「哪个更新」,而是「你需要的是报表还是实时监控」——前者 AWStats 够用,后者它天然不擅长。
另外提醒一句:自建统计最有价值的部分往往不是报表本身,而是你因此开始认真看状态码和爬虫趋势。很多站点的问题(死链、异常 403、被抓取预算浪费在无价值页面)在 JS 统计里完全看不见,在日志报表里一眼就能发现。工具值不值,取决于有没有人每天花五分钟看它。
小结
自建日志统计这件事,核心不在 AWStats 这个软件,而在三个环节:日志格式必须和解析器严格对齐(否则数字全错位)、轮转与解析必须串行(否则报表静默停更)、报表入口必须有认证和 noindex(否则变成泄露面)。把这三件事做对,一套跑在自己服务器上的流量报表就能稳定工作好几年,而且它比任何前端统计代码都更「不添麻烦」——不占首屏、被拦不掉、口径可自己定义。对个人站长来说,这种「安静但可靠」的工具,往往比花哨的实时面板更值得长期留着。