app嵌入的H5頁面的數據埋點總結


好久沒寫博客了,大半年時間花費在了許多雜事上。

最近1個月專門為H5頁面的app開發了一些埋點功能,主要是考慮到以后的可復制性和通用型,由於不是前端開發出身,相對來說還是比較簡陋的。

正題開始:H5頁面的埋點主要涉及到的元素有a標簽,button按鈕,以及form表單的提交。

目前實現的功能基本還都是代碼埋點的方式,但是相對來說比較簡潔了,我這里主要是針對a標簽和button的事件埋點。

第一個js腳本 bigdataIndex.js

var _qjmap = _qjmap || [];
var _bigdataDomain = "http://localhost:8082/"
_qjmap.push(['tenantCode', 'xxxxx000001']);

(function () {

    //監控a標簽的單擊事件
    var a = document.getElementsByTagName("a");
    for(var i =0; i<a.length; i++){
        a[i].onclick = (function(i){
            return function(){
                var data = this.getAttribute('data-bigdata');
                var href = this.getAttribute('href');

                if(data){

                    var prevEvent = sessionStorage.getItem("event") || '';
                    sessionStorage.setItem("prevEvent",prevEvent);

                    if(data.indexOf(':') != -1){
                        var event = data.split(':')[0] || '';
                        var eventData = data.split(':')[1]|| '';
                        sessionStorage.setItem("event", event);
                        sessionStorage.setItem("eventData", eventData);
                    }else {
                        sessionStorage.setItem("event", data);
                    }
                }
                if(href){
                    sessionStorage.setItem('href', href);
                }
                send();
            }
        })(i);
    }

    function send(){
        var ma = document.createElement('script');
        ma.type = 'text/javascript';
        ma.async = true;
        ma.src = _bigdataDomain + "js/qjdata.js?v=1";
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(ma, s);
    }


    //在按鈕事件中調用該方法
    function btnEventSend(event,data){
        sessionStorage.setItem("prevEvent",sessionStorage.getItem("prevEvent"));
        sessionStorage.setItem("event", event);
        sessionStorage.setItem("eventData", data);
        send();
    }

})();


//為button時候,手工觸發
function bigdataBtnEventSend(event, data){
    sessionStorage.setItem("prevEvent",sessionStorage.getItem("prevEvent"));
    sessionStorage.setItem("event", event);
    sessionStorage.setItem("eventData", data);
    var ma = document.createElement('script');
    ma.type = 'text/javascript';
    ma.async = true;
    ma.src = _bigdataDomain + "js/qjdata.js?v=1";
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(ma, s);
}

說明:

_qjmap為全局變量:添加的tenantCode為租戶的id,如果統計多個應用,可以通過這個字段來區分。
_bigdataDomain為第一個js腳本下載第二個js腳本的域名地址。

接下來在一個閉包函數中監聽了a標簽的單擊事件, 如果觸發,則獲取a標簽data-bigdata屬性、href屬性,
並且從sessionStorage中獲取對應的事件,保存到sessionStorage中作為上個事件,以便下個事件獲取。

如果該事件帶有具體的數據,則必須使用冒號放到事件類型后面,方便后面的分拆保存。
例如:用戶點擊商品列表中的某個商品,跳轉到商品詳情頁面中。
則設置a標簽的屬性為 <a href="item/123456.htm" data-bigdata="viewGoods:123456">蘋果</a>
經過如上設置,在該頁面中嵌入上面的bigdataIndex.js,則event為viewGoods, eventData為123456(這里123456假設為商品的id)。

接下來最重要的就是send()方法:
    function send(){
        var ma = document.createElement('script');
        ma.type = 'text/javascript';
        ma.async = true;
        ma.src = _bigdataDomain + "js/qjdata.js?v=1";
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(ma, s);
    }

該方法中動態創建一個腳本,並且從遠程服務器上下載qjdata.js文件加載到頁面中,此處使用的是異步加載的方式。

qjdata.js

(function(){

    function getOsInfo() { // 獲取當前操作系統
        var os;
        if (navigator.userAgent.indexOf('Android') > -1 || navigator.userAgent.indexOf('Linux') > -1) {
            os = 'Android';
        } else if (navigator.userAgent.indexOf('iPhone') > -1) {
            os = 'IOS';
        } else if (navigator.userAgent.indexOf('Windows Phone') > -1) {
            os = 'WP';
        } else {
            os = 'none';  //未知
        }
        return os;
    }

    function getOSVersion() { // 獲取操作系統版本
        var OSVision = '1.0';
        var u = navigator.userAgent;
        var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //Android
        var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios終端
        if (isAndroid) {
            OSVision = navigator.userAgent.split(';')[1].match(/\d+\.\d+/g)[0];
        }
        if (isIOS) {
            OSVision = navigator.userAgent.split(';')[1].match(/(\d+)_(\d+)_?(\d+)?/)[0];
        }
        return OSVision;
    }

    function getDeviceType() { // 獲取設備類型
        var deviceType;
        var sUserAgent = navigator.userAgent.toLowerCase();
        var bIsIpad = sUserAgent.match(/(ipad)/i) == "ipad";
        var bIsIphoneOs = sUserAgent.match(/iphone os/i) == "iphone os";
        var bIsMidp = sUserAgent.match(/midp/i) == "midp";
        var bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) == "rv:1.2.3.4";
        var bIsUc = sUserAgent.match(/ucweb/i) == "ucweb";
        var bIsAndroid = sUserAgent.match(/android/i) == "android";
        var bIsCE = sUserAgent.match(/windows ce/i) == "windows ce";
        var bIsWM = sUserAgent.match(/windows mobile/i) == "windows mobile";

        if (!(bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM)) {
            deviceType = 'PC'; //pc
        } else if (bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM) {
            deviceType = 'phone'; //phone
        } else if (bIsIpad) {
            deviceType = 'ipad'; //ipad
        } else {
            deviceType = 'none';  //未知
        }
        return deviceType;
    }

    function getOrientationStatus() { // 獲取橫豎屏狀態
        var orientationStatus;
        if (window.screen.orientation.angle == 180 || window.screen.orientation.angle == 0) { // 豎屏
            orientationStatus = '豎屏';
        }
        if (window.screen.orientation.angle == 90 || window.screen.orientation.angle == -90) { // 橫屏
            orientationStatus = '橫屏';
        }
        return orientationStatus;
    }

    function getNetWork() { // 獲取網絡狀態
        var netWork;
        switch (navigator.connection.effectiveType) {
            case 'wifi':
                netWork = 'wifi'; // wifi
                break;
            case '5g':
                netWork = '5G'; // 5g
                break;
            case '4g':
                netWork = '4G'; // 4g
                break;
            case '2g':
                netWork = '2G'; // 2g
                break;
            case  '3g':
                netWork = '3G'; // 3g
                break;
            case  'ethernet':
                netWork = 'ethernet'; // 有線
                break;
            case  'default':
                netWork = 'none'; // 未知
                break;
        }
        return netWork;
    }

    //生成唯一Id
    function generateUUID() {
        var d = new Date().getTime();
        if (window.performance && typeof window.performance.now === "function") {
            d += performance.now(); //use high-precision timer if available
        }
        var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = (d + Math.random() * 16) % 16 | 0;
            d = Math.floor(d / 16);
            return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
        });
        return uuid;
    }


    //判斷用戶是否存在,不存在則生成唯一號
    function getUUID() {
        var uuid = localStorage.getItem("bigdata_uuid");
        if(uuid == '' || uuid == null){
            uuid = generateUUID();
            localStorage.setItem("bigdata_uuid", uuid);
        }
        return uuid;
    }

    //獲取cookie
    function getCookie(sName)
    {
        var aCookie = document.cookie.split("; ");
        var returnValue = "";
        for (var i=0; i < aCookie.length; i++)
        {
            var key = sName + '=';
            if(aCookie[i].indexOf(key) != -1){
                returnValue = unescape(aCookie[i].substr(sName.length + 1));
            }
        }
        return returnValue;
    }

    function setCookie(name,value)
    {
        var Days = 30;
        var exp = new Date();
        exp.setTime(exp.getTime() + Days*24*60*60*1000);
        document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString();
    }

    function clearCookie(name){
        setCookie(name,'');
    }

    //頁面離開時觸發
    // window.onbeforeunload = function(){
    //     params.intime = localStorage.getItem("bigdata_intime");
    //     params.duration = getDuration();
    //     params.outtime =  Date.now();
    // }

    //記錄的參數值
    var params = {};
    params.osInfo = getOsInfo();
    params.osVersion = getOSVersion();
    params.deviceType = getDeviceType();
    params.webType = getNetWork();
    params.orientationStatus = getOrientationStatus();
    params.deviceId = getUUID();
    params.actionTime = Date.now();
    var intime = sessionStorage.getItem("bigdata_intime");
    params.previousUrl_intime = intime || '' ;
    params.duration = intime == null ? 0 : Date.now() - intime;
    sessionStorage.setItem("bigdata_intime", Date.now().toString());


    params.event = sessionStorage.getItem("event");
    params.preEvent = sessionStorage.getItem("prevEvent");
    params.eventData = sessionStorage.getItem("eventData");

    sessionStorage.removeItem("eventData");
    sessionStorage.removeItem('href');
    params.loginName = getCookie("_mall_newMobile_username");
    params.userCode = getCookie("userId");
    params.targetUrl = sessionStorage.getItem('href');
    // params.phoneType = getPhoneTypeAndVersion().split("#")[0];
    // params.phoneVersion = getPhoneTypeAndVersion().split("#")[1];


    //document對象元素
    if(document){
        params.currUrl = document.URL || '';             //當前URL地址
        params.prevUrl = document.referrer || '';     //上一路徑

        params.loginIp = document.domain || '';         //獲取域名
        params.title = document.title || '';        //標題
    }

    //window對象元素
    if(window && window.screen){
        params.height = window.screen.height || 0;  //獲取顯示屏信息
        params.width = window.screen.width || 0;
        params.colorDepth = window.screen.colorDepth || 0;
    }

    //navigator對象數據
    if(navigator){
        params.lang = navigator.language || '';  //獲取語言的種類
        if(navigator.geolocation) {
            params.longitude = sessionStorage.getItem('longitude');
            params.latitude = sessionStorage.getItem('latitude');
        }
    }

    //解析_qjmap配置
    if(_qjmap){
        for(var i in _qjmap){

            console.log(_qjmap[i]);

            switch (_qjmap[i][0]){
                case 'tenantCode':
                    params.tenantCode = _qjmap[i][1];
                    break;
                default:
                    break;
            }
        }
    }
    
    

    //拼接字符串
    var args = '';
    for(var i in params){
        if(args != ''){
            args += '\x01';
        }
        var p = params[i];
        if(p != null ){
            p = p.toString().replace(new RegExp("=",'g'),"%3D");
        }
        args += i + '=' + p;  //將所有獲取到的信息進行拼接
    }

    //通過偽裝成Image對象,請求后端腳本
    var img = new Image(1, 1);
    var src = 'http://localhost:8082/bigdata/qjdata.gif?args=' + encodeURIComponent(args);
    // alert("請求到的后端腳本為" + src);
    img.src = src;

})();

 qjdata.js說明:

在該js中主要是拼接數據,並通過構造虛擬的image的方式,發送到后台。

params對象包含了很多用戶訪問的設備、網絡、以及自定義的信息。這里不一一介紹了。

部分數據需要提前存放到sessionStorage中來獲取,比如經緯度等。

最后將params對象轉化為字符串,通過image的參數方式傳遞到后台。

 

后台java代碼實現:

package com.king;


import org.apache.commons.lang3.StringUtils;
import org.jboss.logging.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;

@Controller
@RequestMapping("/bigdata")
public class BigdataController {

    private static final Logger log = Logger.getLogger(BigdataController.class);

    @RequestMapping(value = "qjdata.gif")
    public void dataCollection(String args, HttpServletResponse response){
        if(StringUtils.isNotBlank(args)){
            String[] arr = args.split("\001");
            for(String kv :arr){
                String[] kvmap = kv.split("=");
                if(kvmap.length > 1 && !kv.split("=")[1].equals("null")){
                    String key = kv.split("=")[0];
                    String value = kv.split("=")[1];

                    //針對登錄賬號解密
                    if(key.equals("loginName")){
                        try {
                            value = value.replaceAll("%3D","=");
                            value = value.substring(1,value.length()-1);
                            value = ThreeDES.decryptThreeDESECB(value, ThreeDES.LoginDesKey);
                        }catch (Exception e){
                            log.error(e);
                        }
                    }
                    System.out.println(key + "==>" + value);
                }else{
                    System.out.println(kv.split("=")[0] + "==>" + "");
                }
            }
        }
        System.out.println("=========================================");
    }
}

說明: 由於用戶的賬號保存在sessionStorage中時候進行了加密,所以只能在這里進行解密操作。沒有加密的可以不需要這段。

最后輸出的信息,即為用戶的訪問行為信息,下面為最終的輸出信息供參考:

用戶登錄:

從登錄頁(login)到商品分類頁面(pageClass):

從商品分類頁(pageClass)到首頁(pageHome)

瀏覽商品詳情頁(viewGoods),這里的201807271813151即為商品的id號。

 

 ⚠️最后注意點:

    關於用戶的唯一id問題,由於設備的Id號現在很多被屏蔽了,不好獲取。

    在用戶未登錄的時候,通過js自動生成了一串UUID,然后保存到localStorage中,這個除非手工清除了緩存,否則會一直保存在本地。

    當用戶登錄后,可以查看到用戶登錄的賬號,所以在后續數據清洗時,可以根據有賬號的uuid去匹配無賬號的uuid,達到修復未登錄用戶的賬號。

 

 



 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM