原文链接: https://juejin.cn/post/7415911686929268788
作者:UAENA
首先回答一下什么是埋点:
埋点是一种用于跟踪用户在网站或应用中行为的数据采集技术,通过记录点击、浏览等操作,帮助团队进行用户行为分析、AB 实验、错误监听,指导优化方向和资源分配
为了让读者能够从头到尾彻底学会埋点,我会分别从 埋点的概念与使用、如何实现一个埋点 sdk 入手
这篇文章核心围绕埋点的概念与使用展开,涉及到几个维度:
🎶 埋点的基本概念 🧠 埋点研发流程优化 🚀 埋点数据的消费经验
我们直接进入主题,相信能给大家带来不少的收获
一、埋点的基本概念
在业务开发中,埋点是一定要做的,那作为一名合格的研发,我们先要了解埋点都有什么类型,可以有效解决什么问题,这一部分的内容比较基础,网上也有很多精彩的文章,有一定基础的同学可以直接跳过~
我们会从几个维度进行基本概念的介绍:
监控类型 上报方式 埋点的生命周期(从创建到数据消费)
1. 监控类型
基于我们要监控的内容,可以分为:数据监控、性能监控、异常监控等三个部分
我们先讲述一下几个类型的基本概念,后面会详细讲解如何实现的
1.1 数据监控
特点:
数据监控主要指对用户行为、业务数据等进行埋点监控,采集并上报关键信息。他的目的是:
=> 通过收集用户在系统中的行为数据,帮助产品经理、运营人员更好地分析用户行为,优化产品决策
举例:
用户操作数据:点击某个按钮、页面的浏览路径、搜索的关键词、用户停留时间等。 电商平台:监控用户的商品浏览记录、加入购物车、购买行为、支付情况等数据。 社交平台:记录用户点赞、评论、分享、发布动态等操作。
例如,在一个电商网站中,当用户点击了“购买”按钮,系统会埋点记录这次点击事件,包括用户 ID、商品 ID、点击时间等,都会被上报到服务器,用于分析转化率、购物路径优化等
优缺点:
优点:
数据量大,覆盖面广,有利于全面掌握用户的操作行为 为业务决策提供有力的数据支持,帮助进行 AB 实验、用户行为分析等 缺点:
数据量大时,可能增加数据处理和存储的成本 埋点较多时,可能影响页面性能 考虑数据的置信问题,埋点的开发维护成本会偏高
1.2 性能监控
特点:
性能监控关注的是系统性能的表现,重点监控页面的加载时间、渲染时间、资源请求时间。他的目的是:
=> 帮助开发者了解项目的性能数据,为性能优化做导向
举例:
监控页面从开始加载到完全展示各阶段的时间,如 FP、FCP、LCP、JS 初始化、主接口加载等
优缺点:
优点:
及时发现性能瓶颈,提升用户体验,降低用户流失率 可以通过历史数据跟踪性能优化效果,确保持续优化 缺点:
采集的部分性能指标(如两秒开率等)可能需要与服务端结合,增加开发复杂度
1.3 异常监控
特点:
异常监控主要用于捕获系统中的异常情况,包括 JavaScript 错误、接口请求失败、未处理的 Promise 错误、资源加载失败等,它的目的是:
=> 通过及时捕获并上报异常信息,帮助开发者迅速定位问题、解决问题
举例:
JavaScript 错误:捕获页面中运行时的 JavaScript 错误,如未定义变量。例如,当我访问某个页面时,某个 JavaScript 模块发生了错误,导致页面无法正常展示,异常监控会将错误信息、错误堆栈、用户操作等信息记录并上报 接口请求失败:监控 API 请求的状态码、超时情况等,如请求返回 500 错误或出现网络超时等问题 资源加载失败:监控页面中图片、视频、CSS、JS 文件的加载失败情况,帮助开发者检查资源引用路径或网络问题
优缺点:
优点:
实时捕获异常,帮助开发者迅速发现并修复问题,提高用户体验 能够自动化捕获大量错误信息,减少手动调试的压力 缺点:
捕获到的异常信息可能过多,数据筛选和处理的难度较高 需要额外的策略来区分重要异常和无关紧要的异常,以避免过多无效的报警
2. 上报方式
在了解了埋点的监控类型后,我们再看看埋点的上报都有哪些方式
2.1 手动上报
特点:
手动上报是指我们基于业务需求,在代码中显式地添加埋点逻辑
每当需要记录用户行为或系统事件时,手动调用上报函数,将数据发送至服务器,这种方式的可控性强,开发者可以精确地控制埋点位置和上报时机
举例:
在用户点击某个按钮时,开发者会在按钮的点击事件中调用埋点上报函数,如:
button.addEventListener('click', () => {
sendEvent('click_button', { userId: '12345', time: Date.now() });
});
这种方式能够精确记录用户点击的时间、操作对象等详细信息。
页面展示:在页面加载完成时,埋点记录页面的展示情况
window.addEventListener('load', () => {
sendEvent('page_view', { page: 'homepage', time: Date.now() });
});
组件 DOM 超出 50% 曝光:使用 IntersectionObserver
API 监测页面上特定组件的可见性,当组件的可见部分超过 50% 时,自动上报组件的曝光情况
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && entry.intersectionRatio > 0.5) {
sendEvent('component_exposure', { component: 'banner', time: Date.now() });
}
});
}, {
threshold: [0.5] // 超过50%可见时触发
});
const component = document.querySelector('#banner');
observer.observe(component);
IntersectionObserver - Web API | MDN (mozilla.org)
优缺点:
优点:
高度灵活,开发者可以精确控制上报时机、数据内容等 适合复杂业务场景或需要精确控制的埋点需求 缺点:
需要手动编写大量代码,增加了开发和维护成本 如果埋点较多,容易遗漏或出现冗余埋点,影响代码质量,甚至导致数据不置信
2.2 可视化上报
特点:
可视化上报是通过图形界面或后台工具来配置埋点,无需开发人员手动编写代码。在业务人员或测试人员通过可视化工具对页面上的元素(如按钮、链接等)进行选择和配置后,系统自动生成埋点逻辑并上报数据。这种方式降低了技术门槛,使非开发人员也能够参与埋点配置
大家可能好奇这种可视化的操作是如何实现的,这个会在后续的全流程实现模块中详细介绍~
举例:
使用一些埋点平台(如 GrowingIO、神策数据等),业务人员可以在后台系统的可视化界面上点击页面元素,配置该元素的埋点逻辑。系统会自动捕获该元素的操作,并将数据上报至服务器
优缺点:
优点:
减少了开发者的工作量,业务人员可以快速配置埋点 埋点配置灵活,可以根据需要随时调整,无需发布新的代码 缺点:
灵活性不如手动上报,复杂的业务逻辑可能无法通过可视化工具实现 依赖第三方工具,可能带来兼容性或平台限制的问题
2.3 自动上报(无埋点)
特点:
自动上报(无埋点)是指系统通过框架或插件自动捕获用户行为或系统事件,无需手动埋点
举例:
使用无埋点方案时,项目初始化阶段会注册相关的监听,这种方式依赖于 SDK 的能力,自动监听页面上的交互事件、网络请求、资源加载等,并将数据上报到后台进行分析
优缺点:
优点:
减少了开发和运维成本,开发人员无需为每个事件手动编写埋点逻辑 缺点:
灵活性较低,自动捕获的行为数据可能无法满足某些特定业务需求,且不容易精确控制上报的内容和时机
当然,一个优秀且完整的企业级埋点系统,是灵活支持各类监控类型的,不同的类型专门服务不同的场景,实现对目标项目的全方位监控
3. 埋点的生命周期
在了解了埋点类型和上报方式后,我们再来学习一下:一个需求中埋点的全流程
如果再精确到研发主要负责的范围,我们可以给出一个更精细化的图:
虽然该流程覆盖了埋点从需求确认到数据消费的各个步骤,但在需求确认、开发自测、数据监控等环节可能存在数据遗漏、埋点不准确、数据质量监控不足等问题:
需求确认与埋点设计环节容易遗漏场景:埋点需求的确认和埋点方案设计环节,产品经理对用户行为理解不全面,或者开发人员没有参与设计环节,可能会遗漏一些关键场景或不常见的边缘操作
开发和自测环节容易出现数据不一致:在埋点开发和自测阶段,开发人员可能会因为疏忽或理解偏差,导致埋点的事件数据与需求不符。例如,埋点位置不准确、上报参数不完整、或埋点上报的时间点不对
埋点对性能的影响未充分考虑:如果埋点过多或者不合理设置上报频率,可能会导致页面性能下降,例如页面加载变慢、用户操作卡顿等问题,影响用户体验
二、埋点研发流程优化
我们可以通过自动化工具
、加强测试覆盖
等方式为了提高埋点质量问题,同时确保埋点对性能的影响最小化,至于具体怎么做,下面会给出一些实践经验:
1. 类型代码约束
我们可以通过 Typescript
为埋点的上报提供类型约束,它能带来的收益很明确:
尽可能避免埋点过程中出现数据格式、字段不一致等问题(你要是不管 error,咱也没办法😀)
了解了收益后,我们再来学习一下类型代码的消费方式,以及一个合理的类型代码结构
我们先提供一套最基本的埋点上报函数的封装(sendEvent):
type LogParams = Record<string, string | number | boolean | undefined | null>;
export const sendEvent = (event: string, params?: LogParams) => {
// 埋点上传
};
然而这种类型提示非常的粗糙,对于上报时的检测效果很不理想,那如果要实现非常严格的代码检查,我们可以考虑用 ts 类型体操实现升级:
export const sendEvent = <T extends EventNames>(
event: T,
params?: TrackInterfaces[T]
) => {
// 埋点上传
};
export interface TrackInterfaces {
main_picture_click: MainPictureClick;
page_view: PageView;
}
export type EventNames = "main_picture_click" | "page_view";
/**
* 页面浏览
*/
export interface PageView {
/**
* 应用名称
*/
appName: string;
/**
* 平台
*/
device_platform: string;
/**
* 来源
*/
enter_from: string;
/**
* 链接
*/
page_url: string;
/**
* 页面名称
*/
page_name: string;
}
可以看到我们通过一个枚举类型,维护了项目所有包含的类型名称,通过 T extends EventNames
获取了函数中具体的事件名,再通过 TrackInterfaces
映射到了具体的一个类型,比如:
// 事件名 page_view => 映射类型 PageView
sendEvent('page_view',{
enter_form: 'douyin'
page_name: 'home'
// 参数……
})
话都说到这里了,我们可以再深入一下,对于通参(公共参数,比如操作系统、手机型号等),如果每次都要传递,是不是有点麻烦,那如果我们要抽离出来,该怎么做呢?代码如下:
export interface TrackInterfaces {
main_picture_click: MainPictureClick;
page_view: PageView;
}
export type EventNames = "main_picture_click" | "page_view";
// 公共属性
type CommonParams = {
device_platform: string;
// ……
};
type trackParams<T extends EventNames> = Partial<CommonParams> & Omit<TrackInterfaces[T], keyof CommonParams>;
export const sendEvent = <T extends EventNames>(
event: T,
params?: trackParams[T]
) => {
// 埋点上传
};
通过这种方式,我们把通参从具体的类型中剔除,可以放在一个独立的方法(比如叫 addCommonParams)中维护(一个变量,如 commonParams),在其他事件上报时插入即可
话说回来,维护一个类型代码结构也是相当困难啊,尤其是事件很多的时候,这种情况下,就必须要我们通过一些自动化工具来实现埋点数据转类型代码了
这个问题是我在团队中遇到的,基于这个问题,我开发了一套基于 VSCode
的埋点类型转换插件,核心借助 quick_type 实现,核心的实现逻辑如下:
从埋点管理平台获取事件数据结构 => 事件数据格式解析 => 投喂
quick_type
进行转换 => 进一步加工成合适的格式 => 插入项目代码
大家要是感兴趣也可以实现一下,后续我会考虑把这个插件重写一份并开源出来,也可以用 sdk 的方式提高适用面
2. 埋点测试用例
测试用例可以用很多种方式进行,比如单元测试、抓包测试等,其中单元测试可以通过如 Jest
或 Mocha
实现,存在一定 coding 成本,我们这边展开讲讲抓包测试的实现方式
最简单粗暴的,我们可以使用浏览器开发者工具、Charles 等工具,捕获网页上的网络请求,观察上报的参数是否符合埋点事件的要求
然而这样的方式比较笨,批量检查起来会比较麻烦,因此一般的埋点管理平台会提供一个界面,能够实时显示埋点上报的具体内容,类似于抓包工具,可以有效地提高测试阶段的效率与准确率
对于流程的优化,还有很多的思路,比如聚合上报埋点事件(单位时间内事件打包成一个请求进行上报),可以有效降低请求带来的性能影响,具体的实现就先不在这里详细讲述了
三、埋点数据的消费经验
高效消费埋点数据是数据驱动决策的重要环节,下面我将从多个角度来解释如何高效消费埋点数据,并进行有效的场景分析
等等,你说这都是产品或数据分析师的活?研发应该努力跳出自己的舒适圈,从各方面提高自己的业务能力、解决问题的能力,尤其在如今数据驱动业务的环境下,能做好这些的研发会更有核心竞争力
PV 和 UV
PV(Page View) :页面访问量 UV(Unique Visitor) :独立访客数
我们可以通过 PV 和 UV 评估不同页面的访问量。可以通过聚合查询每日/每周的 PV 和 UV 进行趋势分析:
SELECT page_name, COUNT(DISTINCT user_id) AS UV, COUNT(*) AS PV
FROM page_view_events
WHERE date BETWEEN '2024-09-01' AND '2024-09-08'
GROUP BY page_name;
当然 SQL
这一步,埋点平台都会做好了,我们不用关心具体的实现,这里展示只是为了更好说明逻辑
事件行为分析
事件行为分析通过埋点捕捉用户的操作习惯和行为路径,是我们最常用的分析模式,常用的操作如下:
1. 分析 click_button 事件中按钮名称为某个具体值的 PV
SELECT
button_name,
COUNT(*) AS PV
FROM events
WHERE event_name = 'click_button'
AND button_name = 'submit' -- 按钮名称为 'submit'
GROUP BY button_name;
WHERE event_name = 'click_button'
:过滤出click_button
事件,表示按钮点击行为AND button_name = 'submit'
:只筛选按钮名称为submit
的点击事件COUNT(*) AS PV
:计算按钮被点击的总次数,即该按钮的 PV
结果:
假设返回的 PV 为 500,表示 submit
按钮被点击了 500 次
2. 分析 purchase
事件的用户停留时长的平均数和 90 分位数
90 分位数(90th percentile)表示的是在一组数据中,90% 的数值小于等于这个数值,而另外 10% 的数值大于这个数值
SELECT
AVG(duration) AS avg_duration, -- 计算平均持续时间
PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY duration) AS p90_duration -- 计算 90 分位数
FROM purchase_events
WHERE event_name = 'purchase';
AVG(duration)
:计算所有purchase
事件的duration
字段的平均值PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY duration)
:计算duration
的第 90 分位数WHERE event_name = 'purchase'
:只过滤出购买事件的数据
结果:
avg_duration
:平均持续时间,例如返回180
秒,表示用户平均花费 180 秒完成购买流程p90_duration
:90 分位数的持续时间,例如返回300
秒,表示 90% 的用户花费少于 300 秒完成购买
路径转换分析
路径转换分析用于跟踪用户从进入系统到完成某个目标(如购买、注册、下单)的转化过程,可以查看用户经过的关键步骤,发现在哪一步可能有大量用户流失
转换分析应该按用户 ID 逐个跟踪,确保每个用户的行为链可以被完整记录下来
假设我们有一个电商平台,目标是分析从用户进入首页到完成购买的路径转化率
步骤 1: 定义事件表结构
假设数据库中存储了用户的事件数据,每个事件都有 user_id
和 event_name
:
CREATE TABLE user_events (
user_id VARCHAR(50), -- 用户 ID
event_name VARCHAR(50), -- 事件名,如 "page_view"、"add_to_cart"
event_time TIMESTAMP -- 事件发生时间
);
步骤 2: 查询每个用户在各个步骤的转化情况
为了简单起见,假设路径有三个关键步骤:
page_view
:进入产品详情页。add_to_cart
:将产品加入购物车。purchase
:完成购买。
下面的 SQL 查询可以计算每个用户是否完成了从 page_view
到 add_to_cart
再到 purchase
的全路径。
SELECT
user_id,
MAX(CASE WHEN event_name = 'page_view' THEN 1 ELSE 0 END) AS viewed_product,
MAX(CASE WHEN event_name = 'add_to_cart' THEN 1 ELSE 0 END) AS added_to_cart,
MAX(CASE WHEN event_name = 'purchase' THEN 1 ELSE 0 END) AS purchased
FROM user_events
WHERE event_name IN ('page_view', 'add_to_cart', 'purchase')
GROUP BY user_id;
步骤 3: 计算路径中的转化率
上面的查询会生成一个结果表,显示每个用户是否完成了某个操作步骤。基于这些数据,可以进一步计算转化率。例如,下面的 SQL 可以计算从查看产品
到加入购物车
再到完成购买
的转化率。
WITH conversion_data AS (
SELECT
user_id,
MAX(CASE WHEN event_name = 'page_view' THEN 1 ELSE 0 END) AS viewed_product,
MAX(CASE WHEN event_name = 'add_to_cart' THEN 1 ELSE 0 END) AS added_to_cart,
MAX(CASE WHEN event_name = 'purchase' THEN 1 ELSE 0 END) AS purchased
FROM user_events
WHERE event_name IN ('page_view', 'add_to_cart', 'purchase')
GROUP BY user_id
)
SELECT
COUNT(*) AS total_users,
SUM(CASE WHEN viewed_product = 1 THEN 1 ELSE 0 END) AS total_viewed_product,
SUM(CASE WHEN viewed_product = 1 AND added_to_cart = 1 THEN 1 ELSE 0 END) AS total_added_to_cart,
SUM(CASE WHEN viewed_product = 1 AND added_to_cart = 1 AND purchased = 1 THEN 1 ELSE 0 END) AS total_purchased,
(SUM(CASE WHEN viewed_product = 1 AND added_to_cart = 1 THEN 1 ELSE 0 END) * 100.0) / SUM(CASE WHEN viewed_product = 1 THEN 1 ELSE 0 END) AS cart_conversion_rate,
(SUM(CASE WHEN viewed_product = 1 AND added_to_cart = 1 AND purchased = 1 THEN 1 ELSE 0 END) * 100.0) / SUM(CASE WHEN viewed_product = 1 AND added_to_cart = 1 THEN 1 ELSE 0 END) AS purchase_conversion_rate
FROM conversion_data;
输出结果:
total_users
:所有参与路径的用户数total_viewed_product
:访问了产品详情页的用户数total_added_to_cart
:添加了购物车的用户数total_purchased
:完成购买的用户数cart_conversion_rate
:从查看产品到加入购物车的转化率purchase_conversion_rate
:从加入购物车到完成购买的转化率
最终的结果一般会以漏斗图的形式展现,如:
通过这些结果,我们可以直观地看到每个关键节点的用户流失情况,并根据这些转化率数据进一步优化流程或产品设计 比如,add_to_cart
的转化率较高,但 purchase
的转化率较低,这可能意味着在支付环节出现了问题
异常值与极端行为分析
有时,我们需要分析极端的用户行为(如长时间不操作或过于频繁操作)。通过分析这些异常行为,研发可以定位到系统中的潜在问题或优化点
例如,某个用户反复多次点击某个按钮,这可能是系统卡顿或用户困惑导致的行为。
SELECT user_id, COUNT(*) AS click_count
FROM event_log
WHERE event_name = 'click_button'
GROUP BY user_id
HAVING click_count > 10;
数据变更归因
在埋点数据分析过程中,数据变更归因也是非常重要的,归因分析的场景可以应用于多种情况,如性能优化效果、A/B 测试结果等
以下是几个常见的数据变更归因分析的应用场景:
1. 某次性能优化,1.5 秒开率增长远大于 2 秒开率增长
在一次性能优化后,发现产品的秒开率(即页面在 1.5 秒内完全加载的比例)有了显著提升,但相比之下,2 秒内的开率增长较为缓慢
其中,优化前 2s 开率 > 50%
用户的秒开率呈近似正态分布的形式,一次性能优化可以理解为分布左移,表现如下(图画的有点粗糙,请见谅):
可见有时候数据分析是需要经过严谨的推论才能给出置信的答案的
这时候又要问了:这不应该是数据分析师(DA)做的事吗?汇报的时候 DA 不一定会帮你分析,很多时候都得依赖自己,还是那个标准:跳出自己的舒适圈~
2. A/B 实验增长、下跌分析归因
A/B 实验是常用的用增手段,用来验证某个新功能或设计的效果(在 C 端业务比较常见)
通过对比 A 组(原始版本)和 B 组(新版本)的转化率,可以分析出某些指标的增长或下跌原因
我们可以分析两个版本在关键指标上的表现差异,找出新版本带来的改进点或不足之处
例如:
B 组的页面停留时间增加,说明新版本可能增强了用户的兴趣 B 组的转化率下降,可能是用户在新版本的交互上出现了问题
四、结语
相信这一套下来,大家对埋点的掌握也有一定深度了,考虑到篇幅,我们把埋点 sdk 的实现放在下一篇,同时也会涉及数据存取的优化方案等技术实现,敬请期待!