·约 9 分钟阅读·DeepSeek

可以自托管的无 Cookie 统计:GoAccess 与「读日志」这条路线

为什么客户端追踪同时是隐私和性能的双重税,自托管统计从 GoAccess 读服务器日志到 GoatCounter、Umami、Plausible 的光谱长什么样,一套具体的 GoAccess 配置,以及基于日志这条路诚实的边界。原则是:只测那些会改变你决策的东西。

self-hostingdevopsnginx

我这个博客上的自托管统计,整套东西大约四十行配置加一个 systemd 定时器。页面里没有 JavaScript,没有 Cookie 提示条,没有第三方脚本在读者等正文的时候往家里打电话。如果你正在找 Google Analytics 替代品,诚实的答案是:我们大多数人根本不需要一个追踪平台,我们需要的是 一个日志阅读器。这篇文章写的是我真跑过的无 Cookie 统计光谱、为什么对这样一个站点 GoAccess 读服务器日志就够了,以及基于日志这条路究竟在哪里开始不够用。

贯穿始终的框架:只测那些会改变你决策的东西,别的都不测。

为什么客户端追踪既是隐私问题,也是性能问题

客户端统计意味着页面里有一段 <script>,在读者的浏览器里运行。这是两个问题披着同一件外套。

隐私那一半,说穿了就很明显:一个第三方信标让别的人能够跨所有嵌了同一段脚本的站点观察 你的读者,而 Cookie 存在的意义正是把这些观察缝合成一份画像。哪怕是一个「隐私友好」的厂商, 仍然意味着你读者的 IP 地址和访问记录离开了你的服务器,去了别人的地盘。Cookie 提示条、同意 字符串、数据处理协议——这一整套装置的存在,只是为了让你做一个你本不必做的选择。

性能那一半更安静,对我来说也更具决定性。每一个追踪器都意味着:

  • 在关键路径上的一次阻塞渲染、至少是阻塞网络的请求,和你的 CSS、字体、图片抢同一条连接;
  • 一次到你不控制的域名、你修不了硬件的第三方 DNS 查询 + TLS 握手;
  • 一段JavaScript 字节,在一部已经发烫的手机上被解析和执行,只为了生成一份你每月看一次的 报告。

一篇只有 30 KB 正文的博客,不该携带一个比自己还重的追踪器。而且上面还有一层哲学税:脚本运行 在读者的浏览器里,所以任何客户端方案——哪怕是自托管的——都是你在别人的机器上执行的代码。 基于日志的统计永远不做这件事。

自托管统计的光谱

没有单一的「最佳」工具;存在的是一条从「读我已经有的文件」到「一整套产品分析平台」的光谱。 下面是我真跑过的这些选项的实际对比。

选项数据来源擅长什么代价
GoAccessnginx / Apache 服务器日志零 JS、零数据库、秒出、单个二进制粗糙;对客户端事件是盲的
GoatCounter极小的 JS 信标单个 Go 二进制、私密、界面够用仍是一段脚本;需要一个数据库
UmamiJS 信标会话、事件、漏斗、清爽界面要跑 Node + 数据库,并要备份
PlausibleJS 信标(可代理)打磨精致、接近 GA、有 EU 托管理念活动部件更多;托管要付费
MatomoJS 信标GA 有的一切,外加各种报告PHP + MySQL;最重的一个

规律很简单:越往左移,你要跑的东西越少、测到的东西也越少;越往右移,你得到的越多、要照看 的也越多。 当你需要漏斗、目标、营销活动和逐个用户的旅程时,Plausible 和 Matomo 就是正确答案。 当你想要一个好看的仪表盘和一段轻量脚本时,GoatCounter 和 Umami 是正确答案。而当你想在页面里 一行代码都不加的前提下,回答「是什么把一个陌生人带了进来、人们实际在读哪些页」这个问题时, GoAccess 就是正确答案。

其中有两个和另外三个不一样。GoatCounter、Umami、Plausible、Matomo 全都是信标式的:它们 注入一段脚本、接收一个事件。GoAccess 是日志式的:它解析你的 Web 服务器本来就为排障而写的 访问日志。正是这个区别,构成了整篇论证。

日志式服务端 vs JS 信标:取舍是真实存在的

两边都不会完胜。这里给一份诚实的账本,因为一篇假装日志是免费的「隐私统计」文章,不过是另一种 广告罢了。

日志式赢在哪:

  • 读者的浏览器里什么都不跑。 没有脚本、没有 Cookie、没有同意提示条、没有第三方连接。你的 页面变快,是因为你减了重量,而不是加了重量。
  • 没有新东西要跑、要守。 日志本来就在。没有数据库、没有管理面板、没有会话存储、没有某个 周二才想起来要做的 schema 迁移。
  • 它挡不住也拦不掉。 广告拦截器和隐私扩展没有东西可拦;你和任何一个别的请求无从区分。
  • 便宜到不用去想。 一个二进制,读一个文件。

日志式输在哪:

  • 没有客户端事件。 你看不到按钮点击、滚动深度、表单放弃,或者 JS 报错。你看得到请求,也 只看得见请求。
  • 静态站点没有会话时长。 一个打开一页、读了九分钟的读者,和一个两秒钟就跳走的读者,看起来 一模一样。GoAccess 能通过 $request_time 显示每个请求的服务耗时,但那是服务器的时间, 不是注意力。
  • 刻意不做身份识别。 回访者只能从 IP 推断(在 NAT、移动网络和隐私 VPN 背后很不精确)。你 永远不会知道「这个人又回来了」。
  • 铁轨上的噪声。 爬虫、扫描器、预览抓取器、引荐垃圾——全都会打到日志里。你会越来越会过滤, 但永远到不了清零。

如果你真的需要左边那列,那就上一个信标工具。如果你是一个博客、文档站或店面,做的决策是 「多写点这个主题」,那左边那列就是全部工作,右边那列是纯开销。

一套具体的 GoAccess 配置

这是我在 nginx 机器上的实际配置。它假设日志格式是 vcombined——nginx 的 combined 加上虚拟 主机——这样一份报告就能覆盖多个站点。

首先是 nginx 里的日志格式。vcombined 与 GoAccess 内置的 VCOMBINED 解析器匹配,所以你不用 手写格式字符串。

# /etc/nginx/nginx.conf(http 上下文)
log_format vcombined '$remote_addr - $remote_user [$time_local] '
                     '"$request" $status $body_bytes_sent '
                     '"$http_referer" "$http_user_agent" "$host"';
access_log /var/log/nginx/access.log vcombined;

然后是报告本身。GoAccess 读取日志、套用过滤、写出一张自包含的 HTML 页面,你可以静态地提供 它——没有后端,没有数据库。

# 先手动生成一次报告,确认它能工作
goaccess /var/log/nginx/access.log \
  --log-format=VCOMBINED \
  --output=/var/www/analytics/index.html \
  --ignore-crawlers \
  --anonymize-ip

--ignore-crawlers 丢掉明显的机器人 user-agent;--anonymize-ip 截掉最后一个八位组,这样连 报告本身都泄露不出完整地址。如果想让统计跨日志轮转累积、而不是每次都从头开始,就加上 --persist --restore。

接着用定时器跑它,单实例互斥,避免一次慢报告和下一次堆在一起:

# /etc/systemd/system/goaccess-report.service
[Unit]
Description=生成 GoAccess HTML 报告
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/bin/flock -n /run/goaccess.lock \
  /usr/bin/goaccess /var/log/nginx/access.log \
  --log-format=VCOMBINED \
  --output=/var/www/analytics/index.html \
  --ignore-crawlers --anonymize-ip --persist --restore
# /etc/systemd/system/goaccess-report.timer
[Unit]
Description=每 15 分钟重建一次统计报告

[Timer]
OnCalendar=*:0/15
Persistent=true
[Install]
WantedBy=timers.target

启用一次(这一小步就是普通的 systemctl enable --now,不需要 flock),报告就会永远自己刷新。 为了不让它上公网,要么把它藏在 overlay 后面,要么在 nginx 里拦一道:

location /analytics/ {
    allow 10.144.144.0/24;   # 私有 overlay
    deny all;
}

整套系统就这些:一个二进制、一个定时器、一条受保护的路由。它占的内存也就一台笔记本的量, 而且永远不碰读者。

日志式统计诚实地做不到什么

我宁愿把边界点清楚,也不想让你在迁移之后才发现它们。

  • 它测不了注意力。 跳出率、滚动、阅读时长、漏斗——一个都测不了。对静态站点来说,没有第二个 事件可以拿来对比。如果「他们待了多久」对你来说是一个真实的决策输入,日志答不了,再怎么聪明地 解析也答不了。
  • 机器人流量是常态。 --ignore-crawlers 能去掉蠢的那批。其余的——无头浏览器、AI 爬虫、 链接预览抓取器、可用性监控——会虚高你的数字,而且有些还会谎报 user-agent。请把原始计数当作 方向性的,而非精确值。
  • 引荐数据充满噪声。 隐私代理会剥掉 Referer 头,应用内浏览器什么都不发,有些站点还会发 自我引荐。「直接访问」是一个桶,不是一个真相。你真正有用的引荐洞察,往往来自你在意的那一两个 来源,而不是长尾。
  • 唯一性只是个猜测。 基于 IP 的计数会高估共享网络、低估在 Wi-Fi 和移动网络之间切换的人。 对「大概多少」够用,对「精确多少」是错的。
  • CDN 和缓存会藏起请求。 如果 CDN 替你提供静态资源,大量流量根本到不了你的源站,日志看到的 就不到全量。这没关系——它只意味着日志是一份抽样,不是一次普查。

这些对博客来说都不致命,但它们全都对「日志报告是一套精确的漏斗工具」这个信念致命。它不是, 也从来不该是。

原则:只测那些会改变你决策的东西

我能接受粗糙数字的原因,是我只对其中两个数字采取行动:哪篇文章带来了一个新读者,以及 哪些文章真的有人读。其余一切——页面停留时长、滚动深度、精确到像素的设备分布——从来没有 改变过我做过的任何一件事。在一百次失败的实验之后,真正推动过这个站点的指标,只有那些告诉我 下一篇该写什么的指标。

所以对任何隐私友好统计的门槛,都不是「它能不能和 GA 功能对功能地平起平坐」。而是「它能不能 告诉我一件我会据此行动的事,同时不让我的读者付出隐私、速度或一条 Cookie 提示条的代价」。 GoAccess 读日志以很大的余量跨过了这条线。如果你的站点长出了目标、漏斗,以及一个需要共享仪表盘 的团队,那就升级到 Umami 或 Plausible——带着理由刻意地升级,而不是默认就上。

如果你想看这套东西跑在什么机器上,入口和 TLS 在 不用 Docker 的自托管里,而它在更大图景中的位置在 2026 年我到底自托管了什么里。

少测一点,多行动一点。