乐趣区

大厂经验一一套-Web-自动曝光埋点技术方案

前言:更多关于数智化转型、数据中台内容可扫码加群一起探讨

阿里云数据中台官网 https://dp.alibaba.com/index


(作者:qingliang_hu)

关联阅读:大厂经验(二):多端可视化埋点解决方案

前言

首先在介绍这套方案前,咱们还是简单地普及一下“埋点”这个名词。

埋点是指在各个终端(如网页、小程序)中收集一些关键访问数据并将数据发送到日志服务器,以供后续的数据分析。

如下笔者在写这篇文章之前对公司内的一些业务做的访谈调研记录,可以发现埋点在实际业务中大概会有这些作用:

  • “采集并针对性做些投放调整,比如会员权益的展现、影院场次的优先露出、用户想看和看过的互动等”
  • “做新春大盘活动的时候,某些模块曝光次数不够高,运营会调整相应策略”
  • “个性化推荐,根据曝光和点击情况推荐用户数据”
  • “我们这边埋点数据对算法开发、模型训练、效果评估起决定性作用”
  • “观察用户逛会场深度的分布,做相应决策”

在简单介绍今天的主角——埋点的定义后,接下来,我们一起来研究一下自动曝光这件事情。

什么是自动曝光?

自动曝光是指按照埋点规范在页面上进行一个简单的声明式埋点,第三方采集 SDK 会根据埋点信息自动的采集元素曝光信息的一种方式。

如下图,页面滑动过程中 A、B、C、D 模块出现在视口内采集 SDK 会自动上报埋点日志:

典型轮播图场景,图片滚动出现后需要打曝光日志:

自动曝光的实现难点?

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

技术原理

生命周期

技术细节
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 个元素做了曝光埋点,连续来回滚动,直到所有元素都完成曝光日志上报的性能对比:

额外收益

有了这个基础,做如下两件事会显得比较轻松了
1、可视化埋点
2、可视化分析

原文链接 >>
参考文献:
IntersectionObserver
IntersectionObserverPolyfill


数据中台是企业数智化的新基建,阿里巴巴认为数据中台是集方法论、工具、组织于一体的,“快”、“准”、“全”、“统”、“通”的智能大数据体系。目前正通过阿里云数据中台解决方案对外输出,包括零售、金融、互联网、政务等领域,其中核心产品有:

  • Dataphin,一站式、智能化的数据构建及管理平台;
  • Quick BI,随时随地 智能决策;
  • Quick Audience,全方位洞察、全域营销、智能增长;
  • Quick A+,跨多端全域应用体验分析及洞察的一站式数据化运营平台;

官方站点:
数据中台官网 https://dp.alibaba.com

退出移动版