不蒜子在 Safari 中计数异常

近期,我的博客在 Safari 中,文章访问量异常的大。经过抓包,确认了问题与 Referer 头相关。然而,Referrer-Policy 并没有解决问题。这与其接口设计有关。本文将进行解释,并提出我的解决方案。

问题表现

Safari 打开效果
正常情况打开效果

如图所示,Safari 显示的访问量高达 1k+,比实际访问量高出了 1448 次。这是很奇怪的,引起了我的注意。

问题原因

为了找到引起这个问题的原因,我们对上述页面分别向 busuanzi 发的请求都进行抓包。

Safari

Safari 向 busuanzi 发的请求

Chrome

Chrome 向 busuanzi 发的请求

从上面的两个请求,可以看出Safari 向 busuanzi 发送的请求 Referer 头是错误的。这导致 Safari 获得了 https://blog.cxzlw.top/ 的浏览量信息。这是为什么呢?

高人指点

加入 busuanzi QQ 群[1]后,我在群中询问。并得到了大佬「 」的回复。
大佬的解答

图中的链接是:Referrer Policy那些事 | 煮饭🍚

Referrer-Policy

发生了什么呢?浏览器开始用 strict-origin-when-cross-origin 替换之前的 no-referrer-when-downgrade 作为 Referrer-Policy 的默认值,而这个新策略破坏了向不蒜子发送的 Referer 头。

于是我设置了 Referrer-Policy。具体来说,我将下面的代码加入了我的博客:

1
<meta name="referrer" content="no-referrer-when-downgrade">

还有一些设置 Referrer-Policy 的方法:

  1. 在 HTTP Header 中添加:Referrer-Policy: no-referrer-when-downgrade
  2. 在发起请求的 Element 中添加 referrerpolicy="no-referrer-when-downgrade" 属性
    例如:<a href="http://example.com" referrerpolicy="no-referrer-when-downgrade">

穷途末路

事情并没有完,在 Safari 上,我的博客依然无法正常显示浏览量,这又是为什么呢?

在回答这个问题之前,先让我们解决一个前文从未提到的问题「为什么其他浏览器能正常显示?」。

为什么其他浏览器能正常显示?

前文 Referrer Policy那些事 | 煮饭🍚 中提到:

之前的默认值是 no-referrer-when-downgrade,而现在变成了 strict-origin-when-cross-origin。经过查找,原来是chrome85以上做出了默认规则的改变。 A new default Referrer-Policy for Chrome - strict-origin-when-cross-origin - Chrome for Developers

既然 Chrome 也作出了上述改变,为什么我的博客在 Chrome 中能正常显示呢?

要回答这个问题,让我们看看 busuanzi 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
var bszCaller, bszTag;
!function() {
var c, d, e, a = !1, b = [];
ready = function(c) {
return a || "interactive" === document.readyState || "complete" === document.readyState ? c.call(document) : b.push(function() {
return c.call(this)
}),
this
}
,
d = function() {
for (var a = 0, c = b.length; c > a; a++)
b[a].apply(document);
b = []
}
,
e = function() {
a || (a = !0,
d.call(window),
document.removeEventListener ? document.removeEventListener("DOMContentLoaded", e, !1) : document.attachEvent && (document.detachEvent("onreadystatechange", e),
window == window.top && (clearInterval(c),
c = null)))
}
,
document.addEventListener ? document.addEventListener("DOMContentLoaded", e, !1) : document.attachEvent && (document.attachEvent("onreadystatechange", function() {
/loaded|complete/.test(document.readyState) && e()
}),
window == window.top && (c = setInterval(function() {
try {
a || document.documentElement.doScroll("left")
} catch (b) {
return
}
e()
}, 5)))
}(),
bszCaller = {
fetch: function(a, b) {
var c = "BusuanziCallback_" + Math.floor(1099511627776 * Math.random());
window[c] = this.evalCall(b),
a = a.replace("=BusuanziCallback", "=" + c),
scriptTag = document.createElement("SCRIPT"),
scriptTag.type = "text/javascript",
scriptTag.defer = !0,
scriptTag.src = a,
scriptTag.referrerPolicy = "no-referrer-when-downgrade",
document.getElementsByTagName("HEAD")[0].appendChild(scriptTag)
},
evalCall: function(a) {
return function(b) {
ready(function() {
try {
a(b),
scriptTag.parentElement.removeChild(scriptTag)
} catch (c) {
bszTag.hides()
}
})
}
}
},
bszCaller.fetch("//busuanzi.ibruce.info/busuanzi?jsonpCallback=BusuanziCallback", function(a) {
bszTag.texts(a),
bszTag.shows()
}),
bszTag = {
bszs: ["site_pv", "page_pv", "site_uv"],
texts: function(a) {
this.bszs.map(function(b) {
var c = document.getElementById("busuanzi_value_" + b);
c && (c.innerHTML = a[b])
})
},
hides: function() {
this.bszs.map(function(a) {
var b = document.getElementById("busuanzi_container_" + a);
b && (b.style.display = "none")
})
},
shows: function() {
this.bszs.map(function(a) {
var b = document.getElementById("busuanzi_container_" + a);
b && (b.style.display = "inline")
})
}
};

其中,我们重点关注这段代码:

1
2
3
4
5
6
7
8
9
10
11
fetch: function(a, b) {
var c = "BusuanziCallback_" + Math.floor(1099511627776 * Math.random());
window[c] = this.evalCall(b),
a = a.replace("=BusuanziCallback", "=" + c),
scriptTag = document.createElement("SCRIPT"),
scriptTag.type = "text/javascript",
scriptTag.defer = !0,
scriptTag.src = a,
scriptTag.referrerPolicy = "no-referrer-when-downgrade",
document.getElementsByTagName("HEAD")[0].appendChild(scriptTag)
},

回顾 Referrer-Policy 部分,我们也可以通过在 Element 中添加 referrerpolicy="no-referrer-when-downgrade" 属性来设置 Referrer-Policy

这段代码在使用 JSONP 解决跨域的问题时,创建了一个带有 referrerpolicy 属性的 Script Tag,以此解决了 Chrome 等浏览器上出现的问题。

为什么 Safari 不能?

上面我们解释了为什么 Chrome 等浏览器能正常显示我的博客的浏览量,接下来我们将解释为什么 Safari 中仍然出现这种问题。

Preventing Tracking Prevention Tracking | WebKit

Origin-Only Referrer For All Third-Party Requests
ITP now downgrades all cross-site request referrer headers to just the page’s origin. Previously, this was only done for cross-site requests to classified domains.

As an example, a request to https://images.example that would previously contain the referrer header “https://store.example/baby/strollers/deluxe-stroller-navy-blue.html” will now be reduced to just “https://store.example/”.

原来,Safari 的 阻止跨站跟踪 功能会将跨站请求中的 Referer 头全部降级为页面的根路径。效果就像 strict-origin-when-cross-origin。这无疑对 busuanzi 的请求生效,因此开启了 阻止跨站跟踪 的 Safari 便会遇到这个问题。

显然要求每个用户关闭 阻止跨站跟踪 是奇怪的,在 busuanzi 作者主动修改 API 不再依赖 Referer 头前,似乎这成了一个无解的问题。

我的解决方案

我的解决方案是重构我的博客的访问量系统。自己写一个不依赖 Referer 头的 API,并将前端与访问量相关的代码重构。

我将会写一篇新的文章介绍这个 API,各位也可以直接抓包分析我的博客。这个 API 欢迎各位使用。但与 不如 不同的是,我无法提供任何可用性保证。

参考资料

  1. 不蒜子 | 不如
  2. Referrer Policy那些事 | 煮饭🍚
  3. Referrer-Policy - HTTP | MDNMDN Web DocsMDN logoMozilla logo
  4. 不蒜子在 Chrome 85 版本后所有页面统计是同一个数据 · Issue #376 · fluid-dev/hexo-theme-fluid · GitHub
  5. Referrer Policy | Can I use… Support tables for HTML5, CSS3, etc
  6. Preventing Tracking Prevention Tracking | WebKit

  1. 419260983,来自 busunazi 官网 不蒜子 | 不如

不蒜子在 Safari 中计数异常
https://blog.cxzlw.top/2023/10/03/busuanzi-bug/
作者
cxzlw
发布于
2023年10月3日
许可协议