創業公司作數據分析(三)用戶行爲數據採集系統 (轉)

http://blog.csdn.net/zwgdft/article/details/53542597html

做爲系列文章的第三篇,本文將重點探討數據採集層中的用戶行爲數據採集系統。這裏的用戶行爲,指的是用戶與產品UI的交互行爲,主要表如今Android App、IOS App與Web頁面上。這些交互行爲,有的會與後端服務通訊,有的僅僅引發前端UI的變化,可是不論是哪一種行爲,其背後老是伴隨着一組屬性數據。對於與後端發生交互的行爲,咱們能夠從後端服務日誌、業務數據庫中拿到相關數據;而對於那些僅僅發生在前端的行爲,則須要依靠前端主動上報給後端才能知曉。用戶行爲數據採集系統,即是負責從前端採集所需的完整的用戶行爲信息,用於數據分析和其餘業務。
  舉個例子,下圖所示是一次營銷活動(簡化版)的註冊流程。若是僅僅依靠後端業務數據庫,咱們只能知道活動帶來了多少新註冊用戶。而經過採集用戶在前端的操做行爲,則能夠分析出整個活動的轉化狀況:海報頁面瀏覽量—>>點擊」當即註冊」跳轉註冊頁面量—>>點擊「獲取驗證碼」數量—>>提交註冊信息數量—>>真實註冊用戶量。而前端用戶行爲數據的價值不只限於這樣的轉化率分析,還能夠挖掘出更多的有用信息,甚至能夠與產品業務結合,好比筆者最近在作的用戶評分系統,便會從用戶行爲中抽取一部分數據做爲評分依據。前端


  在早期的產品開發中,後端研發人員每人負責一個攤子,雖然也會作些數據採集的事情,可是基本上只針對本身的功能,各作各的。一般作法是,根據產品經理提出的數據需求,設計一個結構化的數據表來存儲數據,而後開個REST API給前端,用來上報數據;前端負責在相應的位置埋點,按照協商好的數據格式上報給後端。隨着業務的發展,這樣的作法暴露了不少問題,給先後端都帶來了混亂,主要表如今:前端四處埋點,上報時調用的API不統一,上報的數據格式不統一;後端數據分散在多個數據表中,與業務邏輯耦合嚴重。
  因而,咱們考慮作一個統一的用戶行爲數據採集系統,基本的原則是:統一上報方式、統一數據格式、數據集中存儲、儘量全量採集。具體到實現上,概括起來主要要解決三個問題:android

  • 採什麼。搞清楚須要什麼數據,抽象出一個統一的數據格式。
  • 前端怎麼採。解決前端如何有效埋點、全量採集的問題。
  • 後端怎麼存。解決數據集中存儲、易於分析的問題。

採什麼

  用戶在前端UI上的操做,大多數表現爲兩類:第一類,打開某個頁面,瀏覽其中的信息,而後點擊感興趣的內容進一步瀏覽;第二類,打開某個頁面,根據UI的提示輸入相關信息,而後點擊提交。其行爲能夠概括爲三種:瀏覽輸入點擊(在移動端,有時也表現爲滑動)。其中,瀏覽和點擊是引發頁面變化和邏輯處理的重要事件,輸入老是與點擊事件關聯在一塊兒。
  所以,瀏覽點擊即是咱們要採集的對象。對於瀏覽,咱們關注的是瀏覽了哪一個頁面,以及與之相關的元數據;對於點擊,咱們關注的是點擊了哪一個頁面的哪一個元素,與該元素相關聯的其餘元素的信息,以及相關的元數據。頁面,在Android與IOS上使用View名稱來表示,在Web頁面上使用URL(hostname+pathname)來表示。元素,使用前端開發中的UI元素id來表示。與元素相關聯的其餘元素信息,指的是與「點擊」相關聯的輸入/選擇信息,好比在上面的註冊頁面中,與「提交」按鈕相關聯的信息有手機號、驗證碼、姓名。元數據,是指頁面能提供的其餘有用信息,好比URL中的參數、App中跳轉頁面時傳遞的參數等等,這些數據每每都是很重要的維度信息。ios

  除了這些頁面中的數據信息,還有兩個重要的維度信息:用戶時間。用戶維度,用來關聯同一用戶在某個客戶端上的行爲,採用的方案是由後端生成一個隨機的UUID,前端拿到後本身緩存,若是是登陸用戶,能夠經過元數據中的用戶id來關聯;時間維度,主要用於數據統計,考慮到前端可能延遲上報,前端上報時會加上事件的發生時間(目前大多數正常使用的移動端,時間信息應該是自動同步的)。
  綜合起來,將前端上報的數據格式定義以下。uuid、event_time、page是必填字段,element是點擊事件的必填字段,attrs包含了上述的元數據、與元素相關聯的其餘元素的信息,是動態變化的。web

{
    "uuid": "2b8c376e-bd20-11e6-9ebf-525499b45be6",
    "event_time": "2016-12-08T18:08:12",
    "page": "www.example.com/poster.html",
    "element": "register",
    "attrs": {
        "title": "test",
        "user_id": 1234
    }
}

 

  而針對不一樣客戶端的不一樣事件,經過不一樣的REST API來上報,每一個客戶端只需調用與本身相關的兩個API便可。數據庫

REST API 說明
/user_action/web/pv 上報Web頁面的瀏覽事件
/user_action/ios/pv 上報IOS頁面的瀏覽事件
/user_action/android/pv 上報Android頁面的瀏覽事件
/user_action/web/click 上報Web頁面的點擊事件
/user_action/ios/click 上報IOS頁面的點擊事件
/user_action/android/click 上報Android頁面的點擊事件

 

前端怎麼採

  整理好數據格式和上報方式後,前端的重點工做即是如何埋點。傳統的埋點方式,就是在須要上報的位置組織數據、調用API,將數據傳給後端,好比百度統計、google analysis都是這樣作的。這是最經常使用的方式,缺點是須要在代碼裏嵌入調用,與業務邏輯耦合在一塊兒。近幾年,一些新的數據公司提出了「無埋點」的概念,經過在底層hook全部的點擊事件,將用戶的操做盡可能多的採集下來,所以也能夠稱爲「全埋點」。這種方式無需嵌入調用,代碼耦合性弱,可是會採集較多的無用數據,可控性差。通過一番調研,結合咱們本身的業務,造成了這樣幾點設計思路:json

  • hook底層的點擊事件來作數據上報,在上報的地方統一作數據整理工做。
  • 經過UI元素的屬性值來設置是否對該元素的點擊事件上報。
  • 經過UI元素的屬性值來設置元素的關聯關係,用於獲取上述的「與元素相關聯的其餘元素的信息」。

  咱們首先在Web的H5頁面中作了實踐,核心的代碼很簡單。第一,在頁面加載時綁定全部的click事件,上報頁面瀏覽事件數據。第二,經過user_action_id屬性來表示一個元素是否須要上報點擊事件,經過user_action_relation屬性來聲明當前元素被關聯到哪一個元素上面,具體代碼實現不解釋,很簡單。後端

$(d).ready(function() {
    // 頁面瀏覽上報
    pvUpload({page: getPageUrl()},
             $.extend({title: getTitle()}, getUrlParams()));

    // 綁定點擊事件
    $(d).bind('click', function(event) {
        var $target = $(event.target);

        // 查找是不是須要上報的元素
        var $ua = $target.closest('[user_action_id]');
        if ($ua.length > 0) {
            var userActionId = $ua.attr('user_action_id');
            var userActionRelation = $("[user_action_relation=" + userActionId + "]");
            var relationData = [];

            // 查找相關聯的元素的數據信息
            if (userActionRelation.length > 0) {
                userActionRelation.each(function() {
                    var jsonStr = JSON.stringify({
                            "r_placeholder_element": $(this).get(0).tagName,
                            'r_placeholder_text': $(this).text()
                        });
                    jsonStr = jsonStr.replace(/\placeholder/g, $(this).attr('id'));
                    jsonStr = JSON.parse(jsonStr);
                    relationData.push(jsonStr);
                });
            }

            // 點擊事件上報
            clickUpload({page: getPageUrl(), element: userActionId},
                        $.extend({title: getTitle()}, getUrlParams(), relationData));
        }
    });
});

  上述代碼能夠嵌入到任何HTML頁面,而後只要在對應的元素中進行申明就行了。舉個例子,緩存

<div>    
    <div>
        <textarea id="answer" cols="30" rows="10" user_action_relation="answer-submit"></textarea>
    </div>
    <button user_action_id="answer-submit">提 交</button>
</div>

 

 

後端怎麼存

  數據進入後臺後,首先接入Kafka隊列中,採用生產消費者模式來處理。這樣作的好處有:第一,功能分離,上報的API接口不關心數據處理功能,只負責接入數據;第二,數據緩衝,數據上報的速率是不可控的,取決於用戶使用頻率,採用該模式能夠必定程度地緩衝數據;第三,易於擴展,在數據量大時,經過增長數據處理Worker來擴展,提升處理速率。app


  除了前端上報的數據內容外,咱們還須要在後端加入一些其餘的必要信息。在數據接入Kafka隊列以前,須要加入五個維度信息:客戶端類型(Web/Android/IOS)、事件類型(瀏覽/點擊)、時間、客戶端IP和User Agent。在消費者Worker從Kafka取出數據後,須要加入一個名爲event_id的字段數據,具體含義等下解釋。所以,最後存入的數據格式便以下所示:

{
    "uuid": "2b8c376e-bd20-11e6-9ebf-525499b45be6",
    "event_time": "2016-12-08T18:08:12",
    "page": "www.example.com/poster.html",
    "element": "register",
    "client_type": 0,
    "event_type": 0,
    "user_agent": "Mozilla\/5.0 (Linux; Android 5.1; m3 Build\/LMY47I) AppleWebKit\/537.36 (KHTML, like Gecko) Version\/4.0 Chrome\/37.0.0.0 Mobile MQQBrowser\/6.8 TBS\/036887 Safari\/537.36 MicroMessenger\/6.3.31.940 NetType\/WIFI Language\/zh_CN",
    "ip": "59.174.196.123",
    "timestamp": 1481218631,
    "event_id": 12,
    "attrs": {
        "title": "test",
        "user_id": 1234
    }
}

 

  再來看event_id的含義。前端傳過來的一組組數據中,經過page和element能夠區分出到底是發生了什麼事件,可是這些都是前端UI的名稱,大部分是開發者才能看懂的語言,所以咱們須要爲感興趣的事件添加一個通俗易懂的名稱,好比上面的數據對應的事件名稱爲「在海報頁面中註冊」。將page+element、事件名稱進行關聯映射,而後將相應的數據記錄id做爲event id添加到上述的數據中,方便後期作數據分析時根據跟event id來作事件聚合。作這件事有兩種方式:一種是容許相關人員經過頁面進行配置,手動關聯;一種是前端上報時帶上事件名稱,目前這兩種方式咱們都在使用。
  最後,來看看數據存儲的問題。傳統的關係型數據庫在存儲數據時,採用的是行列二維結構來表示數據,每一行數據都具備相同的列字段,而這樣的存儲方式顯示不適合上面的數據格式,由於咱們沒法預知attrs中有哪些字段數據。象用戶行爲數據、日誌數據都屬於半結構化數據,所謂半結構化數據,就是結構變化的結構化數據WIKI中的定義),適合使用NoSQL來作數據存儲。咱們選用的是ElasticSearch來作數據存儲,主要基於這麼兩點考慮:

  • Elasticsearch是一個實時的分佈式搜索引擎和分析引擎,具備很強的數據搜索和聚合分析能力。
  • 在這以前咱們已經搭建了一個ELK日誌系統,能夠複用Elasticsearch集羣作存儲,也能夠複用Kibana來作一些基礎的數據分析可視化。

  Elasticsearch的使用方法能夠參考Elasticsearch使用總結一文,這裏不作過多講解。使用Elasticsearch來作數據存儲,最重要的是兩件事:創建Elasticsearch的映射模板、批量插入。Elasticsearch會根據插入的數據自動創建缺失的index和doc type,並對字段創建mapping,而咱們要作的建立一個dynamic template,告訴Elasticsearch如何自動創建,參考以下。批量插入,能夠經過Elasticsearch的bulk API輕鬆解決。

"user_action_record": {
    "order": 0,
    "template": "user_action_record_*",
    "settings": {

    },
    "mappings": {
        "_default_": {
            "dynamic_templates": [{
                "string_fields": {
                    "mapping": {
                        "type": "string",
                        "fields": {
                            "raw": {
                                "index": "not_analyzed",
                                "ignore_above": 256,
                                "type": "string"
                            }
                        }
                    },
                    "match_mapping_type": "string"
                }
            }],
            "properties": {
                "timestamp": {
                    "doc_values": true,
                    "type": "date"
                }
            },
            "_all": {
                "enabled": false
            }
        }
    }
}
相關文章
相關標籤/搜索