前端埋点实现方案✔

52,460 阅读11分钟

前言

领导今天又来活了😣,要记录每个页面的停留时间,以及页面的操作,是由哪个页面跳转过来的,给每个页面生成GUID上报给服务端,并且需要携带设备型号和设备唯一标识🙄

名称解释

UV(Unique visitor)

是指通过互联网访问、浏览这个网页的自然人。访问您网站的一台电脑客户端为一个访客。00:00-24:00内相同的客户端只被计算一次。一天内同个访客多次访问仅计算一个UV

IP(Internet Protocol)

独立IP是指访问过某站点的IP总数,以用户的IP地址作为统计依据。00:00-24:00内相同IP地址之被计算一次。

UV与IP区别

如:你和你的家人用各自的账号在同一台电脑上登录新浪微博,则IP数+1,UV数+2。由于使用的是同一台电脑,所以IP不变,但使用的不同账号,所以UV+2

PV(Page View)

即页面浏览量或点击量,用户每1次对网站中的每个网页访问均被记录1个PV。用户对同一页面的多次访问,访问量累计,用以衡量网站用户访问的网页数量。

VV(Visit View)

用以统计所有访客1天内访问网站的次数。当访客完成所有浏览并最终关掉该网站的所有页面时便完成了一次访问,同一访客1天内可能有多次访问行为,访问次数累计。

PV与VV区别

如:你今天10点钟打开了百度,访问了它的三个页面;11点钟又打开了百度,访问了它的两个页面,则PV数+5,VV数+2.PV是指页面的浏览次数,VV是指你访问网站的次数。

埋点分类

代码埋点

通过代码的方式在页面中嵌入逻辑🎨,比如捕获一个点击事件,在这个点击事件之前加入代码埋点⛑,上报给后台🥐。国内已经有很多成型的服务商了如友盟,百度统计等🌯,都提供了比较成型的方案🎨,并可以在后台管理系统中查看比较详细的数据分析🧵,但是肯定会有领导想要把这些事情掌握在自己的手中,我们就只能通过自身开发来实现代码埋点🍞。

  • 优点:
  • 控制精准,可以非常精确地选择什么时候发送数据。
  • 传递多样化自定义属性、自定义事件,传递比较丰富的数据到服务端。
  • 缺点:
  • 埋点代价比较大,每一个控件的埋点都需要添加相应的代码,不仅工作量大,必须是技术人员才能完成。
  • 更新的代价比较大,每一次更新埋点方案,都必须改代码。

可视化埋点

个人理解的可视化埋点应该是肯定需要第三方的服务商支持🍜,不会有做专门业务的公司去做可视化埋点的解决方案。可视化埋点开发人员除集成采集可视化SDK 外👜,不需要额外去写埋点代码🍠,而是由业务人员或运营人员通过访问分析平台的圈选功能🤔,来“圈”出需要对用户行为进行捕捉的控件🎪,并给出事件命名🚘。圈选完毕后,这些配置会同步到各个用户的终端上😮,由采集SDK按照圈选的配置自动进行用户行为数据的采集和发送🚇。

优点:

  • 埋点代价小,更新代价小
  • 埋点只需业务同学接入,开发只需对接可视化SDK

缺点:

  • 无法做到自定义获取数据
  • 可视化埋点覆盖的功能有限
  • 仅支持客户端行为

无痕埋点

无痕埋点又叫全埋点🥪,网上又很多文章写的都是无痕埋点是将所有事件的操作全部上报😀,但是我们在实现的过程中肯定是不会监听那么多的事件吧😋,但是好像也有第三方服务商sdk集成了所有事件😏。

我的个人理解无痕埋点是针对某一个单一事件,在全局实现监听达到上报,而不是全部事件上报才叫无痕埋点🥙。只要有某个事件在全局实现监听,针对这个事件的埋点方式就称为无痕埋点🌯

优点:

  • 由于采集的是全量数据,所以产品迭代过程中是不需要关注埋点逻辑的,也不会出现漏埋、误埋等现象。
  • 无埋点方式因为收集的是全量数据,可以大大减少运营和产品的试错成本
  • 如果集成sdk之后无需埋点,方便快捷

缺点:

  • 缺点与可视化埋点相同,未解决个性化自定义获取数据的问题,缺乏数据获取的灵活性;
  • 数据量过大,如果不使用第三方服务商,针对自身的服务器是个考验

实现方案步骤(uni-app,其他项目逻辑相同)

两方面上报: 1.事件上报(目前只有点击事件埋点),2.停留时间上报

  • 事件上报:通过给元素绑定自定义指令的方式实现(减少对原有代码的侵入)🍜,将信息存储在缓存池中定时上报,上报之后清空之前的上报信息🥠。
  • 停留时间上报:需要重新封装路由,创建路由拦截在跳转之前记录来源,以及上一个页面的停留时间,当拦截器捕获成功之后🌯,如果发现停留时间大于xx秒进行上报🥙。

优点:清晰合理,比较适合新项目。

缺点:针对老项目需要与产品和运营对接埋点方案绑定自定义事件🤪,如果是老项目需要对uni.navigateTo,uni.redirectTo,uni.reLaunch,uni.switchTab 进行二次封装。

问:为什么何将信息存储,而不是实时上报?
答:考虑到服务器的压力,采用了定时上报的方式。

问:为什么监听停留时长大于XX秒才进行上报?
答:1.服务器的压力问题。2考虑到用户可能做一些没意义的操作,所以停留时长大于XX秒才属于有效页面。

实现方法

事件埋点上报

  • common文件夹下创建自定义指令文件,在main.js中引用该文件。
import Vue from "vue";
// 自定义埋点指令
Vue.directive("buried", {
  bind: (el, binding) => {
    if (binding.value) {
      //这里参数是根据自己业务可以自己定义
      let params = {
        currentUrl: binding.value.currentUrl,
        triggerType: binding.value.triggerType,
        title: binding.value.title,
        frontTriggerType: binding.value.triggerType,
        behavior: binding.value.behavior,
      };
      //如果是浏览类型,直接保存,目前只考虑点击类型
      if (binding.value.triggerType == "browse") {
        console.log("browse", params);
        //调用后台接口保存数据
      } else if (binding.value.triggerType == "click") {
        //如果是click类型,监听click事件
        el.addEventListener(
          "click",
          () => {
            // 将操作和内容存储在缓存中定时上报
            let buriedArray = uni.getStorageSync('buriedArray') //获取埋点集合
            buriedArray.push(params)  // 将埋点集合存入缓存中
            uni.setStorageSync('buriedArray', buriedArray)
          },
          false
        );
      }
    }
  },
});

app.vue中的onLaunch生命周期中创建定时任务与缓存池,在onHide生命周期中销毁定时任务

let timeInterval = null
onLaunch(){
  uni.setStorageSync('buriedArray', [])
		// 定时上报埋点数据
		timeInterval = setInterval(() => {
			if (uni.getStorageSync('buriedArray').length > 0) {
				// 上报逻辑,根据需求自行完善
				upLoadBuriedInfo(uni.getStorageSync('buriedArray'))
				// 上报成功之后清空埋点数据重新上报
				uni.setStorageSync('buriedArray', [])
			}
		}, time)
}
onHide: function () {
		timeInterval && clearInterval(timeInterval)
}

停留时间上报

  • 首先读取page.json中的文件获取pathtitle(原文链接),先在项目根目录创建一个router文件夹🍞,在vue.config.js里面加入如下代码,这样每次打包之后router文件夹下的index.js中就会生成一个 titlepath的对应表。😚
const path = require('path')
const fs = require('fs')

const fromFile = path.join(__filename, '../pages.json')
const toFile = path.join(__filename, '../router/index.js')
const buffPrefix = Buffer.from('export default ')

const fileData = fs.readFileSync(fromFile)

// 转成可读的js, 正则删除注释, 不然JSON.parse会报错
const fileObj = JSON.parse(fileData.toString().replace(/\/\/.*/g, ''))

// 遍历,只取路径和标题,其他的不要,已减小文件体积
const routes = fileObj.pages.map(e => {
	return {
		title: e.style ? e.style.navigationBarTitleText ? e.style.navigationBarTitleText : e.name : '未知',
		path: '/'+e.path,
	}
})
fs.writeFileSync(toFile, buffPrefix + Buffer.from(JSON.stringify(routes)))

module.exports = {
	configureWebpack: {
		plugins: []
	}
}
  • 在common中创建routeGuards.js 监听路由拦截,进行上报,在main.js中引用该文件
import Vue from 'vue';
import v5 from 'uuid/v5';
import UniRouteGuard from '@/js_sdk/pocky-route-gurads/lib';
import router from '@/router'
Vue.use(UniRouteGuard);

const guard = new UniRouteGuard();

let startTime = Date.now();
guard.beforeEach((to, from, next) => {
	let currentTime = Date.now();
	if (from.url && from.url == '/pages/first/index') {
		next()
		return
	}
	console.log(router)
	if (to.url) {
		if (to.url.indexOf('?') > -1) {
			to.url = to.url.substr(0, to.url.indexOf('?'))
		}
	}
	let fromName = router.find(item => item.path == from.url) ? router.find(item => item.path == from.url).title : '未知'
	let toName = router.find(item => item.path == to.url) ? router.find(item => item.path == to.url).title : '未知'
  const stayTime=  parseInt((currentTime - startTime) / 1000)
	const MY_NAMESPACE = '1b671a64-40d5-491e-99b0-da01ff1f3341';

	console.log(`由${fromName}跳转到${toName}并在${fromName}中停留了${stayTime}秒钟`);
	let params = {
		fromUrl: from.url, //来源地址
		toUrl: to.url, // 目的地址
		fromName: fromName, // 来源名称
		toName: toName, // 目的名称
		stayTime: stayTime, // 停留时长
		guid: v5(from.url, MY_NAMESPACE), //页面uuid
		appUuid: uni.getStorageSync('appUuid') ? uni.getStorageSync('appUuid') : '', // app唯一标识
		model: uni.getStorageSync("model")? uni.getStorageSync('model') : '', // 手机型号
	}
	console.log(params)
  if(stayTime>10){
   // 上报逻辑
    console.log('停留的时间大于10秒钟了,可以进行上报')
  }
  startTime = Date.now();
	next();
});

因为uni-app 没有提供自身的路由拦截插件🥠,所以需要我们手动去封装🚘。这种方案针对新项目比较合适,但是针对老项目路由跳转的逻辑都已经通过原生的方式写完了😣,我们在进行封装的话修改的点太多了🤔,所以在网上找到了这个插件不用修改跳转api并且可以获取到上一个页面的路由(全局路由守卫),原文介绍的是通过npm的方式进行安装,我采用的是hbuilderX导入的方式(有需要的同学可以自行查找)🤙。

使用方法

  • 事件埋点:将需要埋点的元素绑定改指令v-buried绑定参数
<!-- 
triggerType: 事件类型
title: 页面标题
currentUrl: 页面路由
behavior: 操作行为
-->
<view v-buried="{
triggerType:'click',
title:'我的',
currentUrl: currentPath,
behavior:'点击我的收藏按钮'}">
</view>

页面跳转

  • 需要在page.json中进行修改如果使用的是自定义导航条或者没有使用导航条需要进行命名
  • 需要将页面中跳转路径修改成绝对路径否则路由会匹配不到

uni-app集成友盟统计

  • 首先在友盟上创建一个应用获取其对应的appkey

  • 在uni-app的App模块配置中勾选友盟统计并填写对应的key(渠道id随意填写就可以)
  • 这样实际上就在uni-app中集成了友盟统计,如果想看到详细的上报数据可以在友盟后台进行查看(注:必须打包之后或者采用自定义基座的方式才能够进行上报,上报结果可能第二天才会生效,具体上报规则可以查看友盟官网解释)。

这样最基本的集成就完成了。

那我们如何埋入我们的自定义事件呢,比如我将一个燃气罩加入了购物车把他当成一个事件,并且能在友盟后台查看到我加入商品的属性以及加入空气炸锅或者加入电饼铛的一些数量对比

  • 1.在友盟后台注册相应的自定义事件 (我的应用->设置->添加事件)
  • 2.创建事件完毕之后在代码中进行上报的代码的编写(步骤一和步骤二谁先谁后都可以)
// https://www.html5plus.org/doc/zh_cn/statistic.html 文档链接
// 第一个参数: 在友盟后台注册的事件id,第二个参数:业务数据
plus.statistic.eventTrig("purchase", {"type":"book","quantity":"3"});
  • 上报可能会有延迟,发行过一段时间之后就可以在友盟后台查看到相关数据。

  • 点击查看按钮可以看到更详细的数据,以及上报的业务数据对比

uni-app 自带统计

  • 无需集成其他相关sdk只需在manifest.json中勾选uni统计配置即可注:必须打包之后才能够进行上报,上报结果可能第二天才会生效,具体上报规则可以查看uni-app官网解释
  • 查看上报后台

// 注意如果第一个参数是title第二个参数必须是字符串
uni.report("upload",{
					title: '上报数据',
					content: '上报内容'
				})
  • 可在事件和转化模块中进行查看

在测试uni-app自带的统计中也遇到了一些问题,如果有朋友能够解决的话也可以帮助顶顶帖,我在进行完善问题贴

总结

针对埋点的方案,自己也是不太熟悉,没有实战经验,找了很多途径,文中可能会有不对的地方,希望小伙伴们可以多多指点。一起加油!🤪

参考链接