前端如何做页面分析

717 阅读5分钟

前言

有时候我们会有这样的需求:

  • 监测每一个页面的流量,停留时长,对页面的重要性形成判断
  • 了解用户页面行为浏览路径,分析页面之间的流量流转路径,以确定用户行为引导策略 这样我们需要以页面唯独去统计一些指标来帮助我们判断这个页面在生产环境的一些情况。

为此,我开发公司自己内部的埋点及可视化系统。今天就页面分析这块功能进行梳理和讨论。首先来看下目前这个可视化后台的效果。

所有页面的数据汇总:

image.png 单个页面的数据详情: image.png image.png

怎么做

实现流程

关于如何实现页面分析分为几个步骤

  1. (客户端,浏览器)首先需要在客户端上报我们要统计的数据,例如我们这里需要统计: 页面访问Pv、Uv、停留时间及跳转链路。我们需要将这些数据上报。
  2. (服务端)对客户端上报进行处理,存储数据库。
  3. (可视化后台)获取上报数据,进行数据渲染。

上报

上报数据

我们需要上报哪些数据呢,这得看你需要展示哪些数据,分析的需求是怎么样的。例如: 用户访问量、停留时间、跳转页面等需要上报。

如何上报

目前我采用的是 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 开始,且发生跳出)

由此可知,统计跳出率需要知道

  1. 页面 A 是否为起始页面
  2. 页面 A 是否在当前页面关闭
  3. 统计所有由 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中的部分数据统计。

流程图梳理

image.png