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

背景

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

数据搜集能够有不一样的选择。有的公司选择使用第三方统计的SDK,好比友盟、神策等;有的公司选择本身在产品中注入统计代码,搭建查询系统,固然后者的代价会比较大,但优势就是更贴近公司的业务。前端

数据埋点技术

代码埋点vue

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

优势:能够很是精确的选择何时发送数据。 缺点:维护代价较大,每一次更新都要对埋点代码进行维护,不然大几率搜集不到旧版本的数据。github

可视化埋点后端

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

优势:解决埋点代价大和维护代价大的问题。 缺点:覆盖的功能有限,不是全部的控件均可以经过这种方案定制。浏览器

无埋点服务器

也就是全埋点的意思,无埋点尽量收集全部控件的操做数据,而后再在系统里进行数据分析。cookie

优势:对页面全部元素进行埋点,能够获取页面元素点击几率,并进一步分析。 缺点:数据传输和服务器压力相对较大。

构建思路与核心代码

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

思惟导图

自执行方法

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

(function (win, doc) {
    var BP = {
        // 开放接口代码
    };
    
    win.BP = BP;
})(window, document);
复制代码

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

埋点方式

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

var BP = {
        send: function () {
            // 发送数据方法
        }
    };
复制代码

在页面的关键操做方法中经过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, type, func) {
                    ele.attachEvent('on' + type, func);
                };
            } else if (doc.addEventListener) {
                return function (ele, type, func) {
                    ele.addEventListener(type, func, false);
                };
            }
        }()
    }
复制代码

数据搜集

抛开业务来说,一般须要统计的数据每每是uv和pv,有时须要统计页面停留的时长。基于这些基础需求,整理了以下须要搜集的数据:

  • 客户端信息

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

// 浏览器信息
    var CI = {
        size: function () {
            return scr.width + 'x' + scr.height;
        }(),
        // 网络类型
        network: function () {
            return (nav.connection && nav.connection.type) ? nav.connection.type : '-';
        }(),
        // 语言
        language: function () {
            return nav.language || '';
        }(),
        timezone: function () {
            return new Date().getTimezoneOffset() / 60 || '';
        }(),
        ua: function () {
            return encodeURIComponent(ua);
        }(),
        os: function () {
            var o = uaOutput.os;
            return encodeURIComponent(o.name + '_' + o.version.original);
        }(),
        browser: function () {
            var b = uaOutput.browser;
            return b.name + '_' + b.version.original;
        }(),
        engine: function () {
            var e = uaOutput.engine;
            return e.name + '_' + e.version.original;
        }()
    };
复制代码
  • 流量来源

  • 页面地址

  • 会话id

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

var BP = {
        /** * 会话id,刷新页面会更新 */
        sessionId: function () {
            return UUID.create();
        }()
    }
复制代码
  • 设备id

设备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>
复制代码

总结

开发一个前端数据埋点工具,不须要特别复杂的技术,更可能是基于业务的思考。这里把其中比较关键的部分列举出来,做为一个参考:

  • 自执行方法
  • 跨浏览器事件侦听
  • 维护时间线
  • cookie读写
  • 单向跨域请求
  • 类vue计算属性

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

相关文章
相关标签/搜索