# 页面埋点实践
埋点即为监控用户行为而预先埋设的统计代码。埋点的用途很多,视场景而定,例如统计访问入口、访问量、点击量、停留时间、异常监控等等,它是用户调研的依据,是产品效果的佐证,对产品迭代至关重要。
# 一、埋点方式
常见的埋点方式有以下几种:
1. 代码埋点
所谓代码埋点
,就是在需要统计数据的地方,植入发送埋点请求的代码,直接统计用户交互行为。
优点:针对性强,可实现精确埋点,且灵活度高,可针对几乎任何交互行为设置各种埋点信息。
缺点:业务代码与埋点代码耦合性强,工作量大,更新迭代代价大。
2. 无痕埋点
无痕埋点
并非不写代码,而是以高度解耦的形式,利用事件代理 + 元素路径的方式,实现非侵入无感知的埋点。
优点:高度解耦,工作量小,易维护。
缺点:记录行为信息少,不能禁用元素的事件冒泡。
3. 后端埋点
后端埋点
顾名思义,就是由服务器端、实现的埋点,主要针对数据交互行为的埋点,无法对用户非请求性行为埋点。
优点:可通过服务端日志获取埋点信息,减少请求数。
缺点:可监控行为类别较少,无法全方位监控用户行为。
# 二、埋点实现
本文仅介绍前端页面中的埋点实现,即代码埋点
和无痕埋点
。
目前,针对埋点信息的通常做法是利用img
标签的src
属性,携带埋点数据传输到后台,进而针对数据做统计分析。
1. 代码埋点
原理:事件触发时,创建一个img标签,在其src中携带想要传输的埋点数据,例如用户名、元素的唯一标识id等。 以点击事件触发埋点用以统计点击量为例,伪代码实现如下:
// 埋点核心
class Stat {
createImg () {
const IMG = document.createElement('img)
IMG.src = `image.baidu.com/test/demo.png?user_name=${obj.user_name}&ele_id=${obj.ele_id}`
docuemnt.body.appendChild(IMG)
}
log () {
}
}
// 点击时调用,触发埋点
const ST = new Stat()
document.getElementById('#id-name').addEventListener('click', () => {
...
ST.log({
user_name: userName,
ele_id: '123456'
})
})
2. 无痕埋点
原理(以点击为例):点用户点击时,获取元素到body之间整个dom的路径,作为该元素的唯一标识,称之为domPath
,并将其与埋点数据一同传输至服务端。伪代码实现如下:
(参考:https://github.com/airuikun/smart-tracker)
// body绑定事件,捕获全局点击
document.body.addEventListener('click', (event) => {
this.handleEvent(event);
}, false)
// 事件回调
handleEvent(event) {
const domPath = getDomPath(event.target);
const data = {
domPath: domPath,
trackingType: event.type
};
this.send(data);
}
// 获取元素唯一标识即dom路径domPath
getDomPath(element) {
let domPath = [];
let elem = element;
while (elem) {
let domDesc = getDomDesc(elem);
domPath.unshift(domDesc);
elem = elem.parentNode;
}
return domPath.join('>'); // eg. html>body>div[name=testwrapper]>#test>#btn
}
// 获取每一级元素的描述
getDomDesc(element, useClass = false) {
const domDesc = [];
if (!element || !element.tagName) {
return '';
}
if (element.id) {
return `#${element.id}`;
}
domDesc.push(element.tagName.toLowerCase());
if (useClass) {
const className = element.className;
if (className && typeof className === 'string') {
const classes = className.split(/\s+/);
domDesc.push(`.${classes.join('.')}`);
}
}
if (element.name) {
domDesc.push(`[name=${element.name}]`);
}
return domDesc.join('');
}
// 发送埋点信息
send(data = {}) {
let image = new Image(1, 1);
image.onload = function () {
image = null;
};
image.src = `/?${stringify(data)}`;
}