# 积分任务与埋点
在数字营销和用户参与策略中,积分任务系统已成为提升用户活跃度和忠诚度的重要工具。本文探讨了积分任务与埋点技术之间的关联,并分析了如何通过有效的埋点策略来优化积分任务系统。文章首先介绍了积分任务系统的基本框架,包括积分的获取、消耗和用户激励机制。随后,深入讨论了埋点技术在积分任务系统中的作用,包括用户行为追踪、数据收集、用户体验优化、任务完成验证、个性化推荐、效果评估、防止欺诈、A/B测试以及成本控制等方面。
# 积分任务基本框架
- App 启动初初始化积分 SDK 并从服务端拉取积分任务的配置信息。
- 用户操作,调用积分 SDK 对用户行为数据进行匹配、过滤和去重,并将数据上报到服务器。
- 服务端接收到行为数据后,会进行任务记录,任务评估等,确定任务达标后自动发放对应积分,并将结果发送回积分 SDK。
- 积分 SDK 在接收到服务端的响应后,会根据配置通过组件(Popup、Snackbar等方式)展示奖励结果给用户。
- 用户可以选择手动领取积分奖励,或者系统将自动完成发放过程。
上述的积分 SDK 离不开任务的埋点,埋点技术在积分任务系统中有不可或缺的作用。有了埋点,积分任务系统才能准确地记录用户的行为,并根据这些行为数据进行任务评估和积分发放。业务方可以优雅对接,不需要额外关心任务达成的判断,直接基于SDK透传埋点即可;新任务可以快速支持配置,业务方一次接入,后续零成本开发。
# 埋点方案
- 无痕埋点(全埋点)
前端页面通过设置监听器,拦截所有用户操作事件,并将其发送到后端数据采集平台。这些事件数据包括时间戳、用户ID、事件类型、事件属性等关键信息,全埋点是一种全量采集,无需开发人员干预的埋点方案,适用于运营阶段初期,产品功能相对简单的情况。
- 优点:自动采集所有用户行为数据,无需开发人员干预,节省人力、时间成本且灵活适应需求变化;覆盖所有用户交互动作,不会出现漏埋和误埋等现象。
- 缺点:数据量大,数据存储、计算、分析成本高,同时给数据传输和服务器增加压;无法定制化埋点,无法采集到指定事件和业务属性,数据维度单一,业务数据准确性和价值不高;大量的数据上报可能影响端侧的性能。
- 使用场景:业务场景简单,产品快速迭代需求比精细化分析优先级更高(只分析PV、UV)的场景。一般主流 App 会结合代码埋点等方式形成互补来满足不同场景的需求(代码埋点是不可以缺少的,全埋点是锦上添花)。
- 代码埋点
代码埋点是通过在业务代码中手动埋点来实现的,开发人员需要在业务代码中添加埋点代码,以便收集用户行为数据。代码埋点是侵入式的,需要工程师在每个需要埋点的位置上打点。
- 优点:触发方式灵活定义,高可定制化,满足特定业务目标;可以采集到指定事件和业务属性,数据维度丰富,业务数据准确性和价值高。
- 缺点:需要开发人员手动埋点,工作量大,代码侵入太强,过于耦合业务代码,一次埋点的更改就要引起发版之类的操作。
- 适用场景:适用于精细记录用户、业务定制化埋点的场景。适用于需要采集非点击、不可视行为,或者需要整合用户信息和行为附带属性信息的场景。
- 可视化埋点
可视化埋点是通过可视化交互的手段,代替代码埋点,可以新建、编辑、修改埋点。在组件和页面的维度进行埋点的设计。将业务代码和埋点配置分离,允许非技术人员(如产品经理、运营人员等)直接在应用界面上定义和设置数据收集点,而无需编写代码,从服务端拉取配置,根据配置监听相关交互操作并采集上报。
- 优点:可视化圈定并选择埋点模块,开发量小节省研发人力;非研发人员也可进行埋点操作;埋点更改后可以立即生效,无需发版。
- 缺点:可能无法覆盖所有用户行为,特别是那些不触发界面元素交互的行为;需要确保 SDK 与应用的兼容性,以及与后端分析平台的无缝对接,一旦对页面路径或模块名称等进行改动,埋点就会失效, 新增自定义事件比较困难;对于动态加载的内容,可视化埋点可能无法准确捕获数据。
- 适用场景:适用于非技术人员希望快速实施埋点快速收集用户反馈的业务场景。适用于业务页面多,且变动相对不是很频繁,有一定的分析场景但都能枚举, 用户行为可直接与页面可视元素挂钩。
# 常见埋点属性
{
// 用户基本信息
"userId": "uid_12345", // 用户ID,唯一标识用户
"userName": "", // 用户名称,根据实际情况填写
"isLoggedIn": true, // 用户登录状态
// 设备和操作系统信息
"deviceId": "device_id_abcdef", // 设备ID,唯一标识设备
"deviceType": "mobile", // 设备类型,如mobile、tablet、desktop等
"osType": "iOS", // 操作系统类型
"osVersion": "14.2", // 操作系统版本
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Mobile/15E148 Safari/604.1", // 用户代理字符串
// 应用信息
"appId": "com.example.myapp", // 应用ID,唯一标识应用
"appVersion": "1.2.3", // 应用版本
"appName": "MyApp", // 应用名称
// 时间和地理位置信息
"eventTime": 1678590400000, // 事件的时间戳,毫秒级
"localEventTime": "2024-04-19 12:34:56", // 事件的本地时间,格式为YYYY-MM-DD HH:mm:ss
"timeZone": "Asia/Shanghai", // 时区
"country": "CN", // 国家,ISO代码
"city": "Shanghai", // 城市
// 网络信息
"ip": "192.168.1.1", // 用户IP地址
"networkType": "4G", // 网络类型,如WiFi、4G、5G等
"carrier": "China_Mobile", // 运营商类型
// 事件详情
"eventType": "click", // 事件类型,如click、pageview等
"eventName": "Login_Button_Click", // 具体事件名称
"pageUrl": "http://example.com/login", // 当前页面的URL
"pageTitle": "Login Page", // 当前页面的标题
// 交互细节
"elementSelector": "#loginButton", // 触发事件的元素选择器
"elementPosition": { x: 150, y: 200 }, // 触发事件的元素位置
"clickType": "left", // 鼠标点击类型
// 性能指标
"pageLoadTime": 1200, // 页面加载时间,单位为毫秒
"firstContentfulPaint": 500, // 首次内容绘制时间,单位为毫秒
"domInteractive": 700, // DOM 交互时间,单位为毫秒
// 来源和行为路径
"referrerUrl": "http://example.com/referrer", // 上一个页面的URL
"previousPageUrl": "http://example.com/previous", // 用户访问的上一个页面URL
"sessionDuration": 300, // 用户在当前会话中的停留时间,单位为秒
// 自定义属性
"customAttributes": {
"membershipLevel": "gold",
"campaignId": "campaign_2024spring"
},
// 错误信息
"errorOccurred": false,
"errorDescription": "",
"errorStackTrace": ""
}
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
# 埋点上报方式
- XHR 发请求上报
- img 发请求上报
- navigator.sendBeacon() 发送请求上报
# XHR 发请求上报
使用 XMLHttpRequest 或 fetch API 发送异步请求,可以选择 GET 或 POST 方法,并将数据作为请求体或 URL 参数发送。
- 优点:发送异步请求,灵活地设置请求头属性,针对 POST 能发送的数据量大。
- 缺点:在页面销毁时,请求可能还未发送至服务端就被取消,导致数据上报失败;一般埋点域名并不是当前域名,需要处理跨域请求的问题(如设置CORS);GET 上报存在安全和数据大小局限性。
- 解决方案:可以使用 XMLHttpRequest 发送同步请求,以确保请求一定能发送到服务端。由于 fetch 和 axios 不支持同步请求,可以通过 XMLHttpRequest 发送同步请求。
如果使用 ajax 的话,会存在跨域的问题。而且数据上报前端主要是负责将数据传递到后端,并不过分强调前后端交互。因此我们可以通过一些支持跨域的标签去实现数据上报功能,例如 script,link,img。但是当我们使用 script 和 link 进行埋点上报时,需要挂载到页面上,而反复操作 dom 会造成页面性能受影响,而且载入 js/css 资源还会阻塞页面渲染,影响用户体验,因此对于需要频繁上报的埋点而言,script 和 link 并不合适。
# img 上报方式
通过创建一个 Image 对象,将要上报的数据作为 URL 参数,通过 src 发送一个 GET 请求来触发上报。
- 优点:兼容性好,支持跨域,不会阻塞页面加载。
- 缺点:只能发送GET请求,无法获取响应结果,不支持异步操作。不同服务器针对 URI 的长度有限制,长度超过限制时会出现 HTTP 414 错误。
# navigator.sendBeacon 上报方式
通过 HTTP POST 将少量数据异步传输到服务器,允许在页面卸载时异步发送数据,通常用于在页面关闭时进行最后的数据上报,以确保数据能够成功发送。
- 优点:页面销毁时的埋点请求不会中断,可靠地发送数据,不会阻塞页面关闭,支持在后台发送数据。
- 缺点:有数据大小限制(但一般够用了),因此它不适合发送大量数据;始终使用 HTTP POST 请求,且不返回响应,因此无法检查请求是否成功或处理服务器的返回结果;同样遵循同源策略,不能发送跨域请求,除非服务器设置了适当的 CORS 头。
sendBeacon 是异步的,不会影响当前页到下一个页面的跳转速度。这个方法还是异步发出请求,但是请求与当前页面脱离关联,作为浏览器的任务,因此可以保证会把数据发出去,不拖延卸载流程。相较于 img 标签,使用 navigator.sendBeacon 会更规范,数据传输上可传输资源类型会更多。相较于 ajax 在页面卸载时上报,ajax 有可能没上报完,页面就卸载了导致请求中断,因此 ajax 处理这种情况时必须作为同步操作。
对于埋点方式的考量,主要考虑的有三个因素:
- 跨域的问题
- 页面销毁时,如何保障还未成功上传的数据完成数据上传请求
- 大数据量的上传
所以,一般采用组合方式,根据数据量,选择 Image 或者 Beacon 的方式,若检测不支持 Beacon, 在大数据量时回退到传统的 XHR 请求。
# 可视化埋点
通过埋点平台圈选所需埋点的页面元素,进行埋点上报属性的配置与发布,由采集 SDK 同步埋点配置,并根据配置自动进行用户行为数据的采集和发送。
可视化埋点的基本思路:
以点击事件为例,Web 可视化埋点一般会提供一个 SDK,SDK 会在 document 上面监听 click 事件,借助于事件委托的特性,可以捕获到页面上任意元素的 click 事件及元素的信息。同时 Web 可视化埋点会提供一个平台,该平台通过 iframe 嵌入需要进行埋点配置的网页,然后通过 postMessage 来进行平台与目标页面的通信。但是像 React Native,无法通过 iframe 嵌入页面及 postMessage 实现平台与目标页面的通信,无法借助事件委托的特性来实现可视化埋点,可通过高阶组件的方式导出了跟点击相关的一些组件供业务方使用。
# 连接客户端与可视化埋点平台
埋点配置人员首先需要在可视化埋点平台开始一个埋点任务,可视化埋点平台前端会通过 WebSocket 连接到服务端,服务端会生成一个 sessionId 发送给前端,并且会将连接到服务端的 WebSocket 客户端进行登记。此时埋点配置人员在 React Native 客户端通过 SDK 提供的工具进入连接页面,输入 sessionId 后通过 WebSocket 连接到埋点可视化平台服务端,服务端也会将连接到的 WebSocket 客户端进行登记。
- React 高阶组件的思想,通过对 React Native 组件进行重写,添加我们埋点相关的逻辑;
- 通过类组件的 _reactInternals 可获取对应的 FiberNode 节点;
- FiberNode 相关的属性,比如可以通过 child、return、sibling 三个指针来对 FiberNode 树进行遍历,memoizedProps 和 memoizedState 可以用来替代组件的 props 和 state 等;
- 使用 babel 插件对代码进行改写,解决组件名称被混淆的问题。