大廠經驗(一):一套 Web 自動曝光埋點技術方案

阿里雲數據中臺官網 https://dp.alibaba.com/index


(作者:qingliang_hu)

關聯閱讀:大廠經驗(二):多端可視化埋點解決方案

前言

首先在介紹這套方案前,咱們還是簡單地普及一下“埋點”這個名詞。

埋點是指在各個終端(如網頁、小程序)中收集一些關鍵訪問數據並將數據發送到日誌服務器,以供後續的數據分析。

如下筆者在寫這篇文章之前對公司內的一些業務做的訪談調研記錄,可以發現埋點在實際業務中大概會有這些作用:

  • “採集並針對性做些投放調整,比如會員權益的展現、影院場次的優先露出、用戶想看和看過的互動等”
  • “做新春大盤活動的時候,某些模塊曝光次數不夠高,運營會調整相應策略”
  • “個性化推薦,根據曝光和點擊情況推薦用戶數據”
  • “我們這邊埋點數據對算法開發、模型訓練、效果評估起決定性作用”
  • “觀察用戶逛會場深度的分佈,做相應決策”

在簡單介紹今天的主角——埋點的定義後,接下來,我們一起來研究一下自動曝光這件事情。

什麼是自動曝光?

自動曝光是指按照埋點規範在頁面上進行一個簡單的聲明式埋點,第三方採集SDK會根據埋點信息自動的採集元素曝光信息的一種方式。

如下圖,頁面滑動過程中A、B、C、D模塊出現在視口內採集SDK會自動上報埋點日誌:
image.png

典型輪播圖場景,圖片滾動出現後需要打曝光日誌:
image.png

自動曝光的實現難點?

1、一般而言產品上會要求頁面上某個模塊一定面積連續一段時間出現在視口才是有效曝光(如30%、300ms)

2、性能,幾乎所有的第三方採集平臺都會在曝光埋點的說明文檔裏註明:“請不要配置過多的曝光埋點,這會嚴重影響你的頁面性能”

兩個埋點方式
HTML如下:

 

 

<title>輪播圖自動曝光埋點demo</title>
<style type="text/css">
ul {
  padding: 0;
}
.clear{
  clear:both;
  zoom: 1;
}
*, :after, :before {
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}
.clearfix:after {
  clear: both;
}
.clearfix:after,
.clearfix:before {
  display: table;
  content: "";
}
.promo-bd {
  margin: 0 auto;
  overflow: hidden;
  width: 520px;
}
.promo-bd .items-container {
  list-style: none;
  overflow: hidden;
  width: 2280px;
  left: 0px;
  opacity: 1;
  height: 280px;
}
.promo-bd .items-container .item {
  display: list-item;
  float: left;
  overflow: hidden;
  display: block; 
  visibility: visible;
  height: 100%;
}
.promo-bd .items-container .item a {
  display: inline-block;
  height: 100%;
}
.sld-ft-nav {
  text-align: center;
}
.sld-ft-nav li {
  display: inline-block;
  margin-left: 8px;
  border-radius: 10px;
  width: 20px;
  height: 20px;
  line-height: 20px;
  background-color: #ccc;
  color: #fff;
  font-size: 12px;
  cursor: pointer;
}
.sld-ft-nav li:first-child {
  margin-left: 0px;
}
.sld-ft-nav li.selector {
  background-color: #ff7300;
}


 

<div class="container">
  <div class="promo-bd">
    <div class="items-container clear">
      <div class="item" data-id="111">
        <a href="#">
          <img src="https://img.alicdn.com/tfs/TB1fEOLCrr1gK0jSZFDXXb9yVXa-520-280.jpg">
        </a>
      </div>
      <div class="item" data-id="112">
        <a href="#">
          <img src="//img.alicdn.com/tfs/TB1BrwUFuL2gK0jSZPhXXahvXXa-520-280.jpg_q90_.webp">
        </a>
      </div>
      <div class="item" data-id="113">
        <a href="#">
          <img border="0" src="//aecpm.alicdn.com/simba/img/TB183NQapLM8KJjSZFBSutJHVXa.jpg">
        </a>
      </div>
      <div class="item" data-id="114">
        <a href="#">
          <img border="0" src="//aecpm.alicdn.com/simba/img/TB1JNHwKFXXXXafXVXXSutbFXXX.jpg">
        </a>
      </div>
    </div>
    <ul class="promo-nav sld-ft-nav">
      <li class="dot selector" onclick="handlerClick(0)">1</li>
      <li class="dot" onclick="handlerClick(1)">2</li>
      <li class="dot" onclick="handlerClick(2)">3</li>
      <li class="dot" onclick="handlerClick(3)">4</li>
    </ul>
  </div>
</div>
<script type="text/javascript">
  let handlerLoop;
  let width = 520;
  function transform (num) {
    document.querySelector('.items-container').setAttribute('style', `
      transition-duration: 0.3s;
      transform: translate3d(-${width * num}px, 0px, 0px);
      backface-visibility: hidden;
      left: 0px;
      opacity: 1;
    `);
    document.querySelectorAll('li.dot').forEach(function(ele, i){
      ele.setAttribute('class', 'dot');
      if (i === num) {
        ele.setAttribute('class', 'dot selector');
      }
    });
  }
  function loop (n) {
    let num = n;
    handlerLoop = setInterval(function(){
      if (num === 3) {
        num = 0;
      } else {
        num++;  
      }
      transform(num);
    }, 1500);
  }
  loop(0);
  function handlerClick (index) {
    clearInterval(handlerLoop);
    transform(index);
    loop(index);
  }
</script>

 

方式1:在head頭部聲明式埋點

 

......
<meta name="auto-exp-track" 
      content='[{"logkey":"/banner.item.image","cssSelector":".item","props":["data-id"]}]' />

 

 

......

 

方式2:JS注入式埋點

 

......
  <script>
  var q = (window.tracksdk_queue.push || (window.tracksdk_queue.push = []));
  q.push({
    action: 'trackSdk.setMetaInfo',
    arguments: ['auto-exp-track',[{
      "logkey":"/banner.item.image",
      "cssSelector":".item",
      "props":["data-id"]
    }]]
  })
</sctipt>

 

 

......

 

以上兩種方式,埋點SDK均會判斷cssSelector=".name"的所有元素被曝光時,自動採集這個標籤的曝光信息,及當前元素上"data-id"等props參數打包在一起,以logkey=/banner.item.image的形式發出去。

如:https://tracker.xxx.com/banner.item.image

技術原理

生命週期
image.png

技術細節
1、初始化:DomReady後做監聽埋點配置變化(watchConfig)
2、watchConfig首次拿到埋點配置

①監聽dom變化(watchDOM)

  • 步驟一、使用MutationObserver或輪詢(瀏覽器最小化、瀏覽器後臺運行、tab未激活均會暫停輪詢,直到瀏覽器窗口再次激活)。MutationObserver監聽除['IFRAME', 'BODY', 'OBJECT', 'SCRIPT', 'NOSCRIPT','#text', 'LINK', 'STYLE']之外的所有節點增加監聽['class', 'style']屬性變化;
  • 步驟二、監聽到有dom變化後判斷當前元素的track-ae屬性是否有值,有則跳過,無則組裝參數對象並生成元素唯一HASH,同時設置符合條件的節點狀態:“status=init”,再push到_aeElementsHashMap中;
  • 步驟三、分發一個內部事件消息“_AE_DOM_CHANGE”,用於通知watchExposure。

②監聽曝光(watchExposureByIntersectionObserver)

  • 步驟一、監聽來自watchDOM分發的內部消息“_AE_DOM_CHANGE”,轉至步驟二;
  • 步驟二、使用IntersectionObserver包裝埋點配置,拿到回調後將節點帶入步驟三;
  • 步驟三、遍歷aeElementsHashMap,拿到“status===init && element from IntersectionObserver”後判斷被曝光的元素是否符合要求(默認按可視面積30%),符合條件的節點設置“status=exposure_start”並更新aeElementsHashMap;
  • 步驟四、在步驟三的基礎上setTimeout 300ms後用getBoundingClientRect拿到節點座標寬高信息再使用自定義交叉計算方法重新計算一遍交叉面積,如果依然超過30%的面積在視口內,那麼將符合條件的節點設置“status=exposure_complete”並更新aeElementsHashMap。分發日誌發送命令“AE_EXPOSURE_COMPLETE”,用於通知watchRecord;
  • 步驟五、修改已曝光元素dom錨點:track-ae="${HASH}"。

③監聽曝光(watchExposureByCustomIntersection)

  • 步驟一、監聽來自watchDOM分發的內部消息“_AE_DOM_CHANGE”,轉至步驟三;
  • 步驟二、監聽touchmove、scroll、resize三種事件回調函數做成throttle_handler_exposure,拿到回調進入步驟三;
  • 步驟三、遍歷aeElementsHashMap,拿到“status===init”的節點,然後用getBoundingClientRect獲取節點座標寬高計算出交叉面積,符合可視面積超過30%的節點設置“status=exposure_start”並更新aeElementsHashMap;
  • 步驟四、在步驟三的基礎上setTimeout 300ms後用getBoundingClientRect拿到節點座標寬高信息再使用自定義交叉計算方法重新計算一遍交叉面積,如- 果依然超過30%的面積在視口內,那麼 將符合條件的節點設置“status=exposure_complete”並更新aeElementsHashMap。分發日誌發送命令AE_EXPOSURE_COMPLETE,用於通知watchRecord;
  • 步驟五、修改已曝光元素dom錨點:track-ae="${HASH}"。

④監聽日誌發送命令(watchRecord)

  • 步驟一、監聽到“_AE_EXPOSURE_COMPLETE”消息後最多10個元素打包在一起發送日誌;
  • 步驟二、清理_aeElementsHashMap上下文;
  • 步驟三、給待曝光元素設置dom錨點:track-ae="${index}";

⑤watchConfig非首次拿到埋點配置
1、配置不爲空時do_reset,做兩件事:1.1、重置_aeElementsHashMap上下文;1.2、將符合條件的元素的track-ae屬性重置成同類節點索引值,以觸發下一輪曝光監聽。

2、配置爲空值時do_destroy,做三件事:2.1、銷燬_aeElementsHashMap上下文;2.2、移除所有監聽事件;2.3、清空所有track-ae錨點屬性。

效果

 

性能

筆者通過大量測試和線上優化,拿目前這套架構方案與GA做了一個對比,即對208個元素做了曝光埋點,連續來回滾動,直到所有元素都完成曝光日誌上報的性能對比:
image.png

額外收益

有了這個基礎,做如下兩件事會顯得比較輕鬆了
1、可視化埋點
2、可視化分析

原文鏈接>>
參考文獻:
IntersectionObserver
IntersectionObserverPolyfill

 

數據中臺是企業數智化的新基建,阿里巴巴認爲數據中臺是集方法論、工具、組織於一體的,“快”、“準”、“全”、“統”、“通”的智能大數據體系。目前正通過阿里雲數據中臺解決方案對外輸出,包括零售金融互聯網政務等領域,其中核心產品有:

官方站點:
數據中臺官網 https://dp.alibaba.com

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