草庐IT

聊聊令人头疼的埋点

方勇 2023-08-23 原文

埋点,是指在应用中添加代码,以收集用户的操作行为和数据,以便后续进行数据分析和产品决策。这些代码通常被称为埋点代码,它们将事件(如点击、滚动、搜索等)和属性(如时间、位置、设备等)捕捉并发送到数据平台。通常情况下,这些数据用于分析用户行为、监控应用程序性能、改进产品功能等方面。

转转 H5 采用的是手动埋点方式,App 内的页面通常需要添加各种埋点,以验证和辅助产品后续决策。今天就和大家聊聊令笔者头疼的埋点,也希望能加深您对埋点的理解~

以下部分内容、代码,来源于 chatGPT,如有错误,欢迎指出~

埋点内容

首先埋点内容一般会包含用户信息、页面信息、事件信息、访问信息等。

  • 用户信息:包括用户的唯一标识(uid)、设备标识(token)、访问设备、浏览器版本以及网络状态等
  • 页面信息:包括当前页面的 URL、标题、页面 ID 等信息
  • 事件信息:包括用户的行为事件,如点击、滚动、鼠标移动等,以及事件的时间戳、元素路径、事件类型等信息
  • 访问信息:包括用户来源、搜索关键词、渠道信息等,计算整个链路的渗透,或者在出现问题时,帮助还原整个用户操作路径,帮助开发者更快的定位、修复问题。

埋点方式

常见的埋点方式大体可以分为手动埋点、可视化埋点和全埋点三种。

  • 手动埋点在代码中手动加入埋点,相应事件触发的时候,再上报相关埋点。当需要精细化数据或者希望根据业务诉求,定制化添加埋点的时候,就很适合使用这种方式。但缺点就是额外工作量很大,也需要相关的 QA 介入测试,一些复杂的埋点很容易出错,导致延误需求数据分析的时间
  • 可视化埋点 需要页面/项目预先接入可视化埋点 SDK,并开启可视化埋点开关,然后相关人员登录可视化圈选后台,选择相应的页面以及圈选需要上报的相关行为埋点,圈选平台和 SDK 进行通信,让 SDK 拿到需要上报的埋点,然后 SDK 自动上报相关的埋点。
  • 全埋点是一种将应用程序中所有用户行为都收集和分析的埋点技术,例如打开页面、切入后台、点击某个区域、某个区域曝光等等,优点就是可以更全面、更细致地了解用户行为和需求,缺点就是由于自动记录了各种操作行为的数据,会导致大量的无意义的行为被上报,对服务端的压力比较大,并且也考验从纷繁复杂的埋点中找到所需埋点的能力

埋点流程

埋点流程大体可以分为埋点触发、上报、校验以及上报到数据平台后的埋点清洗、过滤和分析,进而产出下一步决策。

  1. 埋点触发埋点触发大致分为自动触发和手动触发两种方式,上面提及的页面展现通常就是自动触发,当页面打开的时候,就自动上报了。但是像点击埋点就可以用手动触发的,只有当区域被真正点击时,才会进行上报。
  2. 埋点上报其中埋点上报又分为立即上报和延迟上报两种。立即上报的逻辑相对简单,在埋点事件触发时,就立即上报。但是缺点也很明显,就是上报的埋点量巨大,会给埋点服务造成巨大负担。延迟上报,就是将一段时间内的埋点,收集起来,然后一次性上报。这样无疑就会使上报的次数,急剧减少,减轻了埋点服务压力。但是其中又会涉及埋点上报去重、埋点触发时间校准(如果客户端时间不准怎么办?)等等其他问题,因此相对立即上报来说,延迟上报逻辑上要复杂一些。并且需要数据层面进行过滤、清洗。
  3. 埋点校验开发者手动添加了部分埋点,需求上线前需要进行验证,确保按照要求进行了上报,其中校验可以使用人工触发,抓包进行校验。也可以通过编写自动化脚本,模拟使用,进行校验。转转侧使用相关的后台,可以通过筛选相关用户、来源以及不同环境,实时接收相关的埋点,进行校验。
  4. 埋点分析埋点上报之后,数据平台就会拿到相关的埋点数据,对纷繁复杂的数据,进行过滤、清洗,得到产品需要的数据,然后产品就会对数据进行分析,有时可以发现一些问题,以及对后续决策产生影响。

埋点常见类型

埋点的触发通常与埋点的类型相关,接下来列举几种常见的埋点类型:

  • 页面展现在页面展现时进行上报,H5 环境下一般通过监听 onshow 或者 visibilitychange 事件来实现,但是这两者都有一定的兼容性问题。而如果是处于 hybrid 环境,则可以利用宿主环境(客户端)暴露的生命周期来实现,借用原生的生命周期来实现,也更加准确些。页面展现一般用来记录页面的 PV/UV,算是一项非常基础的数据了。
  • 点击用户点击某个区域时上报,可以上报相关业务参数,也可以包含点击位置信息,其中位置信息可以用来生成热力图,确定页面的热区,从而可以知道用户对哪部分更加感兴趣,哪部分的转化效率更高,以便调整后续的产品策略。H5 中一般可以通过事件委托来实现,在根结点监听点击事件,当事件冒泡到根结点阶段,触发相应事件。
document.addEventListener('click', function(e) {
const target = e.target
// do something
}, true);
  • 区域曝光当某个区域出现在视口内,一定时间内进行上报,一般配合点击、下单等数据,观察整个路径的漏斗转化。由于会涉及重复上报的问题,所以一般区域都会有一套规则,生成该区域的唯一标识,防止重复上报。以及在商品列表的场景中存在翻页的情况,就需要再使用 MutationObserver 监听 DOM 的变化,动态的调用 IntersectionObserver 进行重复监听。H5 一般有监听页面滚动事件和使用 IntersectionObserver 两种方式来实现
// 1. 监听页面滚动实现
const element = document.querySelector('.exposure-ele');

window.addEventListener('scroll', () => {
const elementPosition = element.getBoundingClientRect();
const windowPosition = {
top: 0,
left: 0,
bottom: window.innerHeight,
right: window.innerWidth
};

if (isElementInViewport(elementPosition, windowPosition)) {
console.log('Element is in viewport!');
} else {
console.log('Element is not in viewport!');
}
});

function isElementInViewport(elementPosition, windowPosition) {
return (
elementPosition.bottom > windowPosition.top &&
elementPosition.top < windowPosition.bottom &&
elementPosition.right > windowPosition.left &&
elementPosition.left < windowPosition.right
);
}

// 2. 使用 IntersectionObserver 实现
const element = document.querySelector('.exposure-ele');

const options = {
root: null,
rootMargin: '0px', // 设置视口四边延伸的范围,可以利用此做列表数据的提前加载
threshold: 0.5 // 区域与视口相交的阈值
};

const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('Element has entered the viewport!');
} else {
console.log('Element has left the viewport!');
}
});
}, options);

observer.observe(element);
  • 页面停留时长定义用户从进入页面到离开页面的时长,一般需要精确到毫秒级别。停留时间越长,一般代表用户对当前页面越感兴趣,预示产品决策是否可以对此进行一些深耕。
  • 热力图通过颜色深浅,标识用户对页面各区域点击的频率。颜色越深,代表点击频率越高,是一种直观、高效发现吸引用户区域的方式。比如常用于商场首页金刚位,可以清晰发现用户对各品类的喜好,就可以动态调整金刚位的类目。一般是通过统计点击埋点上报的位置,进行实现。其中位置又可以分为绝对位置和区域位置两种。绝对位置是指点击时的 x、y 坐标,但是由于各个手机的分辨率不同,点击同一区域的 x、y 坐标也不一样,就需要进行多分辨率的调整、整合,比较复杂。转转现在采用的是后者区域位置,通过页面ID(pageId)、区域ID(sectionId)以及区域次序ID(sortId),对一个区域进行定位,当点击时会上报相关的 pageId、sectionId 以及 sortId,然后就可以统计出页面某个区域、某个次序的点击率,生成相应热力图了。以下是转转游戏账号首页的热力示意图:

  • 性能埋点通过记录页面加载过程不同阶段的耗时,帮助开发者发现性能问题,提升页面加载速度,发现可优化的点,提升用户体验。甚至可以与白屏检测相结合,在页面出现问题时,及时报警通知相关人员查看、处理。

埋点发送方式

埋点发送即将埋点相关数据发送给数据平台,一般有接口方式、img 标签方式和 sendBeacon 三种方式。

  • 接口方式:通过接口的形式将埋点的信息进行上报,兼容性比较好,但是一些网站可能会禁用脚本,导致失效。
  • 创建 img 标签:很多公司都采用 img 标签携带埋点信息进行上报,一方面是图片请求不存在跨域限制(一般而言,埋点发送域名都不是当前域名),另一方面图片标签不需要真正插入到 DOM 节点中,只需要实例化 Image,设置 src 属性就会发出请求,不会阻塞页面渲染,对性能影响较小。
const img = new Image()
img.src = 'https://example.com/log?xxx'

为了追求埋点请求尽可能小,大多采用的是 1*1 像素的透明 GIF 来上报,因为在各种图片格式下,这种相对较小。

  • sendBeacon 发送该 api 是专门被设计来满足统计和诊断代码的需要,通常需要在页面卸载之前,将相关埋点发出。过早的发送数据可能导致错过收集数据的时机,因此需要等到页面即将卸载时发送数据。在 sendBeacon 出现之前,很难保证在页面卸载之前,可以将数据成功发送,因为用户代理通常会忽略在 unload 事件处理器中产生的异步 XHR。过去为了解决这个问题,开发者们想出了一些 hack 的方法:但是无独有偶,上述方法都存在一个问题,那就是会延迟当前页面的卸载,导致下一个页面出现的更晚。而 sendBeacon 不存在上述问题,它数据发送是可靠的、是异步的,正由于异步发送数据,所以不影响下一导航的载入。一般可以监听 visibilitychange 的 hidden 状态来发送埋点
document.addEventListener('visibilitychange', function logData() {
if (document.visibilityState === 'hidden') {
navigator.sendBeacon('/log', analyticsData)
}
})
  1. 发起一个同步 XMLHttpRequest 来发送数据
  2. 创建一个 img 元素并设置 src,大部分用户代理会延迟卸载(unload)文档以加载图像
  3. 创建一个几秒的 noop 循环

总结

以上从埋点内容、方式、流程、常见埋点的类型以及发送方式等方面,介绍了埋点相关的基础概念以及转转采取的方案,希望能对您有所帮助~

参考及引用

  • onshow
  • visiblechange
  • sendBeacon
  • IntersectionObserver
  • MutationObserver

有关聊聊令人头疼的埋点的更多相关文章

  1. ruby - 令人印象深刻的 Ruby 示例 - 2

    几天后,我要在大学里做一个关于Rails项目的演讲,我想向听众介绍Ruby。我想向他们展示一两个非常好的代码示例,以展示Ruby的强大之处。你知道一个很好的例子吗?最好的问候 最佳答案 向他们展示您如何将50行丑陋的脏代码转换为3条非常易于理解的干净代码。(作为评论的第一行)不要表现出你对ruby​​有多酷。但是,如果他们使用ruby​​,他们会多么酷:) 关于ruby-令人印象深刻的Ruby示例,我们在StackOverflow上找到一个类似的问题: htt

  2. 【思考】聊聊低代码的实践之路 - 2

    文章目录背景一、最初的疑惑二、简单聊聊原理三、组织内实践案例四、实践带来的反思五、最后聊几句问题背景这个概念由来已久,但是在国内兴起,是最近几年;低代码即Low-Code;指提供可视化开发环境,可以用来创建和管理软件应用;简单的说就是可以通过各种组件的拖拽,实现页面的创建,交互流程和逻辑,以及数据层面的管理,更加高效的实现需求;早先在数据公司时;见识过低代码的应用,也参与过部分研发,比如元数据平台,BI分析等;不过,当时还是以数据管理的工具来定义项目,并非是低代码;从「2020年底」开始;实际上,那个时间节点,低代码平台的应用已经形成趋势了;现在的公司,将低代码平台的使用规划到业务体系中;后来

  3. ruby-on-rails - Rails Impressionist Gem - WillPaginate::Collection 不令人印象深刻 - 2

    有什么方法可以将印象派gem与分页一起使用?我尝试将印象派用于will_paginate集合,如下所示:posts=Post.all.paginate(:page=>params[:page])impressionist(posts)但是它引发了这个错误:WillPaginate::Collectionisnotimpressionable!有什么方法可以直接在View上使用印象派方法? 最佳答案 will_paginate生成的集合不易受影响意味着您正在阅读,它的类没有调用is_impressionable,正如您的模型应该调用的

  4. ruby-on-rails - 令人难忘的 ruby 名称生成器 gem - 2

    在我自己开始翻字典之前,有没有人知道ruby​​gem可以生成适合应用程序key的令人难忘的名称。我需要一些可以发音的东西,这样我就可以为用户提供唯一的电子邮件地址来提交内容。我喜欢Heroku为其应用程序命名的例子。floating-sky-58simple-fog-45 最佳答案 我刚刚为一个项目实现了这个,我的解决方案是使用Forgerygem和类似这样的东西:[Forgery::Basic.color,Forgery::Address.street_name.split("").first,rand(100)].join("

  5. ruby-on-rails - 需要对 proc 和 lambda 进行令人难忘的解释 - 2

    我已经尝试阅读有关过程和lambda的内容,但我必须继续重新阅读定义。谁能用清晰易记的方式向我解释一下? 最佳答案 已编辑:在这里阅读了其他好的答案后,我提供以下提炼,可能会节省您一些重读时间:(l)ambda-(L)ocalreturn(L)ooksatthearguments(p)roc-(P)opsyououtofthemethodwhenitreturns.(P)ermitsdifferentarguments爱因斯坦说“......让事情尽可能简单,但不要更简单。”如果他有堆栈溢出,他会把你指向这里:Whatarethed

  6. javascript - 为 declare 解释这个令人困惑的 dojo 教程语法 - 2

    我正在阅读使用dojo'sdeclare的语法用于创建类。描述令人困惑:Thedeclarefunctionisdefinedinthedojo/_base/declaremodule.declareacceptsthreearguments:className,superClass,andproperties.ClassNameTheclassNameargumentrepresentsthenameoftheclass,includingthenamespace,tobecreated.Namedclassesareplacedwithintheglobalscope.Thecla

  7. java - 令人困惑的 Java 语法 - 2

    我正在尝试将以下代码(来自Wikipedia)从Java转换为JavaScript:/**3June2003,[[:en:User:Cyp]]:*Maze,generatedbymyalgorithm*24October2006,[[:en:User:quin]]:*Sourceeditedforclarity*25January2009,[[:en:User:DebateG]]:*Sourceeditedagainforclarityandreusability*1June2009,[[:en:User:Nandhp]]:*SourceeditedtoproduceSVGfilewh

  8. javascript - 令人困惑的 "instanceof "结果 - 2

    functionFoo(){}functionBar(){}Bar.prototype=newFoo()console.log("Bar.prototype.constructor===Foo?"+(Bar.prototype.constructor===Foo))console.log("newBar()instanceofBar?"+(newBar()instanceofBar))=>Bar.prototype.constructor===Foo?true=>newBar()instanceofBar?true为什么“instanceof”的结果不是“false”,因为“const

  9. javascript - 令人惊讶的是,JavaScript 代码可以执行它想要的任何进程。为什么? - 2

    我问了“Howtorunaexecutablefilefromawebpage?”很多人告诉我这是不可能的,但我的同事找到了一段可以执行任何进程的JavaScript代码。我无法相信ActiveX如此危险。怎么会这样?为什么这不被IE禁止?functionRun(strPath){try{varobjShell=newActiveXObject("wscript.shell");objShell.Run(strPath);objShell=null;}catch(e){alert('Cannotfind"'+strPath)}}notepadmspaintcalcformatc:

  10. javascript - IE8 - IE10 跨域 JSONP cookie 头疼 - 2

    由于完全不受我控制的决定,我处于以下情况:我在catalog.org上有一个产品列表单击产品上的“添加到购物车”按钮向secure.com/product/add/[productKey]发出AJAXJSONP请求,该请求将购物车记录保存到数据库中,使用购物车ID设置cookie,并返回true响应(如果失败则为false)回到catalog.org,如果响应为真,则会向secure.com/cart/info发出另一个AJAXJSONP请求,该请求读取购物车IDcookie,获取记录,并返repo物车中的商品数量再次返回catalog.org,读取响应并更新页面上的元素,显示购物车中

随机推荐