创业公司做数据分析(三)用户行为数据采集系统 (转)
http://blog.csdn.net/zwgdft/article/details/53542597
作為系列文章的第三篇,本文將重點探討數據采集層中的用戶行為數據采集系統。這里的用戶行為,指的是用戶與產品UI的交互行為,主要表現在Android App、IOS App與Web頁面上。這些交互行為,有的會與后端服務通信,有的僅僅引起前端UI的變化,但是不管是哪種行為,其背后總是伴隨著一組屬性數據。對于與后端發(fā)生交互的行為,我們可以從后端服務日志、業(yè)務數據庫中拿到相關數據;而對于那些僅僅發(fā)生在前端的行為,則需要依靠前端主動上報給后端才能知曉。用戶行為數據采集系統,便是負責從前端采集所需的完整的用戶行為信息,用于數據分析和其他業(yè)務。
??舉個例子,下圖所示是一次營銷活動(簡化版)的注冊流程。如果僅僅依靠后端業(yè)務數據庫,我們只能知道活動帶來了多少新注冊用戶。而通過采集用戶在前端的操作行為,則可以分析出整個活動的轉化情況:海報頁面瀏覽量—>>點擊”立即注冊”跳轉注冊頁面量—>>點擊“獲取驗證碼”數量—>>提交注冊信息數量—>>真實注冊用戶量。而前端用戶行為數據的價值不僅限于這樣的轉化率分析,還可以挖掘出更多的有用信息,甚至可以與產品業(yè)務結合,比如筆者最近在做的用戶評分系統,便會從用戶行為中抽取一部分數據作為評分依據。
??在早期的產品開發(fā)中,后端研發(fā)人員每人負責一個攤子,雖然也會做些數據采集的事情,但是基本上只針對自己的功能,各做各的。通常做法是,根據產品經理提出的數據需求,設計一個結構化的數據表來存儲數據,然后開個REST API給前端,用來上報數據;前端負責在相應的位置埋點,按照協商好的數據格式上報給后端。隨著業(yè)務的發(fā)展,這樣的做法暴露了很多問題,給前后端都帶來了混亂,主要表現在:前端四處埋點,上報時調用的API不統一,上報的數據格式不統一;后端數據分散在多個數據表中,與業(yè)務邏輯耦合嚴重。
??于是,我們考慮做一個統一的用戶行為數據采集系統,基本的原則是:統一上報方式、統一數據格式、數據集中存儲、盡可能全量采集。具體到實現上,歸納起來主要要解決三個問題:
- 采什么。搞清楚需要什么數據,抽象出一個統一的數據格式。
- 前端怎么采。解決前端如何有效埋點、全量采集的問題。
- 后端怎么存。解決數據集中存儲、易于分析的問題。
采什么
??用戶在前端UI上的操作,大多數表現為兩類:第一類,打開某個頁面,瀏覽其中的信息,然后點擊感興趣的內容進一步瀏覽;第二類,打開某個頁面,根據UI的提示輸入相關信息,然后點擊提交。其行為可以歸納為三種:瀏覽、輸入和點擊(在移動端,有時也表現為滑動)。其中,瀏覽和點擊是引起頁面變化和邏輯處理的重要事件,輸入總是與點擊事件關聯在一起。
??因此,瀏覽和點擊便是我們要采集的對象。對于瀏覽,我們關注的是瀏覽了哪個頁面,以及與之相關的元數據;對于點擊,我們關注的是點擊了哪個頁面的哪個元素,與該元素相關聯的其他元素的信息,以及相關的元數據。頁面,在Android與IOS上使用View名稱來表示,在Web頁面上使用URL(hostname+pathname)來表示。元素,使用前端開發(fā)中的UI元素id來表示。與元素相關聯的其他元素信息,指的是與“點擊”相關聯的輸入/選擇信息,比如在上面的注冊頁面中,與“提交”按鈕相關聯的信息有手機號、驗證碼、姓名。元數據,是指頁面能提供的其他有用信息,比如URL中的參數、App中跳轉頁面時傳遞的參數等等,這些數據往往都是很重要的維度信息。
??除了這些頁面中的數據信息,還有兩個重要的維度信息:用戶和時間。用戶維度,用來關聯同一用戶在某個客戶端上的行為,采用的方案是由后端生成一個隨機的UUID,前端拿到后自己緩存,如果是登錄用戶,可以通過元數據中的用戶id來關聯;時間維度,主要用于數據統計,考慮到前端可能延遲上報,前端上報時會加上事件的發(fā)生時間(目前大多數正常使用的移動端,時間信息應該是自動同步的)。
??綜合起來,將前端上報的數據格式定義如下。uuid、event_time、page是必填字段,element是點擊事件的必填字段,attrs包含了上述的元數據、與元素相關聯的其他元素的信息,是動態(tài)變化的。
?
??而針對不同客戶端的不同事件,通過不同的REST API來上報,每個客戶端只需調用與自己相關的兩個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都是這樣做的。這是最常用的方式,缺點是需要在代碼里嵌入調用,與業(yè)務邏輯耦合在一起。近幾年,一些新的數據公司提出了“無埋點”的概念,通過在底層hook所有的點擊事件,將用戶的操作盡量多的采集下來,因此也可以稱為“全埋點”。這種方式無需嵌入調用,代碼耦合性弱,但是會采集較多的無用數據,可控性差。經過一番調研,結合我們自己的業(yè)務,形成了這樣幾點設計思路:
- 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來擴展,提高處理速率。
??除了前端上報的數據內容外,我們還需要在后端加入一些其他的必要信息。在數據接入Kafka隊列之前,需要加入五個維度信息:客戶端類型(Web/Android/IOS)、事件類型(瀏覽/點擊)、時間、客戶端IP和User Agent。在消費者Worker從Kafka取出數據后,需要加入一個名為event_id的字段數據,具體含義等下解釋。因此,最后存入的數據格式便如下所示:
?
??再來看event_id的含義。前端傳過來的一組組數據中,通過page和element可以區(qū)分出究竟是發(fā)生了什么事件,但是這些都是前端UI的名稱,大部分是開發(fā)者才能看懂的語言,因此我們需要為感興趣的事件添加一個通俗易懂的名稱,比如上面的數據對應的事件名稱為“在海報頁面中注冊”。將page+element、事件名稱進行關聯映射,然后將相應的數據記錄id作為event id添加到上述的數據中,方便后期做數據分析時根據跟event id來做事件聚合。做這件事有兩種方式:一種是允許相關人員通過頁面進行配置,手動關聯;一種是前端上報時帶上事件名稱,目前這兩種方式我們都在使用。
??最后,來看看數據存儲的問題。傳統的關系型數據庫在存儲數據時,采用的是行列二維結構來表示數據,每一行數據都具有相同的列字段,而這樣的存儲方式顯示不適合上面的數據格式,因為我們無法預知attrs中有哪些字段數據。象用戶行為數據、日志數據都屬于半結構化數據,所謂半結構化數據,就是結構變化的結構化數據(WIKI中的定義),適合使用NoSQL來做數據存儲。我們選用的是ElasticSearch來做數據存儲,主要基于這么兩點考慮:
- Elasticsearch是一個實時的分布式搜索引擎和分析引擎,具有很強的數據搜索和聚合分析能力。
- 在這之前我們已經搭建了一個ELK日志系統,可以復用Elasticsearch集群做存儲,也可以復用Kibana來做一些基礎的數據分析可視化。
??Elasticsearch的使用方法可以參考Elasticsearch使用總結一文,這里不做過多講解。使用Elasticsearch來做數據存儲,最重要的是兩件事:建立Elasticsearch的映射模板、批量插入。Elasticsearch會根據插入的數據自動建立缺失的index和doc type,并對字段建立mapping,而我們要做的創(chuàng)建一個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}}} }?
總結
以上是生活随笔為你收集整理的创业公司做数据分析(三)用户行为数据采集系统 (转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux下查看tomcat的启动日志
- 下一篇: SQL查询交集、并集、差集