Web Tracking 前端埋点技术探究

本文最后更新于:2022年7月26日 上午

介绍

Web Tracking(此处细分来说应该叫 Web beacon)。Web beacon 是一种在网页和电子邮件上使用的技术,可以不显眼地(通常是无形的)检查用户是否访问了某些内容。Web beacon 通常由第三方用于监控用户在网站上的活动,以进行网络分析或页面标记。它们也可用于电子邮件跟踪。当使用JavaScript实现时,它们可以被称为JavaScript标签。
使用此类信标,公司和组织可以跟踪网络用户的在线行为。起初,进行此类跟踪的公司主要是广告商或网络分析公司;后来的社交媒体网站也开始使用这种跟踪技术,例如通过使用充当跟踪信标的按钮。
2017年,W3C发布了Web开发人员可用于创建Web信标的接口的候选规范。

使用

<img>

例如如果我们发送邮件后,想知道对方是否已读,就需要在邮件中嵌入一张图片,我们发送的get请求即是图片的src。当我们收到这次get请求后,在接口中写下逻辑处理函数等,处理完毕需要返回一张图片(通常情况下是一张1x1的GIF图片)。

例如嵌入在 HTML 中:

1
<img src="http://api.toflying.com/report?from=test&id=123" alt=""></img> 

又或者嵌入到 JavaScript 中:

1
2
3
const image = new Image()
image.src = "http://api.toflying.com/report?from=test&id=123"
// 这里不需要 appendChild 插入到页面中只需设置src属性即可发送Get请求。

上面的 http://api.toflying.com/report?from=test&id=123 就是API GET请求的URL,当然真实情况下是不存在的。我们可以携带任意参数(例如来源的平台、当前文章的ID等),并可以在后端处理相应逻辑。

那么问题来了,为什么要用 <img> 标签?

那是因为,我们的 <img> 相当于一次 GET 请求,并且没有跨域的限制。正是因为没有跨域限制,所以我们可以在任意网站(或者在我们发送的邮件中)嵌入该标签。这样我们就可以知道这些网站页面或者邮件被人阅读的情况,甚至可以分析对方的设备、IP、意图等等。

那为什么一定要返回一张图片?

如果我们返回的是一段文本或者说是JSON字符串(返回头中的 content-type: application/json;)。<img> 标签会触发 onerror 事件,会报错。如果返回空的话也会报CORB的问题,然后页面上的那张图片也会裂开(如果标签存在的话)。所以我们通常返回一张图片(返回头为content-type: image/*)。

为什么图片是 1x1 GIF 图片?

在同样尺寸下,不同格式的图片GIF格式占用的存储空间是最小的,也可以降低服务器的传输压力。

navigator.sendBeacon() 方法可用于通过 HTTP POST 将少量数据 异步 传输到 Web 服务器。它主要用于将统计数据发送到 Web 服务器,同时避免了用传统技术(如:XMLHttpRequest)发送分析数据的一些问题。

1
navigator.sendBeacon(url, data);

其中:url 是 POST 请求的地址。而 data 可选,是请求附带的数据( ArrayBuffer、ArrayBufferView、Blob、DOMString、FormData 或 URLSearchParams)

当用户代理成功把数据加入传输队列时,sendBeacon() 方法将会返回 true,否则返回 false

相较于图片的src,这种方式的更有优势:

  • 不会和主要业务代码抢占资源,而是在浏览器空闲时去做发送;
  • 并且在页面卸载时也能保证请求成功发送,不阻塞页面刷新和跳转;

现在的埋点监控工具通常会优先使用sendBeacon,但由于浏览器兼容性,还是需要用图片的src兜底。

用法如下:

1
2
3
4
5
const url = "http://api.toflying.com/report"
const data = new FormData()
data.append("id", "123")
data.append("from", "test")
navigator.sendBeacon(url, data)

当然,在实际使用中也没有这么简单。

过去,许多网站使用 unloadbeforeunload 事件以在会话结束时发送统计数据。然而这是不可靠的,在许多情况下(尤其是移动设备)浏览器不会产生 unloadbeforeunloadpagehide 事件。下面列出了一种不触发上述事件的情况:

  • 用户加载了网页并与其交互。
  • 完成浏览后,用户切换到了其它应用程序,而不是关闭选项卡。
  • 随后,用户通过手机的应用管理器关闭了浏览器应用。

此外,unload 事件与现代浏览器实现的往返缓存(bfcache)不兼容。在部分浏览器(如:Firefox)通过在 bfcache 中排除包含 unload 事件处理器的页面来解决不兼容问题,但这存在性能损失。

其它浏览器,例如 Safari 和 Android 上的 Chrome 浏览器则采取用户在同一标签页下导航至其它页面时不触发 unload 事件的方法来解决不兼容问题。

Firefox 也会在 bfcache 中排除包含 beforeunload 事件处理器的页面。

在会话结束时发送统计数据
网站通常希望在用户完成页面浏览后向服务器发送分析或诊断数据,最可靠的方法是在 visibilitychange 事件发生时发送数据:

1
2
3
4
5
6
7
8
9
document.addEventListener('visibilitychange', function report() {
if (document.visibilityState === 'hidden') {
const url = "http://api.toflying.com/report"
const data = new FormData()
data.append("id", "123")
data.append("from", "test")
navigator.sendBeacon(url, data)
}
});

可使用 pagehide 件来代替部分浏览器未实现的 visibilitychange 事件。和 beforeunloadunload 事件类似,这一事件不会被可靠地触发(特别是在移动设备上),但它与 bfcache 兼容。

浏览器兼容性

浏览器兼容性

总结

通常 <img> 标签出现的需要跨域的场景中。而 navigator.sendBeacon 可以在自己网站使用,当然如果使用不了(例如IE浏览器)可以用 <img>

未完待续

参考


Web Tracking 前端埋点技术探究
https://toflying.com/2022/07/25/10-web-tracking/
作者
KingChen
发布于
2022年7月25日
更新于
2022年7月26日
许可协议