埋点,是网站分析的一种经常使用的数据采集方法。咱们主要用来采集用户行为数据(例如页面访问路径,点击了什么元素)进行数据分析,从而让运营同窗更加合理的安排运营计划。如今市面上有不少第三方埋点服务商,百度统计,友盟,growingIO 等你们应该都不太陌生,大多状况下你们都只是使用,最近我研究了下 web 埋点,你要不要了解下。javascript
用户行为分析是一个大系统,一个典型的数据平台。由用户数据采集,用户行为建模分析,可视化报表展现几个模块构成。现有的埋点采集方案能够大体被分为三种,手动埋点,可视化埋点,无埋点
咱们暂时放弃可视化埋点的实现,在 手动埋点
和 无埋点
上进行了尝试,为了便于描述,下文我会称采集脚本为 SDK。html
埋点开发须要考虑不少内容,贯穿着不轻易动手写代码的原则,咱们在开发前先思考下面这几个问题
第一期咱们先实现对 PV(即页面浏览量或点击量) 、UV(一天内同个访客屡次访问) 、点击量、用户的访问路径的基础指标的采集。精细化分析的流量转化须要和业务相关,须要和数据分析方作约定,咱们预留扩展。因此咱们的采集接口须要进行如下的约定前端
{ "header":{ // HTTP 头部 "X-Device-Id":" 550e8400-e29b-41d4-a716-446655440000", //设备ID,用来区分用户设备 "X-Source-Url":"https://www.baidu.com/", //源地址,关联用户的整个操做流程,用于用户行为路径分析,例如登陆,到首页,进入商品详情,退出这一整个完整的路径 "X-Current-Url":"", //当前地址,用户行为发生的页面 "X-User-Id":"",//用户ID,统计登陆用户行为 }, "body":[{ // HTTP Body体 "PageSessionID":"", //页面标识ID,用来区分页面事件,例如加载和离开咱们会发两个事件,这个标识可让咱们知道这个事件是发生在一个页面上 "Event":"loaded", //事件类型,区分用户行为事件 "PageTitle": "埋点测试页", //页面标题,直观看到用户访问页面 "CurrentTime": “1517798922201”, //事件发生的时间 "ExtraInfo": { } //扩展字段,对具体业务分析的传参 }] }
以上就是咱们如今约定好了的通用的事件采集的接口,所传的参数基本上会根据采集事件的不一样而发生变化。可是在用户的整一个访问行为中,用户的设备是不会变化的,若是你想采集设备信息能够从新约定一个接口,在整个采集开始以前发送设备信息,这样能够避免在事件采集接口上重复采集固定数据。java
{ "header":{ // HTTP 头部 "X-Device-Id" :"550e8400-e29b-41d4-a716-446655440000" , // 设备id }, "body":{ // HTTP Body体 "DeviceType": "web" , //设备类型 "ScreenWide" : 768 , // 屏幕宽 "ScreenHigh": 1366 , // 屏幕高 "Language": "zh-cn" //语言 } }
埋点应该让调用的业务方,尽量少有工做量,最好是什么都不用作,😁,可是实现起来有点难额。咱们采用的方案是让业务方在代码里经过 script 脚原本引用咱们的 SDK ,业务方只要配置一些须要的参数进行埋点定制(👆咱们讲到过的无埋点的流量控制),而后什么都不作就能够进行基础数据的采集。android
(function() { var collect = document.createElement('script'); collect.type = 'text/javascript'; collect.async = true; collect.src = 'http://collect.trc.com/index.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(collect, s); })(); //用户自定义要进行无埋点采集的元素,若是不进行无埋点采集,能够不配置 var _XT = []; _XT.push(['Target','div']);
若是业务方须要采集更多业务定制的数据,能够调用咱们暴露出的方法进行采集git
//自定义事件 sdk.dispatch('customEvent',{extraInfo:'自定义事件的额外信息'})
咱们使用 userId 来作用户标识,同一个设备的用户,从游客用户切换到登陆用户,若是咱们要把他们关联起来,须要有一个设备Id 作关联github
用户经过浏览器来访问 web 页面,设备Id须要存储在浏览器上,同一个用户访问不一样的业务方网站,设备Id要保持同样。web 变量存储,咱们第一时间想到的就是 cookie,sessionStorage,localStorage,可是这3种存储方式都和访问资源的域名相关。咱们总不能每次访问一个网站就新建一个设备指纹吧,因此咱们须要经过一个方法来跨域共享设备指纹web
咱们想到的方案是,经过嵌套 iframe 加载一个静态页面,在 iframe 上加载的域名上存储设备id,经过跨域共享变量获取设备id,共享变量的原理是采用了iframe 的 contentWindow通信,经过 postMessage 获取事件状态,调用封装好的回调函数进行数据处理具体的实现方式json
//web 应用,经过嵌入 iframe 进行跨域 cookie 通信,设置设备id, collect.setIframe = function () { var that = this var iframe = document.createElement('iframe') iframe.id = "frame", iframe.src = 'http://collectiframe.trc.com' // 配置域名代理,目的是让开发测试生产环境代码一致 iframe.style.display='none' //iframe 设置的目的是用来生成固定的设备id,不展现 document.body.appendChild(iframe) iframe.onload = function () { iframe.contentWindow.postMessage('loaded','*'); } //监听message事件,iframe 加载完成,获取设备id ,进行相关的数据采集 helper.on(window,"message",function(event){ that.deviceId = event.data.deviceId if(event.data && event.data.type == 'loaded'){ that.sendDevice(that.getDevice(), that.deviceUrl); setTimeout(function () { that.send(that.beforeload) that.send(that.loaded) },1000) } }) }
iframe 与 SDK 通信后端
function receiveMessageFromIndex ( event ) { getDeviceInfo() // 获取设备信息 var data = { deviceId: _deviceId, type:event.data } event.source.postMessage(data, '*'); // 将设备信息发送给 SDK } //监听message事件 if(window.addEventListener){ window.addEventListener("message", receiveMessageFromIndex, false); }else{ window.attachEvent("onmessage", receiveMessageFromIndex, false)
若是你想知道能够看个人另外一篇博客 web 浏览器指纹跨域共享
咱们知道单页面应用都是无刷新的页面加载,因此咱们在页面
跳转
的处理和咱们的普通的页面会有所不一样。单页面应用的路由插件运用了 window 自带的无刷新修改用户浏览记录的方法,pushState 和 replaceState。
window 的 history 对象 提供了两个方法,可以无刷新的修改用户的浏览记录,pushSate,和 replaceState,区别的 pushState 在用户访问页面后面添加一个访问记录, replaceState 则是直接替换了当前访问记录,因此咱们只要改写 history 的方法,在方法执行前执行咱们的采集方法就能实现对单页面应用的页面跳转事件的采集了
// 改写思路:拷贝 window 默认的 replaceState 函数,重写 history.replaceState 在方法里插入咱们的采集行为,在重写的 replaceState 方法最后调用,window 默认的 replaceState 方法 collect = {} collect.onPushStateCallback : function(){} // 自定义的采集方法 (function(history){ var replaceState = history.replaceState; // 存储原生 replaceState history.replaceState = function(state, param) { // 改写 replaceState var url = arguments[2]; if (typeof collect.onPushStateCallback == "function") { collect.onPushStateCallback({state: state, param: param, url: url}); //自定义的采集行为方法 } return replaceState.apply(history, arguments); // 调用原生的 replaceState }; })(window.history);
这块介绍起来也比较的复杂,若是你想了解更多,能够看个人另外一篇博客你须要知道的单页面路由实现原理
如今大部分的应用都不是纯原生的应用, app 与 h5 的混合的应用是如今的一种主流。
纯 web 数据采集咱们考虑到前端存储数据容易丢失,咱们在每一次事件触发的时候都用采集接口传输采集到的数据。考虑到如今不少用户的手机会有流量管家的软件监控,若是在 App 中 h5 仍是采集到数据就传输给服务端,颇有可能会让流量管家检测到,给用户报警,从而使得用户再也不信任你的 App , 因此咱们在用户操做的时候将数据传给 app 端,存储到 app。用户切换应用到后台的时候,经过 app 端的 SDK 打包传输到服务器,咱们给 app 提供的方法封装了一个适配器
// app 与 h5 混合应用,直接将数信息发给 app collect.saveEvent = function (jsonString) { collect.dcpDeviceType && setTimeout(function () { if(collect.dcpDeviceType=='android'){ android.saveEvent(jsonString) } else { window.webkit && window.webkit.messageHandlers ? window.webkit.messageHandlers.nativeBridge.postMessage(jsonString) : window.postBridgeMessage(jsonString) } },1000) }
经过上面几个问题的思考,咱们对埋点的实现大体已经有了一些想法,咱们使用思惟导图来还原下咱们即将要作的事情,图片记得放大看哦,过小了可能看不清。
咱们须要暴露给业务方调用的方法
咱们须要处理的事件类型
SDK 的基本实现思路
咱们定义了几个工具方法,提升开发的幸福指数 😝
var helper = {}; // 生成一个惟一的标识,pageSessionId (用这个变量来关联开始加载、加载完成、离开页面的事件,计算出页面加菜时间,停留时间) helper.uuid = function(){} // 元素绑定事件监听,兼容浏览器到IE8 helper.on = function(){} //元素移除事件监听的适配器函数,兼容浏览器到IE8 helper.remove = function(){} //将json转为字符串,事件传输的参数类型转化 helper.changeJSON2Query = function(){} //将相对路径解析成文档全路径 helper.normalize = function(){}
var collect = { deviceUrl:'http://collect.trc.com/rest/collect/device/h5/v1', eventUrl:'http://collect.trc.com/rest/collect/event/h5/v1', isuploadUrl:'http://collect.trc.com/rest/collect/isupload/app/v1', parmas:{ ExtraInfo:{} }, device:{} }; //获取埋点配置 collect.setParames = function(){} //更新访问路径及页面信息 collect.updatePageInfo = function(){} //获取事件参数 collect.getParames = function(){} //获取设备信息 collect.getDevice = function(){} //事件采集 collect.send = function(){} //设备采集 collect.sendDevice = function(){} //判断才否采集,埋点采集的开关 collect.isupload = function(){ 1. 判断是否采集,不采集就注销事件监听(项目中区分游客身份和用户身份的采集状况,这个方法会被判断两次) 2. 采集则判断是否已经采集过 a.已经采集过不作任何操做 b.没有采集过添加事件监听 3. 判断是 混合应用仍是纯 web 应用 a.若是是web 应用,调用 collect.setIframe 设置 iframe b.若是是混合应用 将开始加载和加载完成事件传输给 app } //点击事件处理函数 collect.clickHandler = function(){} //离开页面的事件处理函数 collect.beforeUnloadHandler = function(){} //页面回退事件处理函数 collect.onPopStateHandler = function(){} //系统事件初始化,注册离开事件,浏览器后退事件 collect.event = function(){} //获取记录开始加载数据信息 collect.getBeforeload = function(){} //存储加载完成,获取设备类型,记录加载完成信息 collect.onload = function(){ 1. 判断cookie是否有存设备类型信息,有表示混合应用 2. 采集加载完成时间等信息 3. 调用 collect.isupload 判断是否进行采集 } //web 应用,经过嵌入 iframe 进行跨域 cookie 通信,设置设备id collect.setIframe = function(){} //app 与 h5 混合应用,直接将数信息发给 app,判断设备类型作原生方法适配器 collect.saveEvent = function(){} //采集自定义事件类型 collect.dispatch = function(){} //将参数 userId 存入sessionStorage collect.storeUserId = function(){} //采集H5信息,若是是混合应用,将采集到的信息发送给 app 端 collect.saveEventInfo = function(){} //页面初始化调用方法 collect.init = function(){ 1. 获取开始加载的采集信息 2. 获取 SDK 配置信息,设备信息 3. 改写 history 两个方法,单页面应用页面跳转前调用咱们本身的方法 4. 页面加载完成,调用 collect.onload 方法 } collect.init(); // 初始化 //暴露给业务方调用的方法 return { dispatch:collect.dispatch, storeUserId:collect.storeUserId, }
👆就是我这段时间研究的成果了,代码的篇幅比较长,就不放在博客里了,感兴趣的同窗能够加我微信进行交流,或则在文章下面留言,也欢迎你们给我提意见,帮忙优化 😝。