您现在处于PV 和 UV 有什么区别?无 Cookie 统计到底怎么算访客
jekit免费 · 公开 · 高效
返回博客列表

PV 和 UV 有什么区别?无 Cookie 统计到底怎么算访客

PV 是页面访问次数,UV 却取决于怎样识别“同一个人”。本文结合 Jekit 的实现,解释无 Cookie、无访客 ID 时如何统计每日 UV,以及数字会在哪些情况下偏大或偏小。

浏览量:Err·查看修改记录

多个页面访问事件经过浏览器本地隐私边界后形成匿名 UV 聚合统计

同一个人早上用手机打开网站,晚上又在电脑上看了一遍,算一个 UV 还是两个?

没有唯一答案。把两台设备认成同一个人,需要登录账号或某种稳定标识;什么都不认,就只能算成两个。PV 是事件计数,UV 是在一套身份规则下得到的估计值。 讨论 UV 准不准之前,得先问清楚它把什么当成“同一个访客”。

Jekit 选择了一个很克制的口径:不设置 Cookie,不生成访客 ID,也不把本地标识发给服务器。它统计的是“同一浏览器、同一站点里的访问状态”,不是现实世界中的人。

一、PV 好算,UV 麻烦

PV(Page View)回答的是:页面被打开了多少次。

同一个人刷新五次,通常就是五个 PV。SPA 内切换路由也可以产生新的 PV,只是实现时要处理重复触发和路由竞态。这里的统计对象是一次访问事件,不必知道访问者是谁。

UV(Unique Visitor)回答的是:这些访问里有多少个不同访客。

麻烦正出在“不同”二字。统计系统必须找到一个用于去重的东西:账号、Cookie、设备指纹、IP,或者浏览器本地保存的状态。选择不同,结果也不同。

访问场景 PV Jekit 眼中的 UV
同一浏览器连续刷新 5 次 5 1
同一台电脑换一个浏览器 2 2
手机访问后再用电脑访问 2 2
清除站点存储后再次访问 2 2
同一浏览器当天访问两个页面 站点 PV 为 2 站点 UV 为 1,两个页面各有 1 个页面 UV

这不是把一个人“算错”成了两个,而是统计口径从一开始就没有承诺跨设备识别人。Jekit 宁可接受这部分误差,也不建立能长期关联访问者的身份键。

二、浏览器只回答四种状态

每次上报前,Jekit 会在浏览器里判断三件事:

  • 这是本地第一次使用 Jekit 吗?
  • 这是今天第一次访问这个站点吗?
  • 这是今天第一次访问当前页面吗?

三个问题最后被压成一个 u8 枚举:

export enum visitorStatusOption {
    NewUser_TodayNewSite_TodayNewPage = 1,
    ExistingUser_TodayNewSite_TodayNewPage,
    ExistingUser_ExistingSite_TodayNewPage,
    ExistingUser_ExistingSite_ExistingPage,
}

服务器收到的是 1 到 4 中的一个数字。它可以据此累加站点和页面的当日 UV,却拿不到一个可用于追踪访问者的 ID。下一次请求也不会携带“我是上次那个人”这样的服务端身份凭据。

这四种状态只是 10 字节上报协议 中的第一个字节。

三、去重状态留在 localStorage

浏览器总得记住“今天看过哪些页面”,否则同一页面每刷新一次都会增加 UV。Jekit 把这份状态放在当前站点的 localStorage 中,不会像 Cookie 一样随 HTTP 请求自动发送。

第一层状态固定为 15 字节:

[1 字节日期][1 字节当天页面数][13 字节布隆过滤器]

日期按北京时间计算。跨天后,页面访问状态整份重建,因此“每日 UV”的日界线是北京时间零点。

这里没有随机生成的用户编号。localStorage 只保存日期、当天访问过的页面数量,以及一张无法列出原始页面地址的位图。服务器看不到这 15 字节。

四、为什么用布隆过滤器

直接保存当天访问过的 URL 最准确,但 URL 可能包含文章 ID、订单号或其他不适合长期留在本地的内容,而且列表会越写越长。

Jekit 先把规范化后的页面地址压成 32 位哈希,再用两个 16 位种子生成 7 个位置,写进 104 位的布隆过滤器。检查页面时,只要 7 个位置里有一个还是 0,就能确定它今天没出现过。

这套判断最容易混淆的地方,是“确定没出现过”和“可能出现过”并不对称。如图 1 所示,页面地址经过哈希后落到 7 个位置,浏览器只需读取这些位置当前的状态。

页面地址经过哈希映射到布隆过滤器的七个位置,再根据这些位判断页面是否可能访问过
图 1 布隆过滤器的页面访问判断

反过来不成立。7 个位置碰巧都被其他页面点亮时,过滤器会把一个新页面误判成“访问过”。在 104 位、7 个哈希、记录 10 个页面的条件下,理论假阳性率大约是 0.7%。

这会让页面 UV 略微偏小。为避免访问页面较多时误差迅速上升,Jekit 在超过 10 个页面后按需增加一个 108 字节的第二层过滤器。普通访客通常碰不到它,文档站和大型内容站则更可能用上。

布隆过滤器适合这里,不是因为它绝对准确,而是它用很小、定长的空间换来了一个方向明确的误差:可能少算新页面,不会把已经访问过的页面说成“一定没访问过”。

五、UV 会在哪些地方产生误差

把误差写清楚,比给出一个看起来很精确的数字重要。

5.1 会偏大的情况

  • 跨设备访问:手机和电脑各自保存状态,会被视为两个访客。
  • 更换浏览器:Chrome 和 Firefox 的站点存储互不相通。
  • 清除站点数据:本地状态消失,下次访问会重新计数。
  • 无痕窗口:独立的临时存储会形成新的统计上下文。

因此,Jekit 的累计 UV 更接近“首次出现的浏览器存储上下文数量”,不能直接解释为累计真实人数。

5.2 会偏小的情况

  • 布隆过滤器假阳性:新页面可能被误判成今天已经访问过。
  • localStorage 不可用:当前实现会保守地返回“已有页面”,保留 PV,但不增加这次 UV。
  • 哈希碰撞:两个不同页面理论上可能得到相同的 32 位哈希,统计会合并到一起。

这些偏差不会神奇地互相抵消。它们只是方向不同,而且随访问习惯、站点规模和浏览器环境变化。

六、不追求跨设备去重,是一个产品选择

更精确的 UV 并非做不到。登录账号可以跨设备识别用户,稳定 Cookie 可以跨会话去重,设备指纹还可以在用户不登录时尝试关联浏览器。

代价也很直接:统计系统开始拥有一把能把多次访问串起来的钥匙。即使现在只拿它计数,未来也可以拿它还原路径、划分人群或延长观察窗口。

Jekit 没有保存这把钥匙。所以它能回答“今天这个浏览器是否第一次访问本站或这个页面”,却回答不了“这个人上个月还去过哪些页面”。后者对增长分析很有用,但不是 Jekit 想提供的能力。

如果你需要跨设备用户旅程、转化漏斗或精确归因,应该选择带账号体系或稳定访客标识的分析工具。如果你只想知道页面有没有人看、访问趋势怎样,又不愿建立访客档案,这套口径更合适。

七、看数据时记住口径

在统计面板里,PV 可以理解为符合上报条件的页面访问次数;每日 UV 则是在当前浏览器本地状态下去重后的估计值。

所以,更适合观察的是趋势,而不是把某一天的 127 UV 当成现实中恰好有 127 个人。今天比昨天多,某篇文章长期高于另一篇,这些相对变化通常比个位数差异更有意义。

统计工具给出的从来不是上帝视角。至少在 Jekit 这里,误差来自哪里、为什么接受它,都可以从源码里查到。