数据埋点(二)
这是我在旺仔俱乐部小程序上的埋点实践,小程序使用uniapp
开发。
场景
埋点场景:
- 按钮点击;
- 弹窗进入、退出;
- 页面进入、离开、停留;
- 页面分享出去、分享进入;
- 程序逻辑。
我在俱乐部碰到的埋点可以归结成上面几种场景,下面对每种场景做简单举例。
按钮点击:登录按钮的点击埋点,不需要知道用户是否同意或拒绝登录,仅点击即埋点;广告 banner 的点击埋点;底部导航栏的点击埋点。
弹窗进入、退出:首次进入时首页的弹窗;扫码出现的弹窗。
页面进入、离开、停留:每个页面的进入退出停留埋点;1. 进入首页进行首页进入埋点;2. 点击“会员码”页面,进行首页离开埋点;3. 同时“会员码”页面进行进入埋点;4. 停留在“会员码”页面后每 10s 进行一次停留埋点。
页面分享出去、分享进入埋点:点击分享进行埋点,分享进入链接会携带分享参数,拿到参数进行分享进入埋点。
程序逻辑:代码执行到某种条件内进行埋点,如果这种条件通过点击触发可以反映到 ui 上,又可以转换为第一种按钮点击的情况;如果埋点是在后端接口的 api 中,这种埋点就更适合服务端执行。
做法
埋点的内容
埋点的内容保存在一个或多个文件中,埋点的组件或者函数会在被执行的时候从这些文件中找到具体的埋点内容。
可以根据埋点规模和团队习惯来确定埋点内容的保存方式,是分散在每一个埋点处,还是提取到一个文件中,还是分类至多个文件中。
俱乐部使用的保存方式是把埋点内容分类至多个文件中。
埋点的执行
我的实践中具体进行埋点的时候会涉及到的组件和函数:
组件:clicker-tracking
,viewer-tracking
。
函数:sendEventTagByDomId
,sendEventTagByHardId
。
使用说明:
- 组件
clicker-tracking
包裹在按钮组件外,点击进行按钮点击埋点; - 组件
viewer-tracking
放在弹窗组件内,弹窗展示进行弹窗进入埋点,弹窗关闭进行退出埋点; - 组件
viewer-tracking
放在页面中,进行页面进入、离开、停留埋点; - 点击分享的时候在程序中调用
sendEventTagByHardId
埋点,分享进入的时候检测链接的分享参数,调用sendEventTagByHardId
进行分享进入埋点; - 程序逻辑中调用
sendEventTagByHardId
进行埋点。
解释
组件 clicker-tracking
组件clicker-tracking
的vue
实现放在最后。
组件套在按钮上的样子:
1 | <clicker-tracking |
可以看到我要向组件传递 3 个参数,eid
和scope
来定位具体的埋点内容,data
用来传递埋点内容需要的变量,这个例子里需要的是按钮的名称,这里按钮的名称是根据状态改变的,所以传递了eventBtnName
这个变量。
Props
Name | Type | Default | isRequired | Description |
---|---|---|---|---|
scope | string | / | ✅ | 用于查找埋点内容 |
eid | string | / | ✅ | 用于查找埋点内容 |
data | Record<string, unknown> | / | 🔴 | 传递埋点内容的变量 |
组件 viewer-tracking
组件viewer-tracking
的vue
实现放在最后。
组件放在页面中的样子:
1 | <viewer-tracking :sharedData="{ pageName: '每日签到' }" /> |
这里的作用是“每日签到”页面的埋点,进入埋点、离开埋点和停留埋点。
如果不想要停留埋点,就像这样:
1 | <viewer-tracking :sharedData="{ pageName: '每日签到' }" disableStay /> |
从组件的props
可以看到,和组件clicker-tracking
差不多,也使用scope
和enterId
、stayId
、leaveId
来定位具体的埋点内容,enterData
、stayData
、leaveData
用来传递埋点内容需要的变量,而且有disable
属性用来控制关闭进入、停留或离开埋点,这是为了适应有的页面不用记录停留埋点的需求。(代码块中的sharedData
是enterData
、stayData
、leaveData
的封装,最后的组件实现中有解释。)
这个组件还有些特殊的地方,它的执行总会跟随组件的绑定
状态。页面进入和离开可以看成是页面的绑定
和解绑
,弹窗的弹出和关闭可以看成是弹窗的绑定
和解绑
,所以这个组件要可以监听到这种状态。这就是组件里watch
的isBinded
的作用,还有生命周期mounted
和beforeDestroy
的作用,isBinded
具体的实现我放在最后。
Props
Name | Type | Default | isRequired | Description |
---|---|---|---|---|
scope | string | VIEWER | 🔴 | 用于查找埋点内容,浏览埋点通常都有默认相同的结构,所以设置默认值 |
enterId | string | enter | 🔴 | 用于查找埋点内容,浏览埋点通常都有默认相同的结构,所以设置默认值 |
stayId | string | stay | 🔴 | 用于查找埋点内容,浏览埋点通常都有默认相同的结构,所以设置默认值 |
leaveId | string | leave | 🔴 | 用于查找埋点内容,浏览埋点通常都有默认相同的结构,所以设置默认值 |
enterData | Record<string, unknown> | / | 🔴 | 传递埋点内容(进入)的变量 |
stayData | Record<string, unknown> | / | 🔴 | 传递埋点内容(停留)的变量 |
leaveData | Record<string, unknown> | / | 🔴 | 传递埋点内容(离开)的变量 |
sharedData | Record<string, unknown> | / | 🔴 | 传递埋点内容的变量,这个组件通常会在一个页面里,进入、停留、离开的埋点需要的变量也是相同的,这个属性就是相同的部分,设置之后就不用分别为三个埋点设置埋点内容的变量了 |
disableEnter | boolean | false | 🔴 | 关闭进入埋点 |
disableStay | boolean | false | 🔴 | 关闭停留埋点 |
disableLeave | boolean | false | 🔴 | 关闭离开埋点 |
函数 sendEventTagByDomId 和 sendEventTagByHardId
如果我们用有逻辑和无逻辑划分页面,逻辑脚本是属于有逻辑的,页面结构是属于无逻辑的。组件来配合页面结构埋点,而函数就配合逻辑脚本进行埋点。
sendEventTagByDomId
和sendEventTagByDomId
进行埋点时要传递的参数和组件属性差不多,就像这样:
1 | this.sendEventTagByDomId({ |
1 | this.sendEventTagByHardId({ |
它们都需要scope
和id
、e
来定位具体的埋点内容,data
来传递埋点内容需要的变量。两个函数不同的是函数名称和入参e
与id
,从名称中的DomId
和HardId
能看出来,一个和页面 DOM 有关,另一个和硬编码的 id 有关,下面是sendEventTagByDomId
放在程序环境中的样子:
1 | // 省略非埋点内容...... |
可以看到标签上的id
属性,函数sendEventTagByDomId
的入参e
取的就是这个id
,sendEventTagByDomId
最终会被转化为sendEventTagByHardId
,就像这样:
1 | function _sendEventTagByDomId(sendEventTagByHardId, { scope, e, data }) { |
注意
埋点的请求不应该占用太多资源,浏览器有请求数量限制,所以不可以让埋点请求挂起太长时间,导致真正的业务请求被阻塞。可以设置 1s 后不返回就关闭请求,也可以设置埋点的请求队列,推迟,在不再有业务请求的空闲时进行埋点。
埋点抛出的错误不能停止程序的运行,要在内部捕获错误。
埋点的请求可以不用携带cookie
、session
等和业务有关的请求头信息,结合上面的注意点,另外封装埋点的 http 请求函数会更方便。
不应该让这些规则复杂,停止炫技,不能让团队成员感到困惑。
其它
组件 viewer-tracking 的实现
1 | <template> |
组件 viewer-tracking 的 vue 实现
1 | <template> |
组件 viewer-tracking 内 isBinded 的实现
1 | // @/src/libs/tracking/tracking-store.js |
1 | // @/src/main.js |
组织结构和生成结果
1 | import SCOPE_CONFIG_MAPPER from "./scope-config-mapper" |
存放埋点内容文件
1 | export default ({ |