0

    从零开始写一个前端数据埋点工具(BuryingPoint)

    2023.05.31 | admin | 154次围观

    背景

    互联网发展到现在,数据的重要性已经不需要再多的强调,那如何做好数据搜集的工作则是每一家公司都要面临的问题。

    数据搜集可以有不同的选择。有的公司选择使用第三方统计的SDK,比如友盟、神策等;有的公司选择自己在产品中注入统计代码,搭建查询系统,当然后者的代价会比较大,但优点就是更贴近公司的业务。

    数据埋点技术

    代码埋点

    代码埋点就是在需要数据统计的地方植入数据上报的代码,统计用户行为。

    优点:可以非常精确的选择什么时候发送数据。缺点:维护代价较大,每一次更新都要对埋点代码进行维护,否则大概率搜集不到旧版本的数据。

    可视化埋点

    使用可视化交互手段代替写代码,把核心代码和配置、资源分开,每次打开都通过网络更新配置和资源。

    优点:解决埋点代价大和维护代价大的问题。缺点:覆盖的功能有限,不是所有的控件都可以通过这种方案定制。

    无埋点

    也就是全埋点的意思,无埋点尽可能收集所有控件的操作数据,然后再在系统里进行数据分析。

    优点:对页面所有元素进行埋点,可以获取页面元素点击概率,并进一步分析。缺点:数据传输和服务器压力相对较大。

    构建思路与核心代码

    依然是先从一个思维导图开始。

    自执行方法

    确保埋点工具可以即插即用,只要加载完毕就可以自动上报部分数据。具体实现方式如下:

    (function (win, doc{
        var BP = {
            // 开放接口代码
        };

        win.BP = BP;
    })(windowdocument);

    这样在js文件加载完毕时,就可以直接在全局使用BP来调用埋点工具的方法了。

    埋点方式

    使用代码埋点的方式来上报数据,在工具中定义:

        var BP = {
            sendfunction () {
                // 发送数据方法
            }
        };

    在页面的关键操作方法中通过BP.send()调用。

    同时,考虑到服务端渲染的情况,页面可能直接由后端输出。后端开发者也可以直接在标签中添加属性bp-data,来实现用户有交互操作时进行数据上报。

        /**
         * 埋点,捕获带有bp-data属性的节点点击事件
         */

        var buryingPoint = function () {
            var attr = 'bp-data';
            var evtType = utils.mobile ? 'touchstart' : 'mousedown';
            utils.addEvent(doc, evtType, function (evt{
                var target = evt.srcElement || evt.target;
                while (target && target.parentNode) {
                    if (target.hasAttribute(attr)) {
                        BP.send();
                        break;
                    }
                    target = target.parentNode;
                }
            });
        };

    为了兼顾PC与移动端浏览器,将utils.addEvent设计为一个可以跨浏览器侦听事件的方法,具体实现方法如下:

        var utils = {
            /**
             * 跨浏览器事件侦听
             */

            addEvent: function () {
                if (doc.attachEvent) {
                    return function (ele, typefunc) {
                        ele.attachEvent('on' + typefunc);
                    };
                } else if (doc.addEventListener)
     {
                    return function (ele, typefunc) {
                        ele.addEventListener(typefuncfalse);
                    };
                }
            }()
        }

    数据搜集

    抛开业务来讲,通常需要统计的数据往往是uv和pv,有时需要统计页面停留的时长。基于这些基础需求,整理了如下需要搜集的数据:

    客户端信息对于前端开发来说属于相对比较头疼的问题引用js文件加上时间戳,各种魔改UserAgent严重影响开发者们的情绪。相信各大公司对于UserAgent判断也有一个较为成熟的处理,作为个人开发来说推荐一个代码库ua-device,可以减少很多这方面的工作。唯一的不足,是引用这个库,会使打包出来的js文件体积增加150KB左右,个人认为在当前网络环境下这点无需顾虑。

       // 浏览器信息
        var CI = {
            sizefunction () {
                return scr.width + 'x' + scr.height;
            }(),
            // 网络类型
            network: function () {
                return (nav.connection && nav.connection.type) ? nav.connection.type : '-';
            }(),
            // 语言
            language: function () {
                return nav.language || '';
            }(),
            timezonefunction () {
                return new Date().getTimezoneOffset() / 60 || '';
            }(),
            uafunction () {
                return encodeURIComponent(ua);
            }(),
            osfunction () {
                var o = uaOutput.os;
                return encodeURIComponent(o.name + '_' + o.version.original);
            }(),
            browserfunction () {
                var b = uaOutput.browser;
                return b.name + '_' + b.version.original;
            }(),
            enginefunction () {
                var e = uaOutput.engine;
                return e.name + '_' + e.version.original;
            }()
        };

    会话id用于计算uv,在工具初始化的时候生成一个uuid。由于会话id在打开页面后不会更新,所以使用类vue计算属性的方式来实现。

        var BP = {
            /**
             * 会话id,刷新页面会更新
             */

            sessionId: function () {
                return UUID.create();
            }()
        }

    设备id用于串联用户的行为。比如用户浏览了若干个页面,上报了数条数据,就可以用设备id将这些行为串联起来。由于前端无法真正获取到所用设备的唯一标识,所以与会话id一样,采用不会重复的uuid。同样也是类vue的计算属性。

        BP = {
            /**
             * 设备id,读取cookie,不存在则种入cookie
             */

            deviceId: function () {
                var did = utils.getCookie(cookieName);
                if (!did) {
                    did = UUID.create();
                    utils.setCookie(cookieName, did, year);
                }
                return did;
            }()
        }

    记录页面停留时长成本最低的方法就是使用轮询上报数据,请求间隔可以根据业务需求来定。毕竟间隔越小,服务器承载的压力就会更大一点,但获取的数据就更准确。

      /**
         * Ticker钩子函数,用于上报页面停留时长
         * @param dt 间隔时间
         */

        var calStayTime = function (dt) {
            totalTime += dt;
            if(totalTime >= stayTime) {
                BP.send();
                totalTime -= stayTime;
            }
        };

        // 启动ticker
        ticker.start();
        ticker.register(calStayTime);

        // 页面离开时不再计时
        utils.addEvent(doc, 'visibilitychange'function () {
            if (doc.visibilityState === 'hidden') {
                ticker.stop();
            } else {
                ticker.start();
            }
        });

    为了能够上报尽可能准确的停留时间,当离开页面时(比如最小化或切换标签)应当停止计时。这里用一个独立的,简易版本的Ticker来维护时间线,更多关于维护时间线的问题可以看下面的链接。

    使用TypeScript实现一个Ticker

    数据存储与读取

    采用cookie存储一些需要持久保存的数据,比如设备id。

       var utils = {
            /**
             * 设置cookie
             * @param name 名称
             * @param value 值
             * @param days 保存时间
             * @param domain 域
             */

            setCookie: function (name, value, days, domain{
                if (value === null) {
                    return;
                }
                if (domain === undefined || domain === null) {
                    // 去除host中的端口部分
                    domain = utils.stringSplice(win.location.host, ''':''');
                }
                if (days === undefined || days === null || days === '') {
                    doc.cookie = name + '=' + value + ';domain=' + domain + ';path=/';
                } else {
                    var now = new Date();
                    var time = now.getTime() + DAY * days;
                    now.setTime(time);
                    doc.cookie = name + '=' + value + ';domain=' + domain + ';expires=' + now.toUTCString() + ';path/';
                }
            },
            /**
             * 读取cookie
             * @param name 名称
             */

            getCookie: function (name{
                if (name === undefined || name === null) {
                    return;
                }
                var reg = RegExp(name);
                if (reg.test(doc.cookie)) {
                    return utils.stringSplice(doc.cookie, name, ';''');
                }
            }
        }

    上报数据

    埋点数据上报本质上可以看作是一种单向请求,即不需要关心服务器反馈,可以采用image标签的方式向服务器发送数据,同时还可以避免额外的跨域问题。

        var utils = {
            /**
             * 发送请求,使用image标签跨域
             * @param url 接口地址
             */

            sendRequest: function (url{
                if (page.length === 0) {
                    console.error('请配置有效的page参数''@burying-point');
                    return;
                }
                var img = new Image();
                img.src = url;
            }
        }

    扩展

    添加一个白名单过滤,规定只有白名单内的域名才可以发送请求。这只是一个小把戏,避免在开发过程中上报过多的脏数据,增加数据分析的工作量。

        var utils = {
            /**
             * 白名单校验
             */

            checkWhiteList: function () {
                if (whiteList.length === 0) {
                    return true;
                }
                var href = win.location.href;
                var flag = false;
                for (var i = 0; i < whiteList.length; i++) {
                    if (href.indexOf(whiteList[i]) > -1) {
                        flag = true;
                        break;
                    }
                }
                return flag;
            }
        }

    使用方式

    构建的埋点工具可以通过两种不同的方式进行数据上报。

    第一种,通过代码直接上报:

        <script>
            BP.send();
        script
    >

    第二种,在DOM标签中添加bp-data属性:

        <div bp-data>点击我会上报一条数据div>

    总结

    开发一个前端数据埋点工具,不需要特别复杂的技术引用js文件加上时间戳,更多是基于业务的思考。这里把其中比较关键的部分列举出来,作为一个参考:

    完整代码与使用方法,请移步GitHub,感谢。

    *声明:本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

    公众号ID:tzbc666

    有趣的灵魂在等你

    长按扫码可关注

    点个好看和转发也是一种支持哟!

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    标签: 前端开发
    发表评论