前端无痕埋点技术架构解析

简单介绍

不再需要开发人员在业务代码中手动植入埋点上报请求,而是通过 sdk 自动分析上报所有的有效用户行为信息数据,更方便,更准确。

技术方案

规范定义

1.需要统计哪些数据。

页面的 pv、uv、设备型号、时间段、特别元素的点击次数

2.需要怎么定义这些数据格式。

首次进入(pipe.visitor)、进入(pipe.enter)、离开(pipe.leave)、点击(event.click)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
utmId: "渠道号.站点号.频道号.页面号",
vid: "用户唯一的唯一编号"
logs:[ // 埋点上报的数据
{
key: "event.click", // 数据类型
time: 121212121, // 客户端时间戳
tags: ["ab","c"], // 数据标签
extra: {
path: "BODY[0]/.banner/IMG[0]", // 元素被点击的 xpath 路径
content: "元素所包含的内容",
} // 自定义数据扩展
}
]
}

3.如何来实现。

1.解析 url 来获取页面的 utmId
2.sha1 生成随机 id 存 localStorage 用作唯一编号 3.事件监听获取点击元素,通过 xpath 规则获取元素路径

代码实现

封装 SDK 对外只暴露 UTM 对象,防止变量污染。

1
var UTM = UTM || {};

1.utmId 的获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 获取或设置 ID
* ID 表示`渠道号.站点号.频道号.页面号`
*/
UTM.utmID = function (id) {
if (!id) {
if (UTM._utmID) {
return UTM._utmID;
}
var utmParts = UTM.getQuery("utm").split(".", 4); // 优先获取utm参数值
utmParts.length = 4;
utmParts[1] = utmParts[1] || location.hostname.replace(/\./g, "-");
var path = location.href
.replace(/^.*?:\/\/[^/]*/, "")
.replace(/\?.*#/i, "#")
.replace(/\?.*$/i, "")
.replace(/\./g, "-")
.replace(/^\//, "");
var slash = path.indexOf("/");
utmParts[2] = utmParts[2] || path.slice(0, slash);
utmParts[3] = utmParts[3] || path.slice(slash + 1);
return (UTM._utmID = utmParts.join("."));
}
UTM._utmID = id;
};

2.获取元素唯一路径

1
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
/**
* 获取元素路径
* 定位唯一路径,通过id,class等选择器优先级,优化 xpath 路径减少数据量
*/
UTM.utmPath = function (node) {
if (node.id && document.getElementById(node.id) === node) {
return "#" + node.id;
}
if (node.className && document.getElementsByClassName) {
var classNames = node.className.split(/\s+/, 1);
for (var i = 0; i < classNames.length; i++) {
if (document.getElementsByClassName(classNames[i]).length === 1) {
return "." + classNames[i];
}
}
}
if (document.getElementsByTagName(node.tagName).length === 1) {
return node.tagName;
}
var path = node.id
? "#" + node.id
: node.className
? "." + node.className.replace(/\s.*$/, "")
: node.tagName;
if (node.parentNode) {
var index = 0;
for (var n = node.parentNode.firstChild; n != node; n = n.nextSibling) {
if (n.tagName == node.tagName) {
index++;
}
}
return UTM.utmPath(node.parentNode) + "/" + path + "[" + index + "]";
}
return path;
};

3.埋点数据上报

1
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
/**
* 立即上报所有日志
*/
UTM.utmFlush = function (useBeacon) {
var utm = UTM.utmID(); // 获取utmId
var vid = UTM.utmVID(); // 获取vid
var logs = UTM._utmLogs; // 获取存档的埋点数据内容

while (logs.length) {
var url = "http://xxx.com?utm=" + encodeURIComponent(utm);
if (vid) url += "&vid=" + encodeURIComponent(vid);
url += "&logs=[" + logs.shift();
while (logs.length && url.length + logs[0].length < 7206) {
url += "," + logs.shift();
}
url += "]";
url = url.replace(/\n|\r|\t/g, ""); // 暴力优化特殊字符
if (useBeacon === true) {
if (navigator.sendBeacon) {
navigator.sendBeacon(url);
} else {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.send(null);
}
} else {
var script = document.createElement("script");
script.async = true;
script.setAttribute("crossorigin", "anonymous");
script.onload = script.onreadystatechange = function () {
if (
!script.readyState ||
script.readyState === "loaded" ||
script.readyState === "complete"
) {
script.onload = script.onreadystatechange = null;
script.parentNode.removeChild(script);
}
};
script.src = url;
var firstScript = document.getElementsByTagName("script")[0];
firstScript.parentNode.insertBefore(script, firstScript);
}
}
};

4.SDK 初始化

1
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
/**
* SDK 初始化
*/
UTM.utmInit = function () {
UTM.utm("pipe.enter", {}); // 上报进入页面事件

document.addEventListener(
"click",
function (e) {
if (e.isTrusted !== false) {
UTM.utm("event.click", {
path: UTM.utmPath(e.target), // 获取元素的 xpath 路径
content: UTM.utmContent(e.target), // 获取元素内容
});
}
},
true
);

window.addEventListener(
"unload",
function (e) {
UTM.utm("pipe.leave");
UTM.utmFlush(true); // 监听页面退出后,立即上报埋点数据
},
false
);

UTM.initSPA(); // 启动单页应用数据上报规则
};

最后

以上所附代码片段仅供参考,所提供的是一种设计思路。很多工具类方法,以及一些细节处理都没有展开。比如:需要获取多少设备型号信息、单页应用的数据上报规则、以及一些业务适配、一些高级功能用法拓展等等。

在企业中,可能光开发一个无痕埋点 SDK 还远远不够,需要搭建一个数据分析系统,能够处理巨大的数据量,以及搭建一个数据可视化系统,将基础数据展示,方便业务开发直接使用。同样的可能还需要做一个圈选功能,类似于 gio 的圈选功能,能够可视化的定位需要统计的元素。

目前很多社区方案都十分成熟可以借鉴参考,同时还是要考虑应用的业务场景,考虑当前的流量是否可以做全量上报数据方案或者是做后置数据上报方案,再或者是结合上报。感谢大佬们阅读到此,不足之处请多多包涵。

文章作者: 方长_beezen
文章链接: https://dongbizhen.com/posts/53777/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 BEEZEN