zoukankan      html  css  js  c++  java
  • 【Web技术】465- 关于前端埋点统计方案思考

    埋点即监控用户在应用表现层的行为,于产品迭代而言至关重要。埋点数据分析是产品需求的 来源,检验功能是否达预期的 佐证。前端较服务端更接近用户,本小白将在此对前端埋点统计方案述说一二。

    采集埋点数据可做如下分析(以百度统计为例):

    用户属性用户行为 转化各类可视化图表:

    不同产品对数据的关注角度不同,可按需采集。如信息流产品对停留时长的关注度更高(统计页面访问 & 跳出时间),商城类较注重“复购率”(统计新老用户),广告类更追求最大限度。

    埋点统计通常分两类:

    • 页面访问量统计

    • 功能点击量统计

    页面访问量统计

    页面访问量统计通常分两类:

    • PV:页面访问人次

    • UV:页面访问人数

    页面访问量,并非仅仅取决于其内容吸引力,影响因素包含入口 外观位置深度 等等(在此不考虑刚需)。入口外观属 UI 设计范畴,入口位置可通过分析用户点击热力图调整,入口深度可通过分析用户访问路径调整。

    用户点击 热力图 形如:

    将核心页面入口置于热力图红色区域?

    采集页面加载 fromto 以获知用户访问路径:

    分析可知用户普遍 访问深度、每一深度 & 每一页面的 流失率 等,依照结果调整核心页面入口源、入口深度?

    页面访问量,也并非仅仅取决于产品设计。假若 PV 稳定的页面访问量 爆跌,便需考虑其加载成功率了(或许是枚技术 bug)。

    前端如何实现全局 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: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/onbeforeunload 和 onunload: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/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 全局注册混入 beforeRouteEnterbeforeRouteLeave 对象:

    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 是否触发?

    上述方案存在明显缺陷:

    • 官方曰慎用全局混入对象!!!

    • 对于页面同名钩子函数 beforeRouteEnterbeforeRouteLeave,如何 merge?如何 next

    • 含子路由的页面将调用 2beforeRouteEnterbeforeRouteLeave,PV 无形翻倍...

    我猜此刻有打全局混入 createddestroyed 并通过 this.$route 获知访问对象主意的人了,试试看?

    令人不知所措的输出,打印次数与 路由表 长度一致嗷~

    其中 this.$app.logEvent(vm.$app.logEvent) 等同方案一中 App.logEvent,不再赘述。

    如何恰当选取全局 PV 统计方案?

    • SPA 应用:仅单入口,在入口文件全局定义 Router.beforeEach 方便可行。

    • MPA 应用:多入口,在每个入口文件定义 Router.beforeEach?可封装公用逻辑(伪装单入口),免去重复构造 entry 的成本。

    • SPA + MPA 混合应用:emmmmmm...采用 MPA 应用的统计方案。

    • SSR 应用:为追求更好的 SEO 而采用服务端渲染(SSR)。以 Jinja(Python 模板)为例,调用 TemplateView 则为渲染页面(不同于前后端分离项目,服务端无法获知接口调用与页面渲染的对应关系),统计其调用次数及 TemplateName 可知页面 PV。

    至于 UV,统计 PV 时采集 userId 去重即可。若应用无用户管理体系,采集 IPdeviceId 也可粗略得知 UV(不精准)。

    功能点击量统计

    不同功能的点击量不同,同一功能不同区域的点击量也不同:

    按钮点击量,影响因素包含按钮 外观位置入口 等等(在此不考虑刚需)。按钮外观属 UI 设计范畴,按钮位置可通过分析用户点击热力图调整,按钮入口可通过分析触发源分布调整。

    举一实例:

    运营同学会将一张图片裁切成 n 个区域,点击每一区域所推荐商品不同。统计区域点击坐标,据热力图调整商品排序以求 利益最大化

    前端如何实现功能点击量统计?

    本人将功能点击分两类:

    • 带业务接口请求

    • 无业务接口请求

    方案一

    将埋点上报混入业务接口请求,无接口请求的点击采用自定义上报:

    其中 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>
    

    方案本无优劣,适合才更重要,需综合考虑 产品设计产品使用度服务利用率 等等。例使用度较低应用可将统计与业务混于同一服务以节约成本,使用度较高应用可采取 本地缓存批量上报 以降低服务压力,但批量上报是否加大统计 误差

    本文所述仅冰山一角,欢迎大家留言宝贵经验~

    看到几篇不错的文章:

    页面跳转时,统计数据丢失问题探讨:http://taobaofed.org/blog/2016/04/01/lose-statistics/

    Page Lifecycle API 教程:http://www.ruanyifeng.com/blog/2018/11/page_lifecycle_api.html

    我的后花园:https://sunmengyuan.github.io/garden/

    我的 github:https://github.com/sunmengyuan

    本文转载自掘金专栏,作者 呆恋小喵 

    原创系列推荐

    1. JavaScript 重温系列(22篇全)

    2. ECMAScript 重温系列(10篇全)

    3. JavaScript设计模式 重温系列(9篇全)

    4. 正则 / 框架 / 算法等 重温系列(16篇全)

    5. Webpack4 入门(上)|| Webpack4 入门(下)

    6. MobX 入门(上) ||  MobX 入门(下)

    7. 59篇原创系列汇总

    回复“加群”与大佬们一起交流学习~

    点这,与大家一起分享本文吧~

    个人博客:http://www.pingan8787.com 微信公众号【前端自习课】和千万网友一起,每日清晨,享受一篇前端优秀文章。 目前已连续推送文章 600+ 天,愿每个人的初心都能一直坚持下去!
  • 相关阅读:
    xunsearch 在 window 下测试实践(2)
    xunsearch 在 window 下测试实践(1)
    PHPExcel 读取excel文档单元格的值时出现多种字体的解决方法
    laravel session丢失问题
    php多进程批量处理任务
    idea系列产品破解
    lnmp1.4环境下phpinfo扩展缺失解决方法
    从今天开始培养我的写作能力
    go map底层实现
    go之cron使用
  • 原文地址:https://www.cnblogs.com/pingan8787/p/13069537.html
Copyright © 2011-2022 走看看