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

本文涉及的产品
智能数据建设与治理Dataphin,200数据处理单元
可视分析地图(DataV-Atlas),3 个项目,100M 存储空间
数据可视化DataV,5个大屏 1个月
简介: 超过10年互联网行业技术工作经验总结。

前言:更多关于数智化转型、数据中台内容可扫码加群一起探讨
668d7f5941782665ed1f41529db3eb677f4b9379.png
阿里云数据中台官网 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
数据中台钉钉群二维码2.jpg


相关实践学习
阿里云百炼xAnalyticDB PostgreSQL构建AIGC应用
通过该实验体验在阿里云百炼中构建企业专属知识库构建及应用全流程。同时体验使用ADB-PG向量检索引擎提供专属安全存储,保障企业数据隐私安全。
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
相关文章
|
消息中间件 SQL 关系型数据库
Web应用程序并发问题处理的一点小经验
Web应用程序并发问题处理的一点小经验
113 0
|
移动开发 前端开发 JavaScript
两年经验以下的web前端们需要注意什么?
前些年自己堆前端岗位的经验总结
148 0
|
Web App开发 JavaScript 前端开发
[深度好文]想成为一个高效的Web开发者吗?来看看大牛分享的经验吧~外加一些自己的理解
前言: 无意间浏览到此篇文章,发现这篇文章无论是对于新手程序员,还是学过几年的程序员,都是挺有帮助的。于是,在此分享,后面也有我自己的一些理解,希望能帮到更多的朋友。 作为一个软(ku)件(bi)工(de)程(ma)师(nong),你有没有觉得做什么事都没时间?没时间学习新东西,没时间去回顾、整理原来写的烂代码,没时间写单元测试,没时间给接管你项目的家伙写文档,没时间思考,没时间喘气,没!时!间! 额……如果你肯花点时间看看这篇文章,我相信你会明白应该把时间花在哪。
964 0
|
Web App开发 JavaScript 前端开发
对《30个提高Web程序执行效率的好经验》的理解
阅读了博客园发布的IT文章《30个提高Web程序执行效率的好经验》,这30条准则对我们web开发是非常有用的,不过大家可能对其中的一些准则是知其然而不知其所以然。 下面是我对这些准则的理解和分析,有些有关JS性能的准则,我也测试了它们的差异,大家可以下载DEMO页面,如有理解不正确的地方,请大家指正。
943 0
|
1月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
101 3
|
17天前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
103 45
|
12天前
|
前端开发 API 开发者
Python Web开发者必看!AJAX、Fetch API实战技巧,让前后端交互如丝般顺滑!
在Web开发中,前后端的高效交互是提升用户体验的关键。本文通过一个基于Flask框架的博客系统实战案例,详细介绍了如何使用AJAX和Fetch API实现不刷新页面查看评论的功能。从后端路由设置到前端请求处理,全面展示了这两种技术的应用技巧,帮助Python Web开发者提升项目质量和开发效率。
28 1