埋点与监控
定义
前端埋点
是指在网页或者应用程序中插入特定的代码,用于收集用户的行为数据并发送给服务器进行分析。这些数据可以包括用户的点击、浏览、输入等操作,帮助开发者了解用户的在其网站中的行为,从而进行针对性的优化和改进。
前端埋点通常包括以下几个步骤:
- 定义事件:定义需要收集的数据事件,如点击、浏览等。
- 添加代码:在网页或应用程序中添加特定的代码,用于收集事件数据。
- 发送数据:将收集到的数据发送给服务器进行分析。
- 分析数据:对收集到的数据进行分析和挖掘,找出用户行为规律和需求,为产品的改进和优化提供依据。
前端监控
数据监控 (主要关注用户在网站或应用中的行为和交互)
- PV:即页面浏览量或点击量;
- UV:指访问某个站点或点击某条新闻的不同 IP 地址的人数
- 用户在每一个页面的停留时间
- 用户通过什么入口来访问该网页
- 用户在相应的页面中触发的行为
性能监控 (主要关注网站或应用的加载速度、响应时间和用户体验等方面)
- 不同用户,不同机型和不同系统下的首屏加载时间
- 白屏时间
- http 等请求的响应时间
- 静态资源整体下载时间
- 页面渲染时间
- 页面交互动画完成时间
异常监控 (主要关注网站或应用在运行过程中是否出现错误或异常)
- Javascript 的异常监控
- 样式丢失的异常监控
采集性能数据的API: https://developer.mozilla.org/zh-CN/docs/Web/API/Performance_API/Navigation_timing
埋点的分类
| 埋点类型 | 定义与侧重点 | 典型应用场景 |
|---|---|---|
| 展现埋点 | 记录用户是否看到特定内容 | 统计网页中某个广告或推广信息的展示次数 |
| 曝光埋点 | 侧重于内容是否被充分曝光(用户有机会注意到) | 广告投放中,判断广告是否被用户实际“看到” |
| 交互埋点 | 记录用户与元素的交互行为 | 监控用户点击按钮、填写表单、分享等具体操作 |
实现方式
代码埋点(手动) 原理:在事件触发处手动插入JS代码。 优势:灵活性高,可精确捕获复杂行为。 劣势:开发维护成本高,易漏埋。 代表:友盟、百度统计。
可视化埋点 原理:通过可视化界面圈选元素生成配置,无需修改代码。 优势:操作简单,非技术人员也可配置,开发成本低。 劣势:灵活性受限,难以处理复杂逻辑。 代表:Mixpanel。
无痕埋点(自动) 原理:SDK自动采集所有用户行为数据,后期按需筛选。 优势:无需手动埋点,无漏埋风险,适合快速迭代。 劣势:数据传输与存储压力大,可能采集冗余数据。 代表:GrowingIO。
收集的数据
| 类型 | 页面浏览数据 | 用户行为数据 | 错误数据 | 用户属性数据 | 设备信息 | 使用时长数据 | 搜索关键词数据 |
|---|---|---|---|---|---|---|---|
| 包含 | 页面的PV、UV、停留时间 | 用户的点击、滚动、输入等操作行为 | 代码中的错误信息、异常情况 | 用户年龄、性别、地域等 | 用户设备类型、操作系统、浏览器等信息 | 用户使用产品的时长、频次等 | 用户在搜索框中输入的关键词信息 |
| 作用 | 反映页面受欢迎程度以及用户黏性 | 反映用户的兴趣和偏好 | 帮助开发者定位和解决问题 | 定位用户的数据、营销 | 优化产品跨平台体验 | 反映产品的用户黏性和活跃度 | 优化产品搜索功能 |
手动插桩(命令式埋点)
最原始的方式,直接在业务事件回调里调用上报函数。
// 点击按钮时手动上报
document.getElementById('buy-btn').addEventListener('click', () => {
// 业务逻辑...
track('click', { id: 'buy-btn', page: 'detail' }); // 插桩代码
});
声明式插桩(属性埋点)
在HTML元素上挂载自定义属性,由统一监听器来读取并上报,实现业务与埋点解耦。
document.addEventListener('click', (e) => {
const target = e.target.closest('[data-track-id]');
if (target) {
track('click', {
id: target.dataset.trackId,
page: target.dataset.trackPage
});
}
});
编译时插桩(AOP 自动包裹)
利用 Babel、SWC 等编译器插件,在构建阶段自动注入代码。比如自动给所有函数加上耗时统计,或给特定组件自动加上页面曝光。
// 源码
function fetchData() { /* ... */ }
// 经过 Babel 插件插桩后大致等价于
function fetchData() {
const start = performance.now();
try {
return originalFetchData();
} finally {
track('function_timing', { name: 'fetchData', duration: performance.now() - start });
}
}
运行时动态插桩(API劫持/猴子补丁)
在运行时重写原生DOM API、Fetch/XHR、History API等,实现对网络请求、页面切换的自动采集,完全无需业务代码配合。
// 劫持 fetch 实现全量请求监控
const origFetch = window.fetch;
window.fetch = function(...args) {
track('api_request', { url: args[0] });
return origFetch.apply(this, args);
};
用户点击统计
一般使用事件委托,在 document 层统一监听,然后根据元素标签、类名、ID、data-* 属性或 XPath 路径来唯一定位用户点了哪里。
document.addEventListener('click', (e) => {
const el = e.target;
const clickInfo = {
tag: el.tagName,
id: el.id,
class: el.className,
text: el.innerText?.slice(0, 50), // 截取部分文本
xpath: getXPath(el) // 辅助定位
};
navigator.sendBeacon('/api/click', JSON.stringify(clickInfo));
});
页面浏览统计
需区分两类场景:
- 多页应用(MPA):直接监听
load事件,或利用Performance API获取页面加载信息上报。
window.addEventListener('load', () => {
const [entry] = performance.getEntriesByType('navigation');
track('page_view', {
url: location.href,
referrer: document.referrer,
loadTime: entry.loadEventEnd - entry.fetchStart
});
});
- 单页应用(SPA):路由变化不会触发完整页面加载。必须劫持
history.pushState/replaceState并监听popstate事件,或者在路由守卫里手动上报。
const origPush = history.pushState;
history.pushState = function(...args) {
origPush.apply(this, args);
track('page_view', { url: location.href }); // 每次路由跳转上报
};
window.addEventListener('popstate', () => {
track('page_view', { url: location.href });
});