1.系统概览
数据埋点分析系统都做了些什么?采集了哪些数据?这些数据我们将如何运用和分析?最终又将如何展示呢?
首先我们看下系统结构。整个系统由以下 4 个部分组成,期望能提供一套完整的用户行为分析的解决方案:
- 埋点采集 JSSDK:收集用户行为数据,并进行上报;
- 数据处理服务:接收上报数据并存储;筛取所需数据,进行数据处理并透出;
- 数据可视化平台:汇总展示详细数据,支持自定义,打通业务;
- Chrome插件工具:在页面上直观展示坑位数据,提供场景更友好的数据可视化服务;
其基本协作流程是,用户进入平台任意一个已埋点的 Web 页面,进行的一系列(进入、点击、滚屏等)操作,都会由 JSSDK 进行分类并将数据上报至服务端进行存储,再由站点 / 插件发起查询,服务端将处理后的数据返回,再通过数据可视化平台进行透出展示。
2.数据采集
数据采集一般分为以下三种:
- 无埋点(全埋点):零埋点成本,抓取用户行为全量数据,任何操作行为都会被上传。数据量大,“噪音”多;
- 可视化埋点:在页面中操作,选择埋点位置/模块,非开发人员也可以进行埋点;
- 侵入式埋点:埋点时需要将数据采集代码写入业务代码中,埋点成本较高,但准确度也更高;
由于对数据的准确度要求较高,同时希望前期只投入较少的开发资源就可以进行快速试错,并为了满足重点的用户行为数据的采集需求,因此,我们优先采用代码侵入式埋点方案**。**
同时针对其接入成本较高的劣势,我们也将埋点在搭建系统及组件中采用了自动化的植入方式,这点将不再此详述。为降低接入成本并且更加灵活的捕获数据,我们采用了 DOM 节点挂载特殊属性后自动发送和手动自定义发送两种方式结合,以满足不同场景的需要。
//自动发送埋点方式,举例:
<button data-utm-click="${did}" data-utm-data="${业务数据}">
//手动发送埋点方式,举例:
const utmCnt = g_UTM.batchSend('触发类型(click/browse)等',[{utmCD:['区块信息','位置信息'],bdata:{key:'其他业务数据'}},{utmCD:['001','008'],bdata:{key:'value'}}
]);
基于用户行为分析这个大目标,我们所采集数据紧紧围绕着两个主题,即:Event(事件/行为)和 User(用户)。
围绕“事件“我们采集了:事件的类型、发生时间、页面位置等信息,组成事件唯一标识。
围绕”用户“我们采集了:用户 IP、操作系统、浏览器信息、屏幕分辨率等,并生成用户唯一标识植入 Cookie 中。
{bdata: {}, //业务数据createTime: "1571038815128", // 创建时间evt: "browse", // 事件类型ipAddr: 122.226.174.195, //ip地址logType: 2, // 触发类型lver: 1.1.0, //版本mx: 0, // 页面位置坐标xmy: 0, // 页面位置坐标yos: "Windows/7", // 操作系统pre: "https://www.zcygov.cn/", // 来源地址scr: "1920x1360", // 屏幕分辨率url: "https://www.zcygov.cn/", // 页面地址userId: "001", // 用户标识utmCnt: "a0004.2ef5001f.0001.0001.d814bf60ee5511e99397b37fe9083257", // 触发位置utmUrl: "a0004.2ef5001f.0001.0001", // 来源位置uuid: "d7fd8de0-ee55-11e9-9397-b37fe9083257", // 浏览器唯一标识
}
上述一些收集的字段,会在下面案例中使用到。

3.数据展示
目前前台站点已经提供了比较丰富的数据展示功能,比如:PV(浏览次数)/UV(浏览人数) 排序或趋势、漏斗分析、路径分析、热图分析、用户画像、自定义看板等等,还有各种业务相关的数据统计及报表导出功能。总体菜单如下:

- PV/UV 排序或趋势(PV:PageView,页面浏览次数,用户每打开一次记录一次,多次打开同一页面将累计多次;UV:UserView,浏览页面人数;下文中将直接用PV/UV;)
- 全站的PV/UV单日趋势图:分时段查看访问量的高峰和低谷;
- PV/UV排序:查看Top页面的PV/UV
- 按页面、时间区间查询PV/UV
- 漏斗分析:按流程排序每个阶段的人数,计算出转化率;
- 路径分析:查询各个页面的来源和去向;
- 热图分析
- 点击热图:按钮及链接点击的热图;
- 滚屏热图(即将上线):用户页面滚屏触达率;
- 用户画像(即将上线):针对重点用户的回访次数、浏览路径、用户身份、所在地、操作系统、浏览器等详细信息查询;
- 自定义看板:可选择首页看板的展示项;

4.赋能业务
采集和分析哪些数据才是对业务有价值的,我们参考了许多业界成熟的用户行为分析解决方案,包括:
- GrowingIO
- 神策数据
- 数极客
这些产品在用户行为分析侧的功能可以说是做到了大而全。根据我们的实际需求可以筛选出以下一些重点功能模块:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N3xYZ3mp-1609921993398)(C:\Users\lekaiyu\AppData\Roaming\Typora\typora-user-images\1609913502139.png)]
上面大部分一支持的功能都可以在【数据展示】模块中查看,表单分析及用户行为序列目前已在我们版本的规划中。表单分析是分析一个用户从进入一个表单填写页面到表单提交的过程中各个行为的分析,每个表单项的填写率、重填率、填写时长、放弃率等都是重要的分析指标,会直接影响到整体的转化率。也能帮助开发人员及时发现和定位表单页面中的交互问题,从而提升用户体验,和重要链路中的转化率。
例如:常见的注册表单的转化,即 10 个用户进入注册页面但最后只有 7 个用户成功注册,这个功能可以有效发掘剩余 3 个注册失败的用户流失的点,找到他们是在填写哪个表单项前离开页面或是找到重填率最高的表单项进行优化。用户行为序列是从单一用户的角度去查看在我们站点上的行为轨迹,从而去分析重点用户的行为喜好。
基于公司当前的业务发展,除了上述基础功能模块外,系统中还会对应考虑一些定制化的业务能力模块。依据目前系统的能力类型,可分为用户行为分析、链路转化分析、用户体验分析等:
5.搜索流程埋点案例
那么在一个页面或者一个流程中我们可以采集到哪些有价值的数据呢?下面我们就以一个简单的流程为例来说明。
这里流程可以分为三步,首先,用户进入政采云电子卖场首页,并在搜索框中输入想要搜索的关键词,其次,点击搜索按钮后进入搜索结果页,最后,在结果页中找到了目标商品并点击进去了商品详情页查看。这是在一个电商平台中用户操作行为中较为常见的一种流程,也是一个关键流程。
我们在上述的三个页面中会采集的数据有以下三种:
- 页面进入/离开自动埋点
- 按钮点击埋点
- 链接点击埋点

如上图所示,通过 Chrome 插件工具,可以在页面上直观的展示链接和按钮的点击次数(数据已脱敏)。
利用上面说到的三项埋点,我们在单个页面中可以得到用户行为相关的四种数据。
- PV:通过计算日志中所有进入页面日志条数的总和我们可以得到 pv
- UV:以唯一 uuid 将 pv 进行过滤后可以得到 uv ;
- 按钮点击数:直接通过统计按钮点击事件上报的日志条数可以得到按钮的点击量;
- 链接点击数:与按钮点击有所不同,按钮点击是通过单独发送的点击事件上报来进行统计,而链接点击往往导致的是一次页面跳转,此处即为从电子卖场首页离开进入了搜索结果页,此时我们所统计的就是搜索结果页的页面进入事件中的 utmUrl(即来源按钮的唯一识别码)值,判断出该次搜索结果页的进入是来源于首页常见搜索关键词的点击,从而统计出该位置的的链接点击量。

再对这些数据进行加工,我们进一步可以得到:停留时长、转化率、热力图;
- 热力图:用于反映图中点的密集程度,在此处我们利用点击的坐标(点击的 x,y 坐标位置,再根据屏幕分辨率做一致性的换算)组合成点击热图,如下图所示(数据已脱敏)。

-
漏斗分析:由一个元事件/虚拟事件加一个或者多个筛选条件组成,表示一个转化流程中的一个关键性的步骤;在我们平台上创建一个漏斗主要由以下几步:

如下图所示,在这个漏斗中定义了三个事件。

将这三个事件串联起来,可以得到完整的漏斗分析图(数据已脱敏)。

6.前端埋点方案选型和前端上报方案设计
1)监控数据
首先我们需要明确一个产品或者网页,普遍需要监控和上报数据,监控分为三个阶段:用户进入网页首页 用户在网页内部交互和交互中报错

2)埋点方案
在实际项目中考虑到上报数据灵活制定,以及减少数据传输和服务器压力,在所需埋点处不多的情况下,常用的方式是代码埋点
以用户进入首页为例,我们在首页渲染完成后会发送事件类型和类型相关的数据给server端 告知首页的监控信息。

3)上报周期和上报数据类型
如果埋点的事件不是很多,上报可以时时进行,比如监控用户的交互事件,可以在用户触发事件后,立刻上报用户所触发的事件类型。如果埋点的事件较多,或者说网页内部交互频繁,可以通过本地存储的方式先缓存上报信息,然后定期上报。
接着来确定需要埋点上报的数据,上报的信息包括用户个人信息以及用户行为,主要数据可以分为:
- who: appid(系统或者应用的id),userAgent(用户的系统、网络等信息)
- when: timestamp(上报的时间戳)
- from where: currentUrl(用户当前url),fromUrl(从哪一个页面跳转到当前页面),type(上报的事件类型),element(触发上报事件的元素)
- what: 上报的自定义扩展数据data:{},扩展数据中可以按需求定制,比如包含uid等信息
上报数据的对象为:
{ ----------------上报接口本身提供--------------------currentUrl, fromUrl,timestamp,userAgent:{os,netWord,}----------------业务代码配置和自定义上报数据------------type,appid,element,data:{uid,uname}
}
4)埋点和上报举例
我们以上报首屏加载事件为例,DOM提供了document的DOMContentLoaded事件来监听dom挂载,提供了window的load事件来监听页面所有资源加载渲染完毕。
<script type="text/javascript">var start=Date.now();document.addEventListener('DOMContentLoaded', function() {fetch('some api',{type:'dom complete',data:{domCompletedTime:Date.now()-start}})});window.addEventListener('load', function() {fetch('some api',{type:'load complete',data:{LoadCompletedTime:Date.now()-start}})});
</script>
5)前端埋点系统的前后端通信加密
在上报数据的前后端通信中,需要和server端协商加密机制,利用 OpenSSL库来实现的加密,OpenSSL已经是一个广泛被采用的加密算法。前端可以采用node的crypto模块。
首先来看hash算法,crypto.createHash() 来创建一个Hash实例,可利用的hash算法如下:
- md5
- sha1
- sha256
- sha512
- ripemd160
以sha256算法加密为例:
const str="123445";//需要加密的字段
const hash=crypto.createHash('sha256');//指定加密算法
hash.update(str); //通过算法加密相应的字段
const result=hash.digest('hex');//转化成十六进制
7.前端如何实现全局 PV 统计,以 Vue 应用为例
方案一
通过在入口文件 index.js 全局定义 Router.beforeEach:
import App from './app'
import Router from './router'Router.beforeEach((to, from, next) => {App.logEvent({type: 'visit',name: to.name,time: new Date().valueOf(),params: {from: {name: from.name,path: from.path,query: from.query},to: {name: to.name,path: to.path,query: to.query}}})next()
})
停留时长可通过 (跳转页 time - 当前页 time) 获知,但关闭应用时如何统计?监听应用关闭 onbeforeunload + onunload?
其中 App.logEvent 为自定义 Vue 插件 App 中的 method,用于向服务器发起 埋点上报请求
import Request from './utils/request'const App = {// ...logEvent (opts) {Request({url: '/log/event',method: 'POST',data: {type: opts.type,name: opts.name,time: opts.time,params: opts.params || {}}})}
}
App.install = (Vue, options) => {Vue.prototype.$app = {// ...logEvent: App.logEvent}
}export default App
方案二
通过在入口文件 index.js 全局注册混入 beforeRouteEnter、beforeRouteLeave 对象:
import Vue from 'vue'Vue.mixin({beforeRouteEnter (to, from, next) {next(vm => {vm.$app.logEvent({type: 'visit',name: to.name,time: new Date().valueOf(),params: {from: {name: from.name,path: from.path,query: from.query},to: {name: to.name,path: to.path,query: to.query}}})})},beforeRouteLeave (to, from, next) {this.$app.logEvent({type: 'visit',name: to.name,time: new Date().valueOf(),params: {from: {name: from.name,path: from.path,query: from.query},to: {name: to.name,path: to.path,query: to.query}}})next()}
})
关闭应用时 beforeRouteLeave 是否触发?
上述方案存在明显缺陷:
- 官方曰慎用全局混入对象!!!
- 对于页面同名钩子函数 beforeRouteEnter、beforeRouteLeave,如何 merge?如何 next?
- 含子路由的页面将调用 2 次 beforeRouteEnter、beforeRouteLeave,PV 无形翻倍…
我猜此刻有打全局混入 created、destroyed 并通过 this.$route 获知访问对象主意的人了,试试看?
8.前端如何实现功能点击量统计?
方案一
将埋点上报混入业务接口请求,无接口请求的点击采用自定义上报:

其中 param keys 指代需上报的业务请求参数 key list(并非全部参数均需随埋点上报)。
上述方案大大节约请求数,但存在明显缺陷:
- 将埋点上报混入业务接口,上报 crash 不仅丢失统计数据,还将影响主功能。
- 统计与业务 高耦合,两者尽量不混于同一服务。
方案二
将所有点击事件视为同一类,走统一上报接口:
logEvent (opts) {Request({url: '/log/event',method: 'POST',data: {type: opts.type,name: opts.name,time: opts.time,params: opts.params || {}}})
}
上述方案也存在明显缺陷:
- 请求量翻倍:但统计与业务服务拆分后,请求并非同一组服务器承担。
- 待上报的点击事件函数均需调用 logEvent:封装一枚附带埋点上报的 组件,以 Vue 为例。
<template><div class="vc-trace" @click="triggerClick"><slot></slot></div>
</template><script>
import Request from './utils/request'export default {name: 'Trace',props: {type: {type: String,default: ''},name: {type: String,default: ''},from: {type: String,default: ''},params: {type: Object,default: () => ({})}},methods: {triggerClick () {Request({url: 'XXX/log/event',method: 'POST',data: {type: this.type,name: this.name,from: this.from,time: new Date().valueOf(),params: this.params}})}}
}
</script>
方案本无优劣,适合才更重要,需综合考虑 产品设计、产品使用度、服务利用率 等等。例使用度较低应用可将统计与业务混于同一服务以节约成本,使用度较高应用可采取 本地缓存、批量上报 以降低服务压力,但批量上报是否加大统计 误差?
9.实现整体流程

网站数据统计分析工具是网站站长和运营人员经常使用的一种工具,比较常用的有谷歌分析、百度统计 和 腾讯分析等等。所有这些统计分析工具的第一步都是网站访问数据的收集。目前主流的数据收集方式基本都是基于javascript的。本文将简要分析这种数据收集的原理,并一步一步实际搭建一个实际的数据收集系统。
注:从上图中可以看出,一个统计分析平台架构的挑战来自以下 5 个:
(1)日志采集、(2)元数据管理、(3)业务数据建模、(4)任务调度、(5)OLAP引擎
数据收集原理分析
简单来说,网站统计分析工具需要收集到用户浏览目标网站的行为(如打开某网页、点击某按钮、将商品加入购物车等)及行为附加数据(如某下单行为产生的订单金额等)。早期的网站统计往往只收集一种用户行为:页面的打开。而后用户在页面中的行为均无法收集。这种收集策略能满足基本的流量分析、来源分析、内容分析及访客属性等常用分析视角,但是,随着ajax技术的广泛使用及电子商务网站对于电子商务目标的统计分析的需求越来越强烈,这种传统的收集策略已经显得力不能及。
后来,Google在其产品谷歌分析中创新性的引入了可定制的数据收集脚本,用户通过谷歌分析定义好的可扩展接口,只需编写少量的javascript代码就可以实现自定义事件和自定义指标的跟踪和分析。目前百度统计、搜狗分析等产品均照搬了谷歌分析的模式。
其实说起来两种数据收集模式的基本原理和流程是一致的,只是后一种通过javascript收集到了更多的信息。下面看一下现在各种网站统计工具的数据收集基本原理。
1.1 流程概览
首先通过一幅图总体看一下数据收集的基本流程。
图1. 网站统计数据收集基本流程
首先,用户的行为会触发浏览器对被统计页面的一个http请求,这里姑且先认为行为就是打开网页。当网页被打开,页面中的埋点javascript片段会被执行,用过相关工具的朋友应该知道,一般网站统计工具都会要求用户在网页中加入一小段javascript代码,这个代码片段一般会动态创建一个script标签,并将src指向一个单独的js文件,此时这个单独的js文件(图1中绿色节点)会被浏览器请求到并执行,这个js往往就是真正的数据收集脚本。数据收集完成后,js会请求一个后端的数据收集脚本(图1中的backend),这个脚本一般是一个伪装成图片的动态脚本程序,可能由php、python或其它服务端语言编写,js会将收集到的数据通过http参数的方式传递给后端脚本,后端脚本解析参数并按固定格式记录到访问日志,同时可能会在http响应中给客户端种植一些用于追踪的cookie。
上面是一个数据收集的大概流程,下面以谷歌分析为例,对每一个阶段进行一个相对详细的分析。
1.2埋点脚本执行阶段
若要使用谷歌分析(以下简称GA),需要在页面中插入一段它提供的javascript片段,这个片段往往被称为埋点代码。下面是我的博客中所放置的谷歌分析埋点代码截图:
图2. 谷歌分析埋点代码
其中_gaq是GA的的全局数组,用于放置各种配置,其中每一条配置的格式为:
_gaq.push(['Action', 'param1', 'param2', ...]);
Action指定配置动作,后面是相关的参数列表。GA给的默认埋点代码会给出两条预置配置,_setAccount用于设置网站标识ID,这个标识ID是在注册GA时分配的。_trackPageview告诉GA跟踪一次页面访问。更多配置请参考:https://developers.google.com/analytics/devguides/collection/gajs/ 。实际上,这个_gaq是被当做一个FIFO队列来用的,配置代码不必出现在埋点代码之前,具体请参考上述链接的说明。
就本文来说,_gaq的机制不是重点,重点是后面匿名函数的代码,这才是埋点代码真正要做的。这段代码的主要目的就是引入一个外部的js文件(ga.js),方式是通过document.createElement方法创建一个script并根据协议(http或https)将src指向对应的ga.js,最后将这个element插入页面的dom树上。
注意ga.async = true的意思是异步调用外部js文件,即不阻塞浏览器的解析,待外部js下载完成后异步执行。这个属性是HTML5新引入的。
1.3 数据收集脚本执行阶段
数据收集脚本(ga.js)被请求后会被执行,这个脚本一般要做如下几件事:
(1)通过浏览器内置javascript对象收集信息,如页面title(通过document.title)、referrer(上一跳url,通过document.referrer)、用户显示器分辨率(通过windows.screen)、cookie信息(通过document.cookie)等等一些信息。
(2)解析_gaq收集配置信息。这里面可能会包括用户自定义的事件跟踪、业务数据(如电子商务网站的商品编号等)等。
(3)将上面两步收集的数据按预定义格式解析并拼接。
(4)请求一个后端脚本,将信息放在http request参数中携带给后端脚本。
这里唯一的问题是步骤4,javascript请求后端脚本常用的方法是ajax,但是ajax是不能跨域请求的。这里ga.js在被统计网站的域内执行,而后端脚本在另外的域(GA的后端统计脚本是http://www.google-analytics.com/__utm.gif),ajax行不通。一种通用的方法是js脚本创建一个Image对象,将Image对象的src属性指向后端脚本并携带参数,此时即实现了跨域请求后端。这也是后端脚本为什么通常伪装成gif文件的原因。通过http抓包可以看到ga.js对__utm.gif的请求:

图3. 后端脚本请求的http包
可以看到ga.js在请求__utm.gif时带了很多信息,例如utmsr=1280×1024是屏幕分辨率,utmac=UA-35712773-1是_gaq中解析出的我的GA标识ID等等。
值得注意的是,__utm.gif未必只会在埋点代码执行时被请求,如果用_trackEvent配置了事件跟踪,则在事件发生时也会请求这个脚本。
由于ga.js经过了压缩和混淆,可读性很差,我们就不分析了,具体后面实现阶段我会实现一个功能类似的脚本。
1.4 后端脚本执行阶段
GA的__utm.gif是一个伪装成gif的脚本。这种后端脚本一般要完成以下几件事情:
(1)解析http请求参数的到信息。
(2)从服务器(WebServer)中获取一些客户端无法获取的信息,如访客ip等。
(3)将信息按格式写入log。
(4)生成一副1×1的空gif图片作为响应内容并将响应头的Content-type设为image/gif。
(5)在响应头中通过Set-cookie设置一些需要的cookie信息。
之所以要设置cookie是因为如果要跟踪唯一访客,通常做法是如果在请求时发现客户端没有指定的跟踪cookie,则根据规则生成一个全局唯一的cookie并种植给用户,否则Set-cookie中放置获取到的跟踪cookie以保持同一用户cookie不变(见图4)。
图4. 通过cookie跟踪唯一用户的原理
类似的脚本。
1.4 后端脚本执行阶段
GA的__utm.gif是一个伪装成gif的脚本。这种后端脚本一般要完成以下几件事情:
(1)解析http请求参数的到信息。
(2)从服务器(WebServer)中获取一些客户端无法获取的信息,如访客ip等。
(3)将信息按格式写入log。
(4)生成一副1×1的空gif图片作为响应内容并将响应头的Content-type设为image/gif。
(5)在响应头中通过Set-cookie设置一些需要的cookie信息。
之所以要设置cookie是因为如果要跟踪唯一访客,通常做法是如果在请求时发现客户端没有指定的跟踪cookie,则根据规则生成一个全局唯一的cookie并种植给用户,否则Set-cookie中放置获取到的跟踪cookie以保持同一用户cookie不变(见图4)。
图4. 通过cookie跟踪唯一用户的原理
这种做法虽然不是完美的(例如用户清掉cookie或更换浏览器会被认为是两个用户),但是是目前被广泛使用的手段。注意,如果没有跨站跟踪同一用户的需求,可以通过js将cookie种植在被统计站点的域下(GA是这么做的),如果要全网统一定位,则通过后端脚本种植在服务端域下(我们待会的实现会这么做)。
















