概念
所谓埋点其实就是对网站用户行为和设备状态的数据采集方便后期统计分析,进一步对产品和战略做出调整。数据采集是否丰富,采集的数据是否准确,采集是否及时,都直接影响整个数据平台的应用的效果。目前市场上的埋点方式大致分为三大类型。
代码埋点
代码埋点是最早也是最经典的埋点方式,也是最可控的埋点方式。开发者需要人工将埋点结合到代码逻辑中,理论上只要是客户端种的操作,再复杂也能采集到。尤其是一些非点击的、不可视的行为,是非要代码埋点来实现不可了。所以如果我们需要对埋点有更加精准的控制力,那么代码埋点是最好的选择。
全埋点(无埋点)
全埋点顾名思义就是在网站任何位置,任何交互的地方都会自动采集。所以对开发者而已只是在网站中引入埋点的sdk,就可以自动收集数据,所以也被称为“无埋点”。但是因为没有针对性的采集数据,导致采集的内容数据庞大,对于采集和分析的服务器要求也相应提高,而且针对性分析不明显。
可视化埋点
代码埋点和全埋点并没有在易用性和准确性方面达到平衡。可视化埋点可以直接在网站真实界面上操作埋点,而且埋点之后立即可以验证埋点是否正确,这还不算完,将埋点部署到所有客户端也是几乎实时生效的。因为可视化埋点的这些好处,分析的需求方,业务人员,没有权限触碰代码或者不懂得编程的人都可以非常低的门槛获取到用于分析的数据。可视化埋点,很多时候也被称为“无码埋点”,注意和“无埋点”区分。
分析
优缺点
结论
根据埋点需求,需要我们跟踪特别精准的埋点场景,所以代码埋点是最适合的方式。同时为了满足后期落地页的推广和简化开发工作量,会在第二阶段做可视化埋点方案。
原理分析
概念
PV(访问量):Page View, 即页面浏览量或点击量,用户每次刷新即被计算一次。PV反映的是浏览某网站的页面数,所以每刷新一次也算一次。就是说PV与来访者的数量成正比,但PV并不是页面的来访者数量,而是网站被访问的页面数量。
UV(独立访客):Unique Visitor,一般使用cookie标记,访问您网站的一台电脑客户端(比如一台电脑开多个浏览器访问则为多个UV)为一个访客,00:00-24:00内相同的客户端只会被计算一次。可以理解成访问某网站的电脑的数量。网站判断来访电脑的身份是通过来访电脑的cookies实现的。如果更换了IP后但不清除cookies,再访问相同网站,该网站的统计中UV数是不变的。
IP(独立IP):指独立IP数。00:00-24:00内相同IP地址之被计算一次(多台电脑可能共用一个ip)。某IP地址的计算机访问网站的次数。这种统计方式很容易实现,具有真实性。所以是衡量网站流量的重要指标。
误差
PV、UV和IP的统计方式都是会有误差的。
例如统计PV的时候,来源不明的水军可能会利用脚本不断的重复访问某个网页,使得PV数不正常激增,此时PV > 真实数据。
统计UV的时候,如果有个用户不断清除cookie或者换了很多台浏览器来访问的话,那么这个用户会被统计多次,此时 UV > 真实数据。 如果多个使用者共用一个账号和同一个设备的时候,此时UV < 真实数据(这个可以避免)。
统计IP的时候,如果用户切换了手机的网络模式(4g -> wifi)此时IP > 真实数据,多个使用者共用出口IP,此时IP < 真实数据。
在数据量较多,统计周期较长并明显存在某些规律的情况下,其实这些误差也是可以忽略的。
思考点
推送给后端的时机?PV,UV,IP的统计都可以交给后端,后端存库。现在前后端项目偏多,前端路由更改时就是推给后端的最佳时机。
如何推送给后端?如果采用ajax或者fetch来发送GET/POST消耗特别大,而且会涉及到严重的跨域问题。所以应该采用new Image用get请求1*1像素的gif(gif最小)的图片,请求地址和头部带上相关参数信息。好处:1、统计的接口前端无须关心返回
2、跨域友好,执行过程无阻塞
3、相比 XMLHttpRequest 对象发送 GET 请求,性能上更好
4、GIF的最低合法体积最小,而且图片加载无需更改DOM树影响渲染性能
接口约定。因为数据采集主要由前端提供,后端负责搜集前端采集的数据然后存库。所以要约定好接口参数。因为用get请求gif图片的形式,所以要保证url长度不能过长,接口参数最好越简单越好。哪些放在参数里,哪些放到header消息头里
日期的过期时间。所有的数据采集都记录当前行为时间。记录设备唯一标识也就是UV时,有效期时长为永久,这样可以跟踪同一个设备每天上线时间状态和访问了哪些网站。如果要保证不同域名下的cookies一致,必须从iframe入手
灵活查询。统计后台会有不同职业的人对照查看,定死的表格,柱形图不能满足不同的场景需求,应该弄成可定制,可以自由拖拽想要的对比系数查看结果。
SDK的应用场景。从网站类型分大致分为mvc,mvp,mvvm。从设备上分又有pcweb,移动web,app,混合APP,小程序,桌面应用等。但从目前公司的项目来看,我们只要满足web端的mvc模式和单页面模式即可。从前端规范来讲需要支撑CMD,AMD,CommonJs模式和ES模式。
设计
-
保证设备的唯一性
设备的唯一标识,web端已经不能提供。所以我们只有自定义唯一标识,让设备保存。同时要让所有接入的网站设备标识一致,这样就可以跟踪同一个设备每天上线时间状态和访问了哪些网站。因为域名是不固定的,存放在localStrorage和cookies都不靠谱。所以我们可以自定义一个固定页面,用于存储设备标识。
-
思路:
接入SDK后,新加载一个ifrme,ifrme加载一个oss上存储的一个html.
/* ========== 加载iframe资源设置唯一标识 ========== */
const loadFrame = () => {
const temporary_params = getTemporaryParams();
const appId = temporary_params ? temporary_params.appId : "";
const iframe = document.createElement("iframe");
iframe.setAttribute("frameborder", 0);
iframe.setAttribute("height", 0);
iframe.setAttribute("width", 0);
iframe.setAttribute("id", `Track253SdkIframe_${appId}`);
iframe.src = config.deviceHtml;
iframe.style.display = "none";
document.body.appendChild(iframe);
};
<body>
<script>
(function (w) {
/* ========== 生成设备唯一标识 ========== */
function guid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
/[xy]/g,
function (c) {
var r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
}
);
}
const deviceID = localStorage.getItem('deviceID') || guid();
parent.postMessage({ type: 'setSomeDeviceID', data: deviceID }, '*');
localStorage.setItem('deviceID', deviceID);
window.addEventListener(
'message',
(event) => {
if (event.data.type === 'setSomeDeviceID') {
localStorage.setItem('deviceID', event.data.data);
}
},
false
);
})(window);
</script>
</body>
-
用gif图片传输参数
采用new Image用get请求1*1像素的gif(gif最小)的图片,请求地址和头部带上相关参数信息。
export default (params) => {
const _params = typeof params === "string" ? params : stringify(params);
const img = new Image(1, 1);
const src = `${config.imgBecacon}?${_params}`;
img.src = src;
/** 利用load和error事件来监听动作的完成,返回Promise便于操作 */
return new Promise((resolve, reject) => {
img.onload = function () {
resolve({ code: 200, data: "success!" });
};
img.onerror = function (e) {
reject(new Error(e.error));
};
});
};
-
支持单页面路由监听
单页面路由是前端控制的,这边需要重写history监听方法
const addHistoryMethod = (function () {
const historyDep = new Dep();
return function (name) {
if (name === "historychange") {
return function (_name, fn) {
const event = new Watch(_name, fn);
Dep.watch = event;
historyDep.defined();
Dep.watch = null; // 置空供下一个订阅者使用
};
} else if (name === "pushState" || name === "replaceState") {
const method = history[name];
return function () {
// eslint-disable-next-line prefer-rest-params
method.apply(history, arguments);
historyDep.notify();
};
}
};
}());
window.addHistoryListener = addHistoryMethod("historychange");
history.pushState = addHistoryMethod("pushState");
代码嵌入
(function (w, d, t, s) {
w[t] = w[t] || {
init: function (_a) {
(w[t].q = []).push(_a);
},
};
const e = d.createElement(s);
e.type = 'text/javascript';
e.async = true;
e.src = 'https://static.253.com/js/track/track253_sdk.min.js?t=' + new Date().getTime();
const x = d.getElementsByTagName(s)[0];
x.parentNode.insertBefore(e, x);
})(window, document, 'Track253', 'script');