javascript 實現高仿 growingIO

前言

埋點,是網站分析的一種經常使用的數據採集方法。咱們主要用來採集用戶行爲數據(例如頁面訪問路徑,點擊了什麼元素)進行數據分析,從而讓運營同窗更加合理的安排運營計劃。如今市面上有不少第三方埋點服務商,百度統計,友盟,growingIO 等你們應該都不太陌生,大多狀況下你們都只是使用,最近我研究了下 web 埋點,你要不要了解下。javascript

現有埋點三大類型

用戶行爲分析是一個大系統,一個典型的數據平臺。由用戶數據採集,用戶行爲建模分析,可視化報表展現幾個模塊構成。現有的埋點採集方案能夠大體被分爲三種,手動埋點,可視化埋點,無埋點html

  1. 手動埋點 手動代碼埋點比較常見,須要調用埋點的業務方在須要採集數據的地方調用埋點的方法。優勢是流量可控,業務方能夠根據須要在任意地點任意場景進行數據採集,採集信息也徹底由業務方來控制。這樣的有點也帶來了一些弊端,須要業務方來寫死方法,若是採集方案變了,業務方也須要從新修改代碼,從新發布。
  2. 可視化埋點 但是化埋點是近今年的埋點趨勢,不少大廠本身的數據埋點部門也都開始作這塊。優勢是業務方工做量少,缺點則是技術上推廣和實現起來有點難(業務方前端代碼規範是個大前提)。阿里的活動頁不少都是運營經過可視化的界面拖拽配置實現,這些活動控件元素都帶有惟一標識。經過埋點配置後臺,將元素與要採集事件關聯起來,能夠自動生成埋點代碼嵌入到頁面中。
  3. 無埋點 無埋點則是前端自動採集所有事件,上報埋點數據,由後端來過濾和計算出有用的數據,優勢是前端只要加載埋點腳本。缺點是流量和採集的數據過於龐大,服務器性能壓力山大,主流的 GrowingIO 就是這種實現方案。

咱們暫時放棄可視化埋點的實現,在 手動埋點無埋點 上進行了嘗試,爲了便於描述,下文我會稱採集腳本爲 SDK。前端

思考幾個問題

埋點開發須要考慮不少內容,貫穿着不輕易動手寫代碼的原則,咱們在開發前先思考下面這幾個問題java

  1. 咱們要採集什麼內容,進行哪些採集接口的約定
  2. 業務方經過什麼方式來調用咱們的採集腳本
  3. 手動埋點:SDK 須要封裝一個方法給業務方進行調用,傳參方式業務方可控
  4. 無埋點:考慮到數據量對於服務器的壓力,咱們須要對無埋點進行開關配置,能夠配置進行哪些元素進行無埋點採集
  5. 用戶標識:遊客用戶和登陸用戶的採集數據怎麼進行區分關聯
  6. 設備Id:用戶經過瀏覽器來訪問 web 頁面,設備Id須要存儲在瀏覽器上,同一個用戶訪問不一樣的業務方網站,設備Id要保持同樣,怎麼實現
  7. 單頁面應用:如今流行的單頁面應用和普通 web 頁面的數據採集是否有差別
  8. 混合應用:app 與 h5 的混合應用咱們要怎麼進行通信

咱們要採集什麼內容,進行哪些採集接口的約定

第一期咱們先實現對 PV(即頁面瀏覽量或點擊量) 、UV(一天內同個訪客屢次訪問) 、點擊量、用戶的訪問路徑的基礎指標的採集。精細化分析的流量轉化須要和業務相關,須要和數據分析方作約定,咱們預留擴展。因此咱們的採集接口須要進行如下的約定android

{
    "header":{ // HTTP 頭部
        "X-Device-Id":" 550e8400-e29b-41d4-a716-446655440000", //設備ID,用來區分用戶設備
        "X-Source-Url":"https://www.baidu.com/", //源地址,關聯用戶的整個操做流程,用於用戶行爲路徑分析,例如登陸,到首頁,進入商品詳情,退出這一整個完整的路徑
        "X-Current-Url":"", //當前地址,用戶行爲發生的頁面
        "X-User-Id":"",//用戶ID,統計登陸用戶行爲
    },
    "body":[{ // HTTP Body體
        "PageSessionID":"", //頁面標識ID,用來區分頁面事件,例如加載和離開咱們會發兩個事件,這個標識可讓咱們知道這個事件是發生在一個頁面上
        "Event":"loaded", //事件類型,區分用戶行爲事件
        "PageTitle":  "埋點測試頁",  //頁面標題,直觀看到用戶訪問頁面
        "CurrentTime":  「1517798922201」,  //事件發生的時間
        "ExtraInfo":  {
         }    //擴展字段,對具體業務分析的傳參
    }]
}
複製代碼

以上就是咱們如今約定好了的通用的事件採集的接口,所傳的參數基本上會根據採集事件的不一樣而發生變化。可是在用戶的整一個訪問行爲中,用戶的設備是不會變化的,若是你想採集設備信息能夠從新約定一個接口,在整個採集開始以前發送設備信息,這樣能夠避免在事件採集接口上重複採集固定數據。git

{
    "header":{ // HTTP 頭部
          "X-Device-Id"  :"550e8400-e29b-41d4-a716-446655440000"  ,      //  設備id
    },
    "body":{ // HTTP Body體
              "DeviceType":  "web" ,   //設備類型
             "ScreenWide"  :  768 , //  屏幕寬
             "ScreenHigh":  1366 , //  屏幕高
             "Language":    "zh-cn"  //語言
    }
}
複製代碼

業務方經過什麼方式來調用咱們的採集腳本

埋點應該讓調用的業務方,儘量少有工做量,最好是什麼都不用作,😁,可是實現起來有點難額。咱們採用的方案是讓業務方在代碼裏經過 script 腳原本引用咱們的 SDK ,業務方只要配置一些須要的參數進行埋點定製(👆咱們講到過的無埋點的流量控制),而後什麼都不作就能夠進行基礎數據的採集。github

(function() {
			var collect = document.createElement('script');
			collect.type = 'text/javascript';
			collect.async = true;
			collect.src =  'http://collect.trc.com/index.js';
			var s = document.getElementsByTagName('script')[0];
			s.parentNode.insertBefore(collect, s);
	})();


//用戶自定義要進行無埋點採集的元素,若是不進行無埋點採集,能夠不配置
 var _XT = [];
_XT.push(['Target','div']);
複製代碼

手動埋點:SDK

若是業務方須要採集更多業務定製的數據,能夠調用咱們暴露出的方法進行採集 web

//自定義事件
sdk.dispatch('customEvent',{extraInfo:'自定義事件的額外信息'})
複製代碼

遊客與用戶關聯

咱們使用 userId 來作用戶標識,同一個設備的用戶,從遊客用戶切換到登陸用戶,若是咱們要把他們關聯起來,須要有一個設備Id 作關聯json

web 設備Id

用戶經過瀏覽器來訪問 web 頁面,設備Id須要存儲在瀏覽器上,同一個用戶訪問不一樣的業務方網站,設備Id要保持同樣。web 變量存儲,咱們第一時間想到的就是 cookie,sessionStorage,localStorage,可是這3種存儲方式都和訪問資源的域名相關。咱們總不能每次訪問一個網站就新建一個設備指紋吧,因此咱們須要經過一個方法來跨域共享設備指紋後端

咱們想到的方案是,經過嵌套 iframe 加載一個靜態頁面,在 iframe 上加載的域名上存儲設備id,經過跨域共享變量獲取設備id,共享變量的原理是採用了iframe 的 contentWindow通信,經過 postMessage 獲取事件狀態,調用封裝好的回調函數進行數據處理具體的實現方式

//web 應用,經過嵌入 iframe 進行跨域 cookie 通信,設置設備id,
    collect.setIframe = function () {
        var that = this
        var iframe = document.createElement('iframe')
        iframe.id = "frame",
        iframe.src = 'http://collectiframe.trc.com' // 配置域名代理,目的是讓開發測試生產環境代碼一致
        iframe.style.display='none' //iframe 設置的目的是用來生成固定的設備id,不展現
        document.body.appendChild(iframe)

        iframe.onload = function () {
                iframe.contentWindow.postMessage('loaded','*');
        }

        //監聽message事件,iframe 加載完成,獲取設備id ,進行相關的數據採集
        helper.on(window,"message",function(event){
            that.deviceId = event.data.deviceId

            if(event.data && event.data.type == 'loaded'){
                that.sendDevice(that.getDevice(), that.deviceUrl);
                setTimeout(function () {
                    that.send(that.beforeload)
                    that.send(that.loaded)
                },1000)
            }
        })
    }
複製代碼

iframe 與 SDK 通信

function receiveMessageFromIndex ( event ) {
    getDeviceInfo() // 獲取設備信息
    var data =  {
            deviceId: _deviceId,
            type:event.data
    }

    event.source.postMessage(data, '*'); // 將設備信息發送給 SDK
}

//監聽message事件
if(window.addEventListener){
        window.addEventListener("message", receiveMessageFromIndex, false);
}else{
        window.attachEvent("onmessage", receiveMessageFromIndex, false)
複製代碼

若是你想知道能夠看個人另外一篇博客 web 瀏覽器指紋跨域共享

單頁面應用:如今流行的單頁面應用和普通 web 頁面的數據採集是否有差別

咱們知道單頁面應用都是無刷新的頁面加載,因此咱們在頁面跳轉的處理和咱們的普通的頁面會有所不一樣。單頁面應用的路由插件運用了 window 自帶的無刷新修改用戶瀏覽記錄的方法,pushState 和 replaceState。

window 的 history 對象 提供了兩個方法,可以無刷新的修改用戶的瀏覽記錄,pushSate,和 replaceState,區別的 pushState 在用戶訪問頁面後面添加一個訪問記錄, replaceState 則是直接替換了當前訪問記錄,因此咱們只要改寫 history 的方法,在方法執行前執行咱們的採集方法就能實現對單頁面應用的頁面跳轉事件的採集了

// 改寫思路:拷貝 window 默認的 replaceState 函數,重寫 history.replaceState 在方法裏插入咱們的採集行爲,在重寫的 replaceState 方法最後調用,window 默認的 replaceState 方法

    collect = {}
    collect.onPushStateCallback : function(){}  // 自定義的採集方法

    (function(history){
        var replaceState = history.replaceState;   // 存儲原生 replaceState
        history.replaceState = function(state, param) {     // 改寫 replaceState
           var url = arguments[2];
           if (typeof collect.onPushStateCallback == "function") {
                 collect.onPushStateCallback({state: state, param: param, url: url});   //自定義的採集行爲方法
           }
           return replaceState.apply(history, arguments);    // 調用原生的 replaceState
        };
     })(window.history);
複製代碼

這塊介紹起來也比較的複雜,若是你想了解更多,能夠看個人另外一篇博客你須要知道的單頁面路由實現原理

混合應用:app 與 h5 的混合應用咱們要怎麼進行通信

如今大部分的應用都不是純原生的應用, app 與 h5 的混合的應用是如今的一種主流。

純 web 數據採集咱們考慮到前端存儲數據容易丟失,咱們在每一次事件觸發的時候都用採集接口傳輸採集到的數據。考慮到如今不少用戶的手機會有流量管家的軟件監控,若是在 App 中 h5 仍是採集到數據就傳輸給服務端,頗有可能會讓流量管家檢測到,給用戶報警,從而使得用戶再也不信任你的 App , 因此咱們在用戶操做的時候將數據傳給 app 端,存儲到 app。用戶切換應用到後臺的時候,經過 app 端的 SDK 打包傳輸到服務器,咱們給 app 提供的方法封裝了一個適配器

// app 與 h5 混合應用,直接將數信息發給 app
collect.saveEvent = function (jsonString) {

    collect.dcpDeviceType && setTimeout(function () {
        if(collect.dcpDeviceType=='android'){
            android.saveEvent(jsonString)
        } else {
            window.webkit && window.webkit.messageHandlers ? window.webkit.messageHandlers.nativeBridge.postMessage(jsonString) : window.postBridgeMessage(jsonString)
        }

    },1000)
	}
複製代碼

實現思路

經過上面幾個問題的思考,咱們對埋點的實現大體已經有了一些想法,咱們使用思惟導圖來還原下咱們即將要作的事情,圖片記得放大看哦,過小了可能看不清。

  1. 咱們須要暴露給業務方調用的方法

暴露方法
2. 咱們須要處理的事件類型
監聽事件
3. SDK 的基本實現思路
實現邏輯

咱們來看下幾個核心代碼的實現

工具方法

咱們定義了幾個工具方法,提升開發的幸福指數 😝

var helper = {};

    // 生成一個惟一的標識,pageSessionId (用這個變量來關聯開始加載、加載完成、離開頁面的事件,計算出頁面加菜時間,停留時間)
    helper.uuid = function(){}

    // 元素綁定事件監聽,兼容瀏覽器到IE8
    helper.on = function(){}

    //元素移除事件監聽的適配器函數,兼容瀏覽器到IE8
    helper.remove = function(){}

    //將json轉爲字符串,事件傳輸的參數類型轉化
    helper.changeJSON2Query = function(){}

    //將相對路徑解析成文檔全路徑
    helper.normalize = function(){}
複製代碼

採集邏輯

var collect = {
        deviceUrl:'http://collect.trc.com/rest/collect/device/h5/v1',
        eventUrl:'http://collect.trc.com/rest/collect/event/h5/v1',
        isuploadUrl:'http://collect.trc.com/rest/collect/isupload/app/v1',
        parmas:{ ExtraInfo:{} },
        device:{}
    };

    //獲取埋點配置
    collect.setParames = function(){}

    //更新訪問路徑及頁面信息
    collect.updatePageInfo = function(){}

    //獲取事件參數
    collect.getParames = function(){}

    //獲取設備信息
    collect.getDevice = function(){}

    //事件採集
    collect.send = function(){}

    //設備採集
    collect.sendDevice = function(){}

    //判斷才否採集,埋點採集的開關
    collect.isupload = function(){

        1. 判斷是否採集,不採集就註銷事件監聽(項目中區分遊客身份和用戶身份的採集狀況,這個方法會被判斷兩次)
        2. 採集則判斷是否已經採集過
            a.已經採集過不作任何操做
            b.沒有采集過添加事件監聽
        3. 判斷是 混合應用仍是純 web 應用
            a.若是是web 應用,調用 collect.setIframe 設置 iframe
            b.若是是混合應用 將開始加載和加載完成事件傳輸給 app
    }

    //點擊事件處理函數
    collect.clickHandler = function(){}

    //離開頁面的事件處理函數
    collect.beforeUnloadHandler = function(){}

    //頁面回退事件處理函數
    collect.onPopStateHandler = function(){}

    //系統事件初始化,註冊離開事件,瀏覽器後退事件
    collect.event = function(){}

    //獲取記錄開始加載數據信息
    collect.getBeforeload = function(){}

    //存儲加載完成,獲取設備類型,記錄加載完成信息
    collect.onload = function(){

        1. 判斷cookie是否有存設備類型信息,有表示混合應用
        2. 採集加載完成時間等信息
        3. 調用 collect.isupload 判斷是否進行採集
    }

    //web 應用,經過嵌入 iframe 進行跨域 cookie 通信,設置設備id
    collect.setIframe = function(){}

    //app 與 h5 混合應用,直接將數信息發給 app,判斷設備類型作原生方法適配器
    collect.saveEvent = function(){}

    //採集自定義事件類型
    collect.dispatch = function(){}

    //將參數 userId 存入sessionStorage
    collect.storeUserId = function(){}

    //採集H5信息,若是是混合應用,將採集到的信息發送給 app 端
    collect.saveEventInfo = function(){}

    //頁面初始化調用方法
    collect.init = function(){

        1. 獲取開始加載的採集信息
        2. 獲取 SDK 配置信息,設備信息
        3. 改寫 history 兩個方法,單頁面應用頁面跳轉前調用咱們本身的方法
        4. 頁面加載完成,調用 collect.onload 方法

    }


    collect.init(); // 初始化

    //暴露給業務方調用的方法
    return {
        dispatch:collect.dispatch,
        storeUserId:collect.storeUserId,
    }
複製代碼

擴展

👆就是我這段時間研究的成果了,代碼的篇幅比較長,就不放在博客裏了,感興趣的同窗能夠加我微信進行交流,或則在文章下面留言,也歡迎你們給我提意見,幫忙優化 😝。

web 瀏覽器指紋跨域共享

你須要知道的單頁面路由實現原理

數據埋點是什麼?設置埋點的意義是什麼?

數據採集與埋點

美團點評前端無痕埋點實踐

如何清楚易懂的解釋「UV和PV"的定義

相關文章
相關標籤/搜索