揭開JS無埋點技術的神祕面紗

一、背景

相信很多人都接觸過**“埋點”這個概念,無論是前端還是後端開發,我們都可以使用這門技術來生產出一些運營性質的原始數據(接口耗時、程序安裝/啓動、用戶交互行爲等等),然後分析它們得到一些抽象指標(例如留存率、轉化率),進而決定產品運營或者代碼優化的方向。現在業界有許多比較知名數據平臺,比如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. 基礎代碼

和代碼埋點一樣,要讓“無埋點”工作起來,網頁裏也必須有一段“基礎代碼”。

<!-- 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採用了CustomElementShadowDOM,把可視化工具所有的功能都用自定義的Web Component實現了,雖然目前只有Chrome支持Web Component,但是真的有點叼。。這樣自定義的元素和交互不會對用戶的網頁DOM產生影響。當然,如果你的可視化工具實現做的很輕,比如只是將用戶的網頁放在一個iframe裏面,大部分交互都交給iframe的parent頁面去處理,那也可以在配置的時候,最小程度的破壞用戶的網頁了。

6. 配置工具中如何控制頁面的跳轉

當進入可視化配置狀態時,我們可以讓用戶點擊一個元素,然後彈一個對話框,讓用戶對這個元素進行配置。此時,如果這個元素本身的click行爲是頁面跳轉呢?我們應該怎麼處理?

這裏本質上是一個交互設計的問題。在可視化配置工具中,應該有兩種基本交互操作。一種是讓用戶選中某一個元素,進行配置;另一種,是讓用戶可以觸發頁面原有的行爲。

爲什麼要有第二種交互?因爲我們的工具肯定要支持用戶進行二級頁面的可視化配置對不對?或者說,用戶的頁面中可能會彈出一個對話框,對話框裏面有一個按鈕,用戶對監測這個按鈕,對它做配置,對不對?簡單來說,就是用戶頁面中原有的點擊行爲,可能會導致頁面結構產生變化,例如跳轉,頁面內彈出對話框等等。

那問題就好解了,除了點擊,再設計一種交互來支持用戶網頁中原有的點擊行爲不就好了。用“右鍵點擊”或者“按住shift+點擊”之類都可以。反正不要再和網頁默認的交互很容易產生衝突的方式就行。

最後再提一下,之前想很久沒有想明白,如何能夠能防止用戶點擊的時候頁面產生跳轉。後來才知道,DOM的事件流分三個階段:捕獲、目標、冒泡。所以爲了避免用戶的點擊產身頁面跳轉,給document在捕獲階段加一個listener,攔截掉這個事件的繼續分發就行了。

 

簡單的示例代碼如下:

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,實際上採集了頁面中所有頁面的點擊事件上報,然後在後臺再去根據用戶的配置計算轉化數量。這樣做的好處就是如果頁面變化後,用戶接到告警,修改了配置,那麼用於數據上報方案是全量的,所以平臺是由能力將過去的數據回溯出來的。而上面我們說的根據配置下發,查找監測指定元素,再上報數據的方案屬於按需上報,數據出現誤差是無法回溯的。不過全量上報數據大家也知道,太不友好了,這個數據量太大,不僅前端消耗資源多,如果爲了做數據回溯,後臺的存儲壓力也會加大,而存儲的數據大部分還是無效的,這個成本有點高了。

五、參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章