背景
埋点,是收集产品的数据的一种方式,其目的是上报相关行为数据(PV/UV/时长/曝光/点击等),由相关人员以此分析用户的使用习惯,助力产品不断迭代和优化。对于开发来说,通常不仅仅需要完成基础的业务需求,还需要完成埋点需求,所以,追求的是简单快捷的埋点工作。而一个完整的埋点体系由以下三个部分构成:
- 产品应用(产生行为数据)
- 数据分析平台(展示、分析行为数据)
- 数据平台 SDK(上报行为数据):封装数据分析平台的各种接口,暴露简单的方法供调用,实现简易的埋点上传。
目前,前端埋点存在的痛点一般是:
- 埋点字段的手动拼接,存在出错风险;
-
复杂场景的曝光埋点实现繁琐:分页列表、虚拟列表等;
-
埋点代码的侵入性:尤其是曝光代码导致逻辑复用困难。
埋点类型一般有:
- 页面埋点:统计用户进入或离开页面的各种维度信息,如页面浏览次数(PV)、浏览页面人数(UV)、页面停留时间、浏览器信息等。
- 点击埋点:统计用户在应用内的每一次点击事件,如新闻的浏览次数、文件下载的次数、推荐商品的命中次数等。
- 曝光埋点:统计具体区域是否被用户浏览到,如活动的引流入口的显示、投放广告的显示等。
市场上常见的埋点方案:
- 全埋点(无埋点):由前端自动采集全部事件并上报,前端也就没有埋点成本,由数据分析平台或后端过滤有用数据,优点是数据全面,缺点是数据量大,噪声数据多。
- 可视化埋点:由可视化工具进行配置采集指定元素——查找 dom 并绑定事件,优点是简单,缺点是准确性较低,针对性和自定义埋点能力较弱。
- 代码埋点:用户触发某个动作后手动上报数据,优点时准确性高,能满足自定义的场景,缺点是埋点逻辑容易与业务逻辑耦合(命令式埋点),不利于维护与复用。
综上,需要的是一种简单快速且准确,同时埋点逻辑与业务逻辑解耦的埋点方案,也就是本文分析的声明式的组件化埋点方案。
声明式的组件化埋点方案
名词解释
- 页面 (Page):在浏览器中打开的网页,不同页面以路径
location.pathname
来作区分;- 页面可见时长:一个页面对用户可见的累计时长;
- 页面活跃时长:用户在页面上进行有效的鼠标、键盘及触控活动的累计时长;
- 组件 (Component):DOM 元素的集合,是页面的组成部分。一个页面内可包含多个组件;
- 组件可见时长:一个组件对用户可见的累计时长。
- 可见性(visiability)
visible:
页面 viewport 中且位于前台;invisible
- 页面不 viewport 中,或处于后台。- 活跃性 (activity)
active
- 用户在网页中有活动(例如鼠标、键盘活动及页面滚动等);inactive
- 用户在网页中没有任何活动。
根据概念可知,一个页面不可见时,则一定不活跃,且其中的所有组件一定也都不可见;页面活跃时长 ≤ 页面可见时长;组件可见时长 ≤ 页面可见时长,
原理与思路
该方案总体思路如下:
- 对于通用字段进行统一处理,既不容易出错,也方便后期拓展。对于运行时字段(异步),支持 extra 进行传入。
- 对于页面级事件,埋点库初始化后自动注册关于页面级曝光的相关事件,不需要在代码中进行维护。
- 考虑到存在高频场景,设置上报缓冲队列 pendingQueue,通过定时任务分批次上报数据,支持设置上报频率。根据实践,点击类上报频率 1000ms,曝光类 3000ms。
- 考虑到埋点 sdk 没初始完,上报行为就已经产生了,设置 unInitQueue 来存储。
- 以页面为维度来管理埋点配置,便于维护和迁移。
考虑到埋点 sdk 没初始完,上报行为就已经产生了,比如曝光,新增如果这时候生成对应的点进入缓冲队列,就是属于无效的点因为没有加载到坑位信息、配置参数等,所以针对这种场景下产生的点位信息,我们新开一个队列存储,等到初始化完成再去处理;
因此,埋点上报总体流程为:埋点 sdk 接受返回埋点的函数,将其返回值上报,支持上报多个埋点;埋点事件由应用发送给埋点 sdk 后,埋点 sdk 首先会对数据进行处理,再调用数据平台暴露的方法, 将埋点事件上报给数据平台。
具体实现
判断页面可见性
虽然 Page Visibility API 的浏览器兼容情况不错,但对于Android、iOS 和最新的 Windows 系统可以随时自主地停止后台进程,及时释放系统资源。因此,基于 Google 描述网页生命周期的 Page Lifecycle API 兼容库 PageLifecycle.js 来监听页面可见性变化——一个网页从载入到销毁的过程中,会通过浏览器的各种事件在以下六种生命周期状态 (Lifecycle State) 之间相互转化,通过监听页面生命周期的变化并记录其时间,就可以相应获取页面可见性的统计数据:
- active:网页可见且具有焦点;
- passive:网页可见但处于失焦状态;
- hidden:网页不可见但未被浏览器冻结,一般由用户切换到别的 tab 或最小化浏览器触发;
- frozen:网页被浏览器冻结(后台任务比如定时器、fetch等被挂起以节约 CPU 资源);
- terminated:网页被浏览器卸载并从内存中清理。一般用户主动将网页关闭时触发此状态;
- discarded:网页被浏览器强制清理。一般由系统资源严重不足引起。
由此可得,页面生命周期状态和页面可见状态之间的映射关系为
- active + passive = visible;
- hidden + terminated + frozen + discarded = invisible。
因此,通过监听 statechange 来识别页面可见状态的改变,在生命周期状态为 active
和 passive
时标记页面为 visible
状态,在生命周期状态为其他几个时标记页面为 invisible
状态,更新最后一次可见的时间戳