前端埋点

2,405 阅读13分钟

前端监控

前端监控是指通过在前端页面嵌入监控代码,对用户在页面上的操作、页面性能、错误信息等进行实时监控和收集,基于用户产生的行为数据为产品优化指明方向,能够为用户提供更加精确和完善的服务;在系统出现问题时前端监控能够及时收到反馈有利于及时排查问题并解决。

前端监控通常会监控以下几个方面:

  • 用户行为 (也被称为数据埋点):包括用户在页面上的点击、滚动、输入等操作。
  • 页面性能:包括页面加载速度、渲染速度、资源加载速度等。
  • 错误信息:包括 JavaScript 错误、接口请求失败、图片加载失败等。
  • 设备信息:包括用户所使用的设备类型、操作系统、浏览器类型和版本等。
  • 其他信息:包括用户访问来源、访问路径等。

前端监控流程

什么是埋点?

埋点是数据采集领域的术语,针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。

前端埋点可以在产品的客户端 (网站或应用程序) 嵌入代码,监控数据流量将关键节点的数据进行监控和上报,构建行为路径,借助用户行为数据帮助产品了解用户的需求和行为,从用户的角度对产品进行优化升级,提高用户体验和留存、转化率,使产品在市场上更具竞争力。

埋点中常见的几个概念

UV(Unique visitor)

独立访客数是指一天之内访问、浏览这个网页的用户数量。

访问网站的一台电脑客户端为一个访客,一天内同个账号的多次访问也仅计算一个UV,因为同为一个访客。

IP(Internet Protocol)

独立IP是指访问过某站点的IP总数,以用户的IP地址作为统计依据。

一天之内相同IP地址访问只被计算一次。

PV(Page View)

即页面浏览量,用户每次对网站中的网页进行访问都会被记录1个PV。访问了5个页面会产生5个PV, 对同一页面的多次访问,页面的浏览量也会被累计,用以衡量网站用户访问的网页数量。

VV(Visit View)

访问全站的次数,用以统计所有访客1天内访问网站的次数。

当访客完成所有浏览并最终关掉该网站的所有页面时便完成了一次访问,同一访客1天内可能有多次访问行为,访问次数会进行累计。

UV 与 IP的区别

事实上 访客数(UV)要比IP数更能真实准确地反映用户数量。

  • 访客数是根据用户的浏览器cookie或设备ID来计算的,可以更准确地识别不同的用户。
  • IP数是根据用户的IP地址来计算的,而一个IP地址可能会被多个用户共享,

例如在同一家公司内的多个用户可能会共享同一个IP地址。因此,使用IP数来估算用户数量会出现重复计算的情况,导致用户数量被高估。

PV与VV的区别

PV是指页面的浏览次数,VV是指用户一天内访问网站的次数。

展现量

在用户搜索关键词时内容展现的次数

转化率

转化率=转化次数/访问次数。转化率可以用来衡量网络营销的效果,用户的转化率越高,面向的用户画像更加精准,网站或产品起到的网络营销效果更好。

事件追踪(Event Tracking)

记录用户在页面上的行为,例如按钮的点击、页面滚动、搜索输入等事件,以及对这些行为的响应,例如弹出提示框、跳转链接等。

自定义事件(Custom Event)

开发者可以根据业务需求自定义事件名称和属性,用于更精细地追踪用户行为和分析用户行为路径。

埋点方案

手动埋点

手动埋点也被称为代码埋点,在页面中嵌入编写的逻辑,自定义监听事件代码或在关键代码处插入埋点代码来触发数据收集。

  • 优点:手动埋点可配置化更高,获取的用户数据更加精确(任意时刻,数据量全面)

  • 缺点:项目工程量大,埋点位置多,造成代码工作量大,后期维护成本高

可视化埋点

通过可视化界面配置需要埋点的事件和属性,将业务代码和埋点代码分离,系统提供一个可视化交互的页面,自动生成埋点代码。通过可视化系统,在业务代码中自定义的增加埋点事件,最后输出的代码耦合了业务代码和埋点代码。

  • 优点: 用系统来代替手动埋点,减少手动编写代码的工作量。

  • 缺点:可视化埋点的控件有限,可配置化程度不高,不能实现手动定制。

无痕埋点

通过 JavaScript 代码自动采集用户行为和页面信息,并上报到后台服务器,由后端计算和过滤出有用数据,无需手动埋点。

  • 优点:前端只需一次加载埋点脚本,采集全量的数据,不会出现漏埋和误埋等现象
  • 缺点:对服务器的性能要求高,会给数据传输和服务器增加压力,也无法灵活定制数据结构

埋点实现

实现埋点的多种方式

向服务器端上报数据,可以通过请求接口,请求普通文件,或者请求图片资源的方式进行。

一般而言,埋点域名并不是当前域名,而是指向一个专门用于收集数据的服务器域名。这样可以避免因为数据收集导致的网站性能问题,同时也可以更好地管理和分析收集到的数据。

因此采用 ajax 的方式进行上报可能会存在跨域风险,一旦发生跨域会被浏览器拦截,因此使用请求并不是万全之策。

解决跨域问题可以考虑使用 src 的形式向资源发起请求,可以采用 script、link、图片 实现数据的上报。

那我们该怎么进行选择呢?

当我们使用 script 和 link 进行埋点上报时,需要挂载到页面上才会发起请求,

而反复操作 DOM 会造成页面性能受影响,并且载入js/css资源会阻塞页面渲染,影响用户体验,

因此对于需要频繁上报的埋点而言,script 和 link 并不合适。

使用 img 标签加载并不需要挂载到页面上,基于 js 在 new image() 时,设置其 src 时就可以直接请求图片,使用图片做埋点则无需将图片挂载到页面,减少对 DOM 的操作。

通过 GIF 实现埋点上报

当我们做埋点上报时,使用 img 是一个不错的选择。

  1. img 兼容性好
  2. 无需挂载到页面上,反复操作dom
  3. img 的加载也不会阻塞html的解析,但 img 加载后并不渲染,它需要等待 Render Tree 生成完后才和Render Tree 一起渲染出来
  4. 与 PNG/JPG 相比,GIF 的体积最小

(最小的 BMP 文件需要74个字节,PNG 需要67个字节,而合法的 GIF 只需要43个字节。)

同样的响应,GIF 可以比 BMP 节约41%的流量,比 PNG 节约35%的流量。

所以通常埋点上报会使用gif图,并且大多采用的是1×1 透明像素的 GIF 来上报

1x1 像素是最小的合法图片。因为是通过图片进行埋点数据采集,所以图片最好是透明的,不会影响到页面本身的展示效果,同时采用透明的图片不用存储色彩空间数据可以节省图片的大小。

基于Navigator.sendBeacon的埋点

Navigator.sendBeacon 是浏览器提供的一个请求方式,指浏览器通过异步的 post 方式发送数据到服务端。浏览器会对其进行调度以保证数据有效送达,且不会阻塞页面的加载或卸载。不受跨域限制,浏览器兼容性较好,

  • 发出的是POST异步请求, 后端解析参数时,需要注意处理方式
  • 发出的请求放在浏览器的任务队列中,作为浏览器任务执行,脱离了当前的页面

因此该方法不会阻塞页面卸载流程和延迟后面页面的加载

  • sendBeacon 如果成功进入浏览器的发送队列后,会返回true,进入了发送队列,浏览器会尽力保证发送成功,但是无法判断请求是否发送成功。
  • 数据发送可靠,不受跨域的限制

API 的使用方式

navigator.sendBeacon(url, data);//true||false

Navigator.sendBeacon 接受两个参数:

  • url:目标服务器的 URL,
  • data:所要发送的数据(可选),可以是任意类型(字符串、表单对象、二进制对象等等)。

接口返回的布尔值表示当前请求是否成功加入浏览器的任务队列,如果受到队列总数、数据大小的限制后,会返回false。

当请求进入队列后并无法判断请求是否发送成功。

具体使用:

let data={
  a:"haha",
  b:"lala"
}
//创建blob对象,并设置请求头
const blob = new Blob([JSON.stringify(data)], {
    type: 'application/json; charset=UTF-8',
  });
//调用接口,并传送数据
navigator.sendBeacon(url, blob);

常见的埋点行为

点击事件触发埋点

绑定埋点的触发的点击事件,当用户触发时上报埋点数据

function handleClick(url, data) {
  navigator.sendBeacon(url, data)
}

记录页面停留时间

通过路由守卫监听用户的进入和离开

  1. 通过全局前置路由守卫记录用户进入页面的时间
  2. 当用户切换页面时会再次触发前置路由此时获取用户离开页面的时间
  3. 通过计算时间差得到用户的停留时间并上报
  4. 最后初始化新页面进入的开始时间
let url = ''// 上报地址
let startTime = Date.now()
let currentTime = ''
router.beforeEach((to, from, next) => { 
     if (to) {
         currentTime = Date.now()
         stayTime = parseInt(currentTime - startTime)
         // 上报用户停留的页面路由及停留时长
         navigator.sendBeacon(url, {time: stayTime,pageUrl:to.fullPath})
         //  重置初始时间
         startTime = Date.now()
     }
 })

错误捕获

  1. 借助 Vue 中处理异常的 errorHandle 捕获错误进行上报

errorHandler可以捕获的异常类型

  • 代码里的异常

错误的js语法使用(errorHandler自动捕获)

  • 接口请求异常

接口请求报错404,500等(需手动提交完成捕获)

在公共方法axios请求的响应拦截错误回调函数里,使用 Vue.$throw(err) 将错误信息提交给errorHandler函数去捕获处理

main.js文件中全局注册errorHandler方法

//Vue全局异常捕获
Vue.config.errorHandler = (err) => { 
    navigator.sendBeacon(url, {error: error.message, text: 'vue运行异常' })
}
  
Vue.prototype.$throw = (error)=> errorHandler(error,this);

拦截接口请求出错

在 axios 中通过 Vue.$throw(err) 将错误信息提交给errorHandler函数去捕获处理

axios.interceptors.response.use(
  (response) => {
    if (response.code == 200) {
      return Promise.resolve(response);
    } else {
      return Promise.reject(response);
    }
  },
  (error) => {
    // 返回错误逻辑
    Vue.$throw(error)
  }
);
  1. 通过 window.onerror 全局捕获错误事件上报异常
window.addEventListener('error', (error) => { 
    if (error.message) { 
        navigator.sendBeacon(url, {error: error.message, text: 'js执行异常' })
    } else { 
        navigator.sendBeacon(url, {error: error.filename, text: '资源加载异常' })
    } 
}, true)

内容可见埋点

使用 IntersectionObserver 可以更高效的监视某个页面元素是否进入了可见窗口

// 可见性发生变化后的回调 
function callback(data) { 
    navigator.sendBeacon(url, { target: data[0].target, text: '内容可见' }) 
} 
// 交叉观察器配置项 
let options = {}; 
// 生成交叉观察器 
const observer = new IntersectionObserver(callback); 
// 获取目标节点 
let target = document.getElementById("target"); 
// 监听目标元素 
observer.observe(target);

Intersection 交叉观察器

IntersectionObserver API,可以自动"观察"元素是否可见,由于可见(visible)的本质是:目标元素与视口产生一个交叉区,所以这个 API 叫做"交叉观察器"。

注意: IntersectionObserver API 是异步的,不随着目标元素的滚动同步触发

IntersectionObserver 的实现,应该采用 requestIdleCallback(),即只有线程空闲下来,才会执行观察器。这意味着,这个观察器的优先级非常低,只在其他任务执行完,浏览器有了空闲才会执行。

Intersection 用法
var io = new IntersectionObserver(callback, option);

IntersectionObserver是浏览器原生提供的构造函数,接受两个参数:

  • callback是可见性变化时的回调函数

  • option是配置对象(该参数可选)

    • root: 监听对象的具体祖先元素,用于检查目标的可见性。必须是目标元素的父级元素。如果未指定或者为null,则默认为浏览器视窗。

    • rootMargin: 设置 root 与检测元素的相交区域,及即设置触发 root 的边界范围。

      • rootMargin: "0px 0px -200px 0px" 交叉过视图的200,才开始派发事件
    • threshold: 设置特定的相交比例,当相交比例达到时会自动触发。默认为[0] 是一个数组,每个成员都是一个门槛值,也可设置成 [0.25, 0.5, 0.75, 1] 的形式,表示在相交的这几阶段会触发回调。

目标元素的可见性变化时,就会调用观察器的回调函数callback,callback 的触发时机:

  • Observer 第一次监听目标元素时。

  • 当目标元素与设备视窗或其他指定元素产生交集的时执行。(出现或是离开指定的 root 区域时都会触发)

var io = new IntersectionObserver(
  entries => {
    console.log(entries);
  }
);

callback函数的参数(entries)是一个数组,每个成员都是一个IntersectionObserverEntry对象,

如果同时有两个被观察的对象的可见性发生变化,entries数组就会有两个成员。

IntersectionObserverEntry 对象提供目标元素的信息,一共有六个属性。

{
  time: 3893.92,  // 可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
  rootBounds: ClientRect {   // 根元素的矩形区域的信息
    bottom: 920,
    height: 1024,
    left: 0,
    right: 1024,
    top: 0,
    width: 920
  },
  boundingClientRect: ClientRect {  // 目标元素的矩形区域的信息
     // ...
  },
  intersectionRect: ClientRect {  // 目标元素与视口(或根元素)的交叉区域的信息
    // ...
  },
  // 目标元素的可见比例
  intersectionRatio: 0.54, // 当intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0
  target: element
}
Intersection 的应用场景
  • 实现图片的懒加载 ,当图片滚动到可见时才进行加载。
  • 触底加载更多, 用户滚动到接近页面底部时直接加载更多,无需操作翻页。
  • 在用户看到某个区域时,播放动画或执行任务。

总结

以上 !!!

经过上面几个埋点方式的学习我们可以得出 Navigator.sendBeacon 是手动埋点方式中较为合适的方法,

但是具体情况具体分析,在实际开发的项目中我们可以选择合适的埋点方式,来收集用户的行为数据,这将有助于与产品的迭代与提升。

参考

  1. 三分钟,教你3种前端埋点方式!
  2. 前端埋点实现方案✔
  3. 监控都在用GIF做埋点
  4. 目前为止整理最全的前端监控体系搭建篇(长文预警)
  5. IntersectionObserver API 使用教程