前言
有时候我们会有这样的需求:
- 监测每一个页面的流量,停留时长,对页面的重要性形成判断
- 了解用户页面行为浏览路径,分析页面之间的流量流转路径,以确定用户行为引导策略
这样我们需要以页面唯独去统计一些
指标
来帮助我们判断这个页面在生产环境的一些情况。
为此,我开发公司自己内部的埋点及可视化系统。今天就页面分析这块功能进行梳理和讨论。首先来看下目前这个可视化后台的效果。
所有页面的数据汇总:
单个页面的数据详情:
怎么做
实现流程
关于如何实现页面分析分为几个步骤
- (客户端,浏览器)首先需要在客户端上报我们要统计的数据,例如我们这里需要统计: 页面访问Pv、Uv、停留时间及跳转链路。我们需要将这些数据上报。
- (服务端)对客户端上报进行处理,存储数据库。
- (可视化后台)获取上报数据,进行数据渲染。
上报
上报数据
我们需要上报哪些数据呢,这得看你需要展示哪些数据,分析的需求是怎么样的。例如: 用户访问量、停留时间、跳转页面等需要上报。
如何上报
目前我采用的是 navigator.sendBeacon
作为上报首选。可以说明下选它有几个理由:
- 数据发送是可靠的。
- 数据异步传输,在浏览器空闲的时候异步发送数据,不影响页面诸如 JS、CSS Animation 等执行。
- 不影响下一导航的载入,不会阻塞页面的加载或卸载。
注意点:
如果你想在页面关闭前上报一些数据,不建议在 unload
或者 beforeunload
中发送,在移动端做过测试,并不会触发该事件。替换方案是在 visibilitychange
中进行处理。
document.addEventListener('visibilitychange', function logData() {
if (document.visibilityState === 'terminated') {
navigator.sendBeacon('/log', analyticsData);
}
});
状态枚举如下,根据你的需要进行不同时机的上报。
type VisibilityState = 'terminated' | 'hidden' | 'active' | 'frozen' | 'passive'
navigator.sendBeacon
的请求 method 是 post 类型,这里作为回退方案就使用了 xhr,有经验的小伙伴可以在评论区留言说明你们的方案。如果采用 image 作为回退,服务端需要支持两种类型的接口请求。
服务端处理
至此我们已经拿到客户端上传的数据,我们该如何处理这些数据呢。这里我拿跳出率
的统计作为例子。
跳出率: 指单页会话次数在所有会话次数中所占的比例 通俗的讲,这个页面是直接访问的,并且在当前页面关闭。来测试下你是否完全理解该定义。
- 星期一:网页 B > 网页 A > 网页 C > 退出
- 星期二:网页 B > 退出
- 星期三:网页 A > 网页 C > 网页 B > 退出
- 星期四:网页 C > 退出
- 星期五:网页 B > 网页 C > 网页 A > 退出
分别统计 A、B、C 的跳出率。
- 网页 A:0%(有 1 个会话由网页 A 开始,但该会话不是单页会话,因此没有跳出率)**
- 网页 B:33%(跳出率低于退出率,因为有 3 个会话由网页 B 开始,但只有 1 个会话发生跳出)
- 网页 C:100%(有 1 个会话由网页 C 开始,且发生跳出)
由此可知,统计跳出率需要知道
- 页面 A 是否为起始页面
- 页面 A 是否在当前页面关闭
- 统计所有由 A 作为起始页面的数
跳出率 = 满足条件1 && 满足条件2 / 满足条件3
Typeorm
我这里使用 mysql 作为数据库,选择使用 Typeorm 操作数据库。经过上面的分析,我们来定义表结构
export class Visit extends BaseEntity {
@PrimaryGeneratedColumn() // 主键
id: number
@Index() // 索引
@Column()
uuid: string; // 自己生成的唯一 id
@Index()
@Column()
projectId: string; // 项目id
@Column()
url: string; // 页面地址,页面维度的分析
@Column({ default: false })
isOrigin: boolean; // 是否为起始页面,用于跳出率判断依据
@Column({ default: false })
isClose: boolean; // 是否关闭了页面
@Column({default: 0})
@Column()
updated: Date // 数据更新时间
}
如果我们想统计页面跳出率,定义了上面的数据就可以满足我们的需求。
可视化后台
如何实现完成文章开始图1的表格展示呢?
首先来看后端接口如何查询。
const bothOpenAndCloseCount = 'COUNT(case when visit.isClose = 1 AND visit.isOrigin = 1 then 1 else null end)'
// 也可以写成 'COUNT(if((visit.isClose = 1 AND visit.isOrigin = 1), 1, null))'
const openCount = 'COUNT(case when visit.isOrigin = 1 then 1 else null end)'
async function queryTableList(ctx) {
const { projectId, start, end, page = '' } = ctx.request.query
return await getRepository(Visit)
.createQueryBuilder('visit') // 创建 queryBuilder
.select(['visit.url']) // 查询字段, 对应 sql select url from 'visit'
.where('visit.projectId = :projectId', { projectId }) // 查询条件
// 多条件使用 andWhere 继续添加
.andWhere('visit.updated between :start and :end', { start, end })
// 模糊搜索
.andWhere('visit.url like :page').setParameters({ page: '%' + page + '%' })
// 分组
.groupBy("visit.url")
// 添加自定义字段
.addSelect(`Format( ${bothOpenAndCloseCount} / ${openCount}, 4)`, 'originCloseRate')
.orderBy("COUNT(visit.url)", "DESC") // 根据 pv 数量进行排序
// 前面的操作返回的都是 queryBuilder 这里去获得原始结果列表,相当于执行查询
.getRawMany()
}
这样我们获取到了每组链接对应的跳出率,结构如下:
const tableList: {url:string;originCloseRate:string}[] = []
至此就完成表格中跳出率的计算,完成了图1中的部分数据统计。