一、背景
相信很多人都接觸過“埋點”這個概念,無論是前端還是後端開發,我們都可以使用這門技術來生產出一些運營性質的原始資料(介面耗時、程式安裝/啟動、使用者互動行為等等),然後分析它們得到一些抽象指標(例如留存率、轉化率),進而決定產品運營或者程式碼優化的方向。現在業界有許多比較知名資料平臺,比如Google Analytics、Facebook Pixel、Mixpanel、GrowingIO、諸葛IO、TalkingData、神策資料等數不勝數一大票,這些平臺有單純做資料分析的,也有服務於特定領域例如廣告監測轉化的,都提供了多端(Android、iOS、Web、小程式、ReactNative)的埋點SDK和比較全面的BI服務。這一兩年,不少平臺都開始宣傳一種叫“無埋點”的技術,下面以Web端為例,揭開它的神祕面紗。
二、什麼是無埋點?
“無埋點”在國外一些平臺被叫做Codeless Tracking
,顧名思義就是可以寫“更少”的埋點程式碼。而“程式碼埋點”一般需要開發人員編寫程式碼,監聽某個html元素的產生的事件,然後呼叫上報資料的介面,傳送資料。而無埋點則可以由非技術人員(例如運營、產品),在視覺化的工具中作出配置,然後就可以將html元素中產生的行為上報到後臺。下面是Mixpanel平臺的視覺化工具的截圖。
在這個工具裡,需要首先輸入頁面的url,頁面載入完成後,會出現視覺化配置的工具條。點選建立事件,就可以進入元素選擇模式,用滑鼠點選頁面上的某個元素(例如button、a這些element),就可以在彈出的對話方塊裡面,設定這個事件的名稱(比如叫TEST
)。儲存這個配置之後,如果頁面在瀏覽器中被瀏覽,剛才配置的那個按鈕發生點選時,就會向後臺上報一個TEST
事件。我們還可以設定上報TEST
事件的時候,帶上一些屬性(properties),這些屬性同樣也是在頁面中用滑鼠去選擇,然後儲存起來的。
看到這裡,首先從產品層面上,我們比較具體的瞭解到“無埋點”到底是幹什麼的了,無埋點就是用視覺化工具配置頁面中需要被監測的元素,並設定這個元素產生行為的時候需要上報的資料。但是還有非常關鍵的一點必須提到,要讓“無埋點”工作起來,頁面裡面還是必須嵌入了一段JS SDK的基礎程式碼,只是不需要再去呼叫SDK具體的資料上報介面罷了。
所以,“無埋點”技術的關鍵是:
- 操作視覺化配置工具,儲存配置
- SDK基礎程式碼如何根據配置上報行為
下面介紹一下如何實現這兩個關鍵。
三、關鍵技術
1. 基礎程式碼
和程式碼埋點一樣,要讓“無埋點”工作起來,網頁裡也必須有一段“基礎程式碼”。
1 2 3 4 |
<!-- start Mixpanel --><script type="text/javascript">(function(e,a){if(!a.__SV){var b=window;try{var c,l,i,j=b.location,g=j.hash;c=function(a,b){return(l=a.match(RegExp(b+"=([^&]*)")))?l[1]:null};g&&c(g,"state")&&(i=JSON.parse(decodeURIComponent(c(g,"state"))),"mpeditor"===i.action&&(b.sessionStorage.setItem("_mpcehash",g),history.replaceState(i.desiredHash||"",e.title,j.pathname+j.search)))}catch(m){}var k,h;window.mixpanel=a;a._i=[];a.init=function(b,c,f){function e(b,a){var c=a.split(".");2==c.length&&(b=b[c[0]],a=c[1]);b[a]=function(){b.push([a].concat(Array.prototype.slice.call(arguments, 0)))}}var d=a;"undefined"!==typeof f?d=a[f]=[]:f="mixpanel";d.people=d.people||[];d.toString=function(b){var a="mixpanel";"mixpanel"!==f&&(a+="."+f);b||(a+=" (stub)");return a};d.people.toString=function(){return d.toString(1)+".people (stub)"};k="disable time_event track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config reset opt_in_tracking opt_out_tracking has_opted_in_tracking has_opted_out_tracking clear_opt_in_out_tracking people.set people.set_once people.unset people.increment people.append people.union people.track_charge people.clear_charges people.delete_user".split(" "); for(h=0;h<k.length;h++)e(d,k[h]);a._i.push([b,c,f])};a.__SV=1.2;b=e.createElement("script");b.type="text/javascript";b.async=!0;b.src="undefined"!==typeof MIXPANEL_CUSTOM_LIB_URL?MIXPANEL_CUSTOM_LIB_URL:"file:"===e.location.protocol&&"//cdn4.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//)?"https://cdn4.mxpnl.com/libs/mixpanel-2-latest.min.js":"//cdn4.mxpnl.com/libs/mixpanel-2-latest.min.js";c=e.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)}})(document,window.mixpanel||[]); mixpanel.init("46042714e64a7536dde6f02af1aec923");</script><!-- end Mixpanel --> |
上面是Mixpanel平臺的基礎程式碼,不同平臺家的這段基礎程式碼,大同小異,都是一段IIFE形式的、壓縮過的js程式碼,執行完成之後,在head裡面插入了一個新的script標籤,非同步去下載真正的核心SDK程式碼下來工作。所以並不是基礎程式碼可以根據配置上報行為,而是基礎程式碼會下載一段“更大”的SDK核心程式碼,這段程式碼才是SDK真正的功能實現。
這樣子做的好處是,基礎程式碼很短,載入的時候不會影響到網頁的效能,而且核心SDK程式碼的更新也不需要使用者去更新這段基礎程式碼。
2. 頁面的唯一標識
在配置元素行為的時候,需要唯一標識一個頁面,這樣才能保證A頁面的配置,不會下發給在B頁面,不會導致B頁面產生出A頁面裡配置的行為。在Web裡面標識頁面靠的是url,url由protocol、domain、port、path和引數組成,儲存配置的時候要將url的引數提出來再存。而url的引數位置是可以變化的,比如urlA(http://a.b.com/c.html?pa=1&pb=2
)和urlB(http://a.b.com/c.html?pb=2&pa=1
)雖然urlA !== urlB
,但是其實它們是一個頁面。
3. 元素的唯一標識
唯一標識頁面後,接下來就要唯一標識頁面裡面的元素,這樣才能保證A頁面中配置的元素A1可以被SDK找到,從而監聽它產生的事件。
在html裡面,元素是以DOM Tree組織的,如果沿著元素A1出發,一直向上記錄它的parent和它在parent中的index,直到根節點body,那麼就可以得到元素A1在DOM Tree中的唯一路徑。
html的元素還會擁有很多屬性,例如css class、id可以用來定位元素。通過Chrome開發者工具可以看到Mixpanel的視覺化工具在配置元素的時候,使用的是https://github.com/Autarc/optimal-select這個庫來生成element的唯一標識的。而Github上還有https://github.com/rowthan/whats-element這樣的庫,也可以生成元素在DOM Tree中的唯一標識。
此外,還有平臺在標識元素的時候,採用了xpath
,這也是一個思路。
4. 如何查詢元素
上面說到元素可以有唯一標識,那麼有了唯一標識,就可以利用它的原理,找到這個元素。有一個很好用的API是document.querySelector()
,這個API可以根據CSS選擇器找到對應的元素。此外,根據元素的標識方法,還可以使用document.getElementById()
、document.getElementByName()
來實現元素的查詢。
這裡需要重點強調的是,如果頁面在配置完成之後又發生了修改,導致DOM Tree發生變化,此時需要被監測的元素的唯一標識可能也會發生改變。很可能導致根據之前的配置無法找到該元素了,或者找到的並不是我們希望監測的元素,從而導致產生的事件數量發生比較明顯的變化。為了資料的穩定性和準確性,應該設有相應的監測告警處理這種case,並提示使用者去重新配置頁面。我個人認為這是無埋點最大的缺點。
5. 標記元素時的高亮效果和視覺化互動實現
這是一個比較細節的點,其實熟悉js的大牛們都知道,有無數種方式去實現滑鼠移動到元素上時的類hover
效果,點選元素後彈出一個對話方塊,讓使用者輸入配置的資訊也so easy。但是我想說的是,一旦我們採用向頁面中動態新增元素的方式去實現視覺化工具的互動介面,那麼有可能會破壞掉頁面原來的DOM Tree結構。從而導致生成元素唯一標識的時候出現誤差,所以這裡必須要好好處理,保證生成的元素標識不會受到影響。
我看到Mixpanel採用了CustomElement
和ShadowDOM
,把視覺化工具所有的功能都用自定義的Web Component
實現了,雖然目前只有Chrome支援Web Component
,但是真的有點叼。。這樣自定義的元素和互動不會對使用者的網頁DOM產生影響。當然,如果你的視覺化工具實現做的很輕,比如只是將使用者的網頁放在一個iframe
裡面,大部分互動都交給iframe的parent頁面去處理,那也可以在配置的時候,最小程度的破壞使用者的網頁了。
6. 配置工具中如何控制頁面的跳轉
當進入視覺化配置狀態時,我們可以讓使用者點選一個元素,然後彈一個對話方塊,讓使用者對這個元素進行配置。此時,如果這個元素本身的click
行為是頁面跳轉呢?我們應該怎麼處理?
這裡本質上是一個互動設計的問題。在視覺化配置工具中,應該有兩種基本互動操作。一種是讓使用者選中某一個元素,進行配置;另一種,是讓使用者可以觸發頁面原有的行為。
為什麼要有第二種互動?因為我們的工具肯定要支援使用者進行二級頁面的視覺化配置對不對?或者說,使用者的頁面中可能會彈出一個對話方塊,對話方塊裡面有一個按鈕,使用者對監測這個按鈕,對它做配置,對不對?簡單來說,就是使用者頁面中原有的點選行為,可能會導致頁面結構產生變化,例如跳轉,頁面內彈出對話方塊等等。
那問題就好解了,除了點選,再設計一種互動來支援使用者網頁中原有的點選行為不就好了。用“右鍵點選”或者“按住shift+點選”之類都可以。反正不要再和網頁預設的互動很容易產生衝突的方式就行。
最後再提一下,之前想很久沒有想明白,如何能夠能防止使用者點選的時候頁面產生跳轉。後來才知道,DOM的事件流分三個階段:捕獲、目標、冒泡。所以為了避免使用者的點選產身頁面跳轉,給document在捕獲階段加一個listener,攔截掉這個事件的繼續分發就行了。
簡單的示例程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 |
document.addEventListener('click', e => { // 如果是按住shift的點選,那麼保持原有的行為 if (e.shiftKey) { return; } // 如果是單純的點選,那麼攔截分發 e.preventDefault(); e.stopImmediatePropagation(); // 獲取元素的唯一標識,然後讓使用者進行配置等等 this._selectElement(e.target); }, true); // useCapture必須為true |
四、總結
可以看到“無埋點”並不是零侵入,使用者的網頁中依然需要載入SDK的程式碼(除非你是瀏覽器廠商,可以在載入網頁的時候,給網頁加inject基礎程式碼)。只是每一個行為事件的上報程式碼不需要開發人員手動編寫,而是由運營人員用視覺化工具配置,所以叫它“視覺化埋點”也許更加合適。我們知道資料採集是資料分析的基礎和先決條件,資料採集做不好,其他的東西都是空中樓閣。
這裡可以小結一下“無埋點”技術的優劣。無埋點的好處是技術成本低,對使用者非常友好,不需要重新部署,配置完成就可以生效。但是其缺點也非常明顯,不具有程式碼埋點的靈活性和深度,只能採集到使用者肉眼可見的資料,無法獲取記憶體裡的資料,同時也無法適應頁面結構的變化,所以在實際生產中,要選擇性地在合適的地方使用無埋點技術。
多扯一點產品設計和技術方案的選擇,產品上是否可以支援採集記憶體資料呢?當然可以,比如微信小程式的“自定義分析”,就可以支援上報頁面data
下面的屬性,這時雖然同樣是視覺化配置,運營人員肯定不會知道程式碼裡面的變數名字,必須得有開發人員參與配置才行。關於頁面結構發生變化之後的資料丟失,也是有方案可以破的。比如Mixpanel平臺的Codeless Tracking,實際上採集了頁面中所有頁面的點選事件上報,然後在後臺再去根據使用者的配置計算轉化數量。這樣做的好處就是如果頁面變化後,使用者接到告警,修改了配置,那麼用於資料上報方案是全量的,所以平臺是由能力將過去的資料回溯出來的。而上面我們說的根據配置下發,查詢監測指定元素,再上報資料的方案屬於按需上報,資料出現誤差是無法回溯的。不過全量上報資料大家也知道,太不友好了,這個資料量太大,不僅前端消耗資源多,如果為了做資料回溯,後臺的儲存壓力也會加大,而儲存的資料大部分還是無效的,這個成本有點高了。