落地西瓜视频埋点方案,埋点从未如此简单

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 落地西瓜视频埋点方案,埋点从未如此简单

前言


  • 目前,几乎每个商用应用都有数据埋点的需求。你的 App 是怎么做埋点的呢,有遇到让你 “难顶” 的问题吗?
  • 在这篇文章里,我将带你建立数据埋点的基本认识,还会介绍西瓜视频团队的前端埋点方案,最后为你带来我的落地实现 EasyTrack。如果能帮上忙,请务必点赞加关注,这真的对我非常重要。


目录

image.png



1. 数据埋点概述


1.1 为什么要埋点?


“除了上帝,任何人都必须用数据说话”,在数据时代,使用数据驱动产品迭代已经称为行业共识。在分析应用数据之前,首先需要获得数据,这就需要前端或服务端进行数据埋点。


1.2 数据需求的工作流程


首先,你需要了解数据需求的工作流程,需求是如何产生,又是如何流转的,主要分为以下几个环节:


  • 1、需求产生: 产品需求引起产品形态变化,产生新的数据需求;
  • 2、事件设计: 数据产品设计埋点事件并更新数据字典文档,提出埋点评审;
  • 3、埋点开发: 开发进行数据埋点开发;
  • 4、埋点测试: 测试进行数据埋点测试,确保数据质量;
  • 5、数据消费: 数据分析师进行数据分析,推荐系统工程师进行模型训练,赋能产品运营决策。


image.png

1.3 数据消费的经典场景


消费场景 需求描述 技术需求
渗透率分析 统计 DAU/PV/UV/VV 等 准确的上报时机
归因分析 分析前因后果 准确上报上下文 (如场景、会话、来源页面)
1. A / B 测试
2. 个性化推荐
分析用户特征、产品特征等 准确上报事件属性


可以看到,在归因分析中,除了需要上报事件本身的属性之外,还需要上报事件产生时的上下文信息,例如当前页面、来源页面、会话等。


1.4 埋点数据采集的基本模型


数据采集是指在前端或服务端收集需要上报的事件属性的过程。为了满足复杂、高效的数据消费需求,需要科学合理地设计端侧的数据采集逻辑,基本可以总结为 “4W + 1H” 模型:


模型 描述 举例
1、WHAT 什么行为 事件名
2、WHEN 行为产生的时间 时间戳
3、WHO 行为产生的对象 对象唯一标识 (例如用户 ID、设备 ID)
4、WHERE 行为产生的环境 设备所处的环境 (例如 IP、操作系统、网络)
5、HOW 行为的特征 上下文信息 (例如当前页面、来源页面、会话)



2. 如何实现数据埋点?


2.1 埋点方案总结


目前,业界已经存在多种埋点方案,主要分为全埋点、前端代码埋点和服务端代码埋点三种,优缺点和适用场景总结如下:


全埋点 前端埋点 服务端埋点
优势 开发成本低 完整采集上下文信息 不依赖于前端版本
劣势 数据量大,无法获取上下文数据,数据质量低 前端开发成本较高 服务端开发成本较高、获取上下文信息依赖于接口传值
适用场景 通用基础事件(如启动/退出、浏览、点击) 核心业务流程(如登录、注册、收藏、购买) 核心业务结果事件(如支付成功)
  • 1、全埋点: 指通过编译时插桩、运行时动态代理等 AOP 手段实现自动埋点和上报,无须开发者手动进行埋点,因此也称为 “无埋点”;
  • 2、前端埋点: 指前端 (包括客户端) 开发者手动编码实现埋点,虽然可以通过埋点工具或者脚本简化埋点开发工作,但总体上还是需要手动操作;
  • 3、服务端埋点: 指服务端手动编码实现埋点,缺点是需要客户端需要侵入接口来保留上下文参数。


2.2 全埋点方案的局限性


表面上看,全埋点方案的优势很明显:客户端和服务端只需要一次开发,就能实现所有页面、所有路径的曝光和点击事件埋点,节省了研发人力,也不用担心埋点逻辑会侵入正常业务逻辑。然而,不可能存在完美的解决方案,全埋点方案还是存在一些局限性:


  • 1、资源消耗较大: 全场景上报会产生大量无用数据,网络传输、数据存储和数据计算需要消耗大量资源;
  • 2、页面稳定性要求较高: 需要保持页面视图结构相对稳定,一旦页面视图结果变化,历史录入的埋点数据就会失效;
  • 3、无法采集上下文信息: 无法采集事件产生时的上下文信息,也就无法满足复杂的数据消费需求。


2.3 埋点设计的整体方案


考虑的不同方案都存在优缺点,单纯采用一种埋点方案是不切实际的,需要根据不同业务场景和不同数据消费需要而采用不同的埋点方案:


  • 1、全埋点: 作为全局兜底方案,可以满足粗粒度的统计需求;
  • 2、前端埋点: 作为全埋点的补充方案,可以自定义埋点参数,主要处理核心业务流程事件,例如(如登录、注册、收藏、购买);
  • 3、服务端埋点: 核心业务结果事件,例如订单支付成功。


3. 前端埋点中的困难


3.1 一个简单的埋点场景


现在,我们通过一个具体的埋点场景,试着发现在做埋点需求时会遇到的困难或痛点。我直接使用西瓜视频中的一个埋点场景:

image.png

—— 图片引用自西瓜视频技术博客


这个产品场景很简单,左边是西瓜视频的推荐流列表,点击 “电影卡片” 会进入右边的 “电影详情页” 。两个页面中都有 “收藏按钮”,现在的数据需求是采集不同页面中 “收藏按钮” 的点击事件,以便分析用户收藏影片的行为,优化影片的推荐模型。

  • 1、在推荐列表页中上报点击事件:


“event_name" : "click_favorite", // 事件名
"cur_page" : "feed",           // 当前页面
"video_id" : "123",            // 影片 ID
"video_name" : "影片名",         // 影片名
"video_type" : "1",            // 影片类型
"$user_id" : "10000",          // 用户 ID
"$device_id" : "abc"           // 设备 ID
...                         // 其他预置属性
复制代码


  • 2、在电影详情页中上报点击事件:


“event_name" : "click_favorite", // 事件名
"from_page" : "feed"
"cur_page" : "video_detail",    // 当前页面
"video_id" : "123",           // 影片 ID
"video_name" : "影片名",        // 影片名
"video_type" : "1",           // 影片类型
"$user_id" : "10000",         // 用户 ID
"$device_id" : "abc"          // 设备 ID
...                        // 其他预置属性
复制代码

3.2 现状分析


理解了这个埋点场景之后,我们先梳理出目前遇到的困难:

  • 1、埋点参数分散: 需要上报的埋点参数位于不同 UI 容器或不同业务模块,代码跨度很大(例如:Activity、Fragment、ViewHolder、自定义 View);
  • 2、组件复用: 组件抽象复用后在多个页面使用(例如通用的 ViewHolder 或自定义 View);
  • 3、数据模型不一致: 不同场景 / 页面下描述状态的数据模型不一致,需要额外的转换适配过程(例如有的模型用 video_type 表示影片类型,另一些模型用 videoType 表示影片类型)。


3.3 评估标准


理解了问题和现状,现在我们开始尝试找到解决方案。为此,我们需要想清楚理想中的解决方案,应该满足什么标准:


  • 1、准确性: 这是核心目标,能够在保证不同场景 / 页面下准确收集埋点数据;
  • 2、简洁性: 使用方法尽可能简单,收敛模板代码;
  • 3、可用性: 尽可能高效稳定,不容易出错,性能开销小。


3.4 常规解决方案


1、逐级传递 —— 通过面向对象的关系逐级传递埋点参数:

通过 Android 框架支持的 Activity / Fragment 参数传递方式和面向对象程序设计,逐级将埋点参数传递到最深层的收藏按钮。例如:

  • 列表页: Activity -> ViewModel -> FeedFragment (推荐) -> Adapter -> ViewHolder (电影卡片) -> CollectButton (收藏按钮)
  • 详情页: Activity -> ViewModel -> DetailBottomFragment(底部功能区) -> CollectButton (收藏按钮)


缺点 (参数传递困难) :传递数据需要编写大量重复模板代码,工程代码膨胀,增大维护难度。再叠加上组件复用的情况,逐级传递会让代码复杂度非常高,很明显不是一个合理的解决方案。


2、Bean 传递 —— 在 Java Bean 中增加字段来收集埋点参数:


缺点 (违背单一职责原则):Java Bean 中侵入了与业务无关的埋点参数,同时会造成 Java Bean 数据冗余,增大维护难度。


3、全局单例 —— 通过全局单例对象来收集埋点参数:


这个方案与 “Bean 传递 ” 类似,区别在于埋点参数从 Java Bean 中移动到全局单例中,但缺点还是很明显:

缺点 (写入和清理时机):单例会被多个位置写入,一旦被覆盖就无法被恢复,容易导致上报错误;另外清理的时机也难以把握,清理过早会导致埋点参数丢失,清理过晚会污染后面的埋点事件。


4. 西瓜视频方案

理解了数据埋点开发中的困难,有没有什么方案可以简化埋点过程中的复杂度呢?我们来讨论下西瓜视频团队分享的一个思路:基于视图树收集埋点参数。


image.png

image.png

—— 图片引用自西瓜视频技术博客


通过分析数据与视图节点的关系可以发现,事件的埋点数据正好分布在视图树的不同节点中。当 “收藏按钮” 触发事件时,只需要沿着视图树逐级向上查找 (通过 View#getParent()) 就可以收集到所有数据。


并且,树的分支天然地支持为参数设置不同的值。例如 “推荐 Fragment” 需要上报 “channel : recomment”,而 “电影 Fragment” 需要上报 “channel : film”。因为 Fragment 的根布局对应有视图树中的不同节点,所以在不同 Fragment 中触发的事件最终收集到的 “channel” 参数值也就不同了。Nice~


5. EasyTrack 埋点框架


思路 Get 到了,现在我们来讨论如何应用这个思路来解决问题。贴心的我已经帮你实现为一个框架 EasyTrack。源码地址:github.com/pengxurui/E…


5.1 添加依赖


  • 1、依赖 JitPack 仓库

在项目级 build.gradle 声明远程仓库:


allprojects {
    repositories {
        google()
        mavenCentral()
        // JitPack 仓库
        maven { url "https://jitpack.io" }
    }
}
复制代码
  • 2、依赖 EasyTrack 框架

在模块级 build.gradle 中依赖类库:


dependencies {
    ...
    // 依赖 EasyTrack 框架
    implementation 'com.github.pengxurui:EasyTrack:v1.0.1'
    // 依赖 Kotlin 工具(非必须)
    implementation 'com.github.pengxurui:KotlinUtil:1.0.1'
}
复制代码

5.2 依附埋点参数到视图树


ITrackModel接口定义了一个数据填充能力,你可以创建它的实现类来定义一个数据节点,并在 fillTrackParams() 方法中声明参数。例如:MyGoodsViewHolder 实现了 ITrackMode 接口,在 fillTrackParams() 方法中声明参数(goods_id / goods_name)。

随后,通过 View 的扩展函数View.trackModel()将其依附到视图节点上。扩展函数 View.trackModel() 内部基于 View#setTag() 实现。


MyGoodsViewHolder.kt


class MyGoodsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), ITrackModel {
    private var mItem: GoodsItem? = null
    init {
        // Java:EasyTrackUtilsKt.setTrackModel(itemView, this);
        itemView.trackModel = this
    }
     override fun fillTrackParams(params: TrackParams) {
        mItem?.let {
            params.setIfNull("goods_id", it.id)
            params.setIfNull("goods_name", it.goods_name)
        }
    }
}
复制代码


EasyTrackUtils.kt


/**
 * Attach track model on the view.
 */
var View.trackModel: ITrackModel?
    get() = this.getTag(R.id.tag_id_track_model) as? ITrackModel
    set(value) {
        this.setTag(R.id.tag_id_track_model, value)
    }
复制代码

ITrackModel.kt


/**
 * 定义数据填充能力
 */
interface ITrackModel : Serializable {
    /**
     * 数据填充
     */
    fun fillTrackParams(params: TrackParams)
}
复制代码

5.3 触发事件埋点


在需要埋点的地方,直接通过定义在 View 上的扩展函数 trackEvent(事件名)触发埋点事件,它会以该扩展函数的接收者对象为起点,逐级向上层视图节点收集参数。另外,它还有多个定义在 Activity、Fragment、ViewHolder 上的扩展函数,但最终都会调用到 View.trackEvent。


class MyGoodsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
     fun bind(item: GoodsItem) {
        ...
        trackEvent(GOODS_EXPOSE)
    }
}
复制代码

EasyTrackUtils.kt


@JvmOverloads
fun Activity?.trackEvent(eventName: String, params: TrackParams? = null) =
    findRootView(this)?.doTrackEvent(eventName, params)
@JvmOverloads
fun Fragment?.trackEvent(eventName: String, params: TrackParams? = null) =
    this?.requireView()?.doTrackEvent(eventName, params)
@JvmOverloads
fun RecyclerView.ViewHolder?.trackEvent(eventName: String, params: TrackParams? = null) {
    this?.itemView?.let {
        if (null == it.parent) {
            it.post { it.doTrackEvent(eventName, params) }
        } else {
            it.doTrackEvent(eventName, params)
        }
    }
}
@JvmOverloads
fun View?.trackEvent(eventName: String, params: TrackParams? = null): TrackParams? =
    this?.doTrackEvent(eventName, params)
复制代码


查看 logcat 日志,可以看到以下日志,显示埋点并没有生效。这是因为没有为 EasyTrack 配置埋点数据上报和统计分析的能力。

logcat 日志


EasyTrackLib: Try track event goods_expose, but the providers is Empty.
复制代码

5.4 实现 ITrackProvider 接口


EasyTrack 的职责在于收集分散的埋点数据,本身没有提供埋点数据上报和统计分析的能力。因此,你需要实现 ITrackProvider 接口进行依赖注入。例如,这里模拟实现友盟数据埋点提供器,在 onInit() 方法中进行初始化,在 onEvent() 方法中调用友盟 SDK 事件上报方法。


MockUmengProvider.kt


/**
 * 模拟友盟数据上报
 */
class MockUmengProvider : ITrackProvider() {
    companion object {
        const val TAG = "Umeng"
    }
    /**
     * 是否启用
     */
    override var enabled = true
    /**
     * 名称
     */
    override var name = TAG
    /**
     * 初始化
     */
    override fun onInit() {
        Log.d(TAG, "Init Umeng provider.")
    }
    /**
     * 执行事件上报
     */
    override fun onEvent(eventName: String, params: TrackParams) {
        Log.d(TAG, params.toString())
    }
}
复制代码

5.5 配置 EasyTrack


在应用初始化时,进行 EasyTrack 的初始化配置。我们可以将相关的初始化代码单独封装起来,例如:


StatisticsUtils.kt


// 模拟友盟数据统计提供器
val umengProvider by lazy {
    MockUmengProvider()
}
// 模拟神策数据统计提供器
val sensorProvider by lazy {
    MockSensorProvider()
}
/**
 * 初始化 EasyTrack,在 Application 初始化时调用
 */
fun init(context: Context) {
    configStatistics(context)
    registerProviders(context)
}
/**
 * 配置
 */
private fun configStatistics(context: Context) {
    // 调试开关
    EasyTrack.debug = BuildConfig.DEBUG
    // 页面间参数映射
    EasyTrack.referrerKeyMap = mapOf(
        CUR_PAGE to FROM_PAGE,
        CUR_TAB to FROM_TAB
    )
}
/**
 * 注册提供器
 */
private fun registerProviders(context: Context) {
    EasyTrack.registerProvider(umengProvider)
    EasyTrack.registerProvider(sensorProvider)
}
复制代码

EventConstants.java


public static final String FROM_PAGE = "from_page";
public static final String CUR_PAGE = "cur_page";
public static final String FROM_TAB = "from_tab";
public static final String CUR_TAB = "cur_tab";
复制代码


配置 类型 描述
debug Boolean 调试开关
referrerKeyMap Map<String,String> 全局页面间参数映射
registerProvider() ITrackProvider 底层数据埋点能力


以上步骤是 EasyTrack 的必选步骤,完成后重新执行 trackEvent() 后可以看到以下日志:


logcat 日志


/EasyTrackLib:  
onEvent:goods_expose
goods_id= 10000
goods_name = 商品名
Try track event goods_expose with provider Umeng.
Try track event goods_expose with provider Sensor.
------------------------------------------------------
复制代码

5.6 页面间参数映射


上一节中有一个referrerKeyMap配置项,定义了全局的页面间参数映射。 举个例子,在分析不同入口的转化率时,不仅仅需要上报当前页面的数据,还需要上报来源页面的信息。这样我们才能分析用户经过怎样的路径来到当前页面,并最终触发了某个行为。

需要注意的是,来源页面的参数往往不能直接添加到当前页面的埋点参数中,这里一般会有一定的转换规则 / 映射关系。例如:来源页面的 cur_page 参数,在当前页面应该映射为 from_page 参数。 在这个例子里,我们配置的映射关系是:


  • 来源页面的 cur_page 映射为当前页面的 from_page;
  • 来源页面的 cur_tab 映射为当前页面的 from_tab。


因此,假设来源页面传递给当前页面的参数是 A,则当前页面在触发事件时的收集参数是 B:


A (来源页面):
{
    "cur_page" : "list"
    ...
}
B (当前页面):
{
    "cur_page" : "detail",
    "from_page" : "list",
    ...
}
复制代码


BaseTrackActivity 实现了页面间参数映射,你可以创建 BaseActivity 类并继承于 BaseTrackActivity,或者将其内部的逻辑迁移到你的 BaseActivity 中。这一步是可选的,如果你不使用页面间参数映射的特性,你那大可不必使用 BaseTrackActivity。


操作 描述
定义映射关系 1、EasyTrack.referrerKeyMap 配置项
2、重写 BaseTrackActivity #referrerKeyMap() 方法
传递页面间参数 Intent.referrerSnapshot(TrackParams) 扩展函数

MyGoodsDetailActivity.java


public class MyGoodsDetailActivity extends MyBaseActivity {
    private static final String EXTRA_GOODS = "extra_goods";
    public static void start(Context context, GoodsItem item, TrackParams params) {
        Intent intent = new Intent(context, GoodsDetailActivity.class);
        intent.putExtra(EXTRA_GOODS, item);
        EasyTrackUtilsKt.setReferrerSnapshot(intent, params);
        context.startActivity(intent);
    }
    @Nullable
    @Override
    protected String getCurPage() {
        return GOODS_DETAIL_NAME;
    }
    @Nullable
    @Override
    public Map<String, String> referrerKeyMap() {
        Map<String, String> map = new HashMap<>();
        map.put(STORE_ID, STORE_ID);
        map.put(STORE_NAME, STORE_NAME);
        return map;
    }
}
复制代码


需要注意的是,BaseTrackActivity 不会将来源页面的全部参数都添加到当前页面的参数中,只有在全局 referrerKeyMap 配置项或 referrerKeyMap() 方法中定义了映射关系的参数,才会添加到当前页面。 例如:MyGoodsDetailActivity 继承于 BaseActivity,并重写 referrerKeyMap() 定义了感兴趣的参数(STORE_ID、STORE_NAME)。最终触发埋点时的日志如下:


logcat 日志


/EasyTrackLib:  
onEvent:goods_detail_expose
goods_id= 10000
goods_name = 商品名
store_id = 10000
store_name = 商店名
from_page = Recommend
cur_page = goods_detail
Try track event goods_expose with provider Umeng.
Try track event goods_expose with provider Sensor.
------------------------------------------------------
复制代码


在一般的埋点模型中,每个 Activity (页面) 都有对应一个唯一的 page_id,因此你可以重写 fillTrackParams()  方法追加这些固定的参数。例如:MyBaseActivity 定义了  getCurPage() 方法,子类可以通过重写  getCurPage() 来设置 page_id。

MyBaseActivity.java


abstract class MyBaseActivity : BaseTrackActivity() {
    @CallSuper
    override fun fillTrackParams(params: TrackParams) {
        super.fillTrackParams(params)
        // 填充页面统一参数
        getCurPage()?.also {
            params.setIfNull(CUR_PAGE, it)
        }
    }
    protected open fun getCurPage(): String? = null
}
复制代码

5.7 TrackParams 参数容器


TrackParams 是 EasyTrack 收集参数的中间容器,最终会分发给 ITrackProvider 使用。


方法 描述
set(key: String, value: Any?) 设置参数,无论无何都覆盖
setIfNull(key: String, value: Any?) 设置参数,如果已经存在该参数则丢弃
get(key: String): String? 获取参数值,参数不存在则返回 null
get(key: String, default: String?) 获取参数值,参数不存在则返回默认值 default

5.8 使用 Kotlin 委托依附参数


如果你觉得每次定义 ITrackModel 数据节点后都需要调用 View.trackModel,你可以使用我定义的 Kotlin 委托 “跳过” 这个步骤,例如:


MyFragment.kt


private val trackNode by track()
复制代码

EasyTrackUtils.kt


fun <F : Fragment> F.track(): TrackNodeProperty<F> = FragmentTrackNodeProperty()
fun RecyclerView.ViewHolder.track(): TrackNodeProperty<RecyclerView.ViewHolder> =
    LazyTrackNodeProperty() viewFactory@{
        return@viewFactory itemView
    }
fun View.track(): TrackNodeProperty<View> = LazyTrackNodeProperty() viewFactory@{
    return@viewFactory it
}
复制代码


如果你还不了解委托属性,可以看下我之前写过的一篇文章,这里不解释其原理了:Android | ViewBinding 与 Kotlin 委托双剑合璧


6. EasyTrack 核心源码


这一节,我简单介绍下 EasyTrack 的核心源码,最核心的部分在入口类 EasyTrack 中:

6.1 doTrackEvent()


doTrackEvent() 是触发埋点的主方法,主要流程是调用 fillTrackParams() 收集埋点参数,再将参数分发给有效的 ITrackProvider。


internal fun Any.doTrackEvent(eventName: String, otherParams: TrackParams? = null): TrackParams? {
    1. 检查是否有有效的 ITrackProvider
    2. 基于视图树递归收集埋点参数(fillTrackParams)
    3. 日志
    4. 将收集到的埋点参数分发给有效的 ITrackProvider
}
复制代码

6.2 fillTrackParams()


-> 基于视图树递归收集埋点参数
internal fun fillTrackParams(node: Any?, params: TrackParams? = null): TrackParams {
    val result = params ?: TrackParams()
    var curNode = node
    while (null != curNode) {
        when (curNode) {
            is View -> {
                // 1. 视图节点
                if (android.R.id.content == curNode.id) {
                    // 1.1 Activity 节点
                    val activity = getActivityFromView(curNode)
                    if (activity is IPageTrackNode) {
                        // 1.1.1 IPageTrackNode节点(处理页面间参数映射)
                        activity.fillTrackParams(result)
                        curNode = activity.referrerSnapshot()
                    } else {
                        // 1.1.2 终止
                        curNode = null
                    }
                } else {
                    // 1.2 Activity 视图子节点
                    curNode.trackModel?.fillTrackParams(result)
                    curNode = curNode.parent
                }
            }
            is ITrackNode -> {
                // 2. 非视图节点
                curNode.fillTrackParams(result)
                curNode = curNode.parent
            }
            else -> {
                // 3. 终止
                curNode = null
            }
        }
    }
    return result
}
复制代码


主要逻辑:从入参 node 为起点,循环获取依附在视图节点上的 ITrackModel 数据节点并调用 fillTrackParams() 方法收集参数,并将循环指针指向 parent。


7. 总结


EasyTrack 框架的源码我已经放在 Github 上了,源码地址:github.com/pengxurui/E… 我也写了一个简单的 Sample Demo,你可以直接运行体验下。欢迎批评,欢迎 Issue~

说说目前遇到的问题,在处理页面间参数传递时,我们需要依赖 Intent extras 参数。这就导致我们需要在大量创建 Intent 的地方都加入来源页面的埋点参数(注意:即使你不使用 EasyTrack,你也要这么做)。目前我还没有想到比较好的方法,你觉得呢?说说你的看法吧

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
监控 JavaScript 前端开发
百度统计分析埋点最佳实战篇
百度统计分析埋点最佳实战篇
1939 0
百度统计分析埋点最佳实战篇
|
3月前
|
JavaScript 前端开发 数据挖掘
用百度和神策做埋点为何 pv 差异很大?
近期 ClkLog 收到一个客户反馈说我们与百度统计的 PV 数据差异很大。为了验证问题,开发进行了一次对页面浏览量统计的测试。针对同一个 IP 同一个时间的页面浏览量统计发现,百度的统计数据只有一条,而 ClkLog 有十条记录,于是我们展开了问题排查。
用百度和神策做埋点为何 pv 差异很大?
|
3月前
|
数据采集 数据可视化 前端开发
深入了解埋点分析:Clklog助你优化用户体验
所谓埋点,就是通过在应用程序或网站中插入代码,针对用户行为或事件进行捕获,以收集用户的各种行为数据,比如用户点击了某个按钮、页面访问时间、功能使用频率等。 通过分析这些数据,数据产品经理或数据分析师能够深入理解用户行为,优化用户体验,并根据埋点数据改进产品功能。
深入了解埋点分析:Clklog助你优化用户体验
|
3月前
|
消息中间件 搜索推荐 小程序
开源埋点用户行为分析方案-ClickLog埋点(ClkLog)
ClkLog 是一款记录用户行为分析和画像的免费可商用开源软件,技术人员可快速搭建私有的应用系统。项目基于神策分析SDK,采用ClickHouse数据库对采集数据进行存储,采用前后端分离的方式来实现的访问统计和用户画像分析系统。在这里,你可以轻松看到用户访问网页、APP、小程序或业务系统的行为轨迹,同时也可以从时间、地域、渠道、用户访客类型等多维度了解用户的全方位信息,完美助力大数据用户画像、实时归因/离线归因分析、漏斗分析、大数据推荐场景。
开源埋点用户行为分析方案-ClickLog埋点(ClkLog)
|
数据采集 前端开发 搜索推荐
埋点tracker:前端数据埋点-方案设计思路梳理
埋点tracker:前端数据埋点-方案设计思路梳理
1596 0
|
存储 消息中间件 监控
架构设计第42讲:美团 - 可视化全链路日志追踪
架构设计第42讲:美团 - 可视化全链路日志追踪
410 0
|
存储 开发框架 前端开发
埋点tracker:前端埋点服务-技术要点梳理
埋点tracker:前端埋点服务-技术要点梳理
536 0
|
小程序 测试技术 定位技术
05 埋点测试实战之易观方舟
05 埋点测试实战之易观方舟
|
监控 小程序 机器人
06 埋点测试之体系形成
06 埋点测试之体系形成
|
监控 前端开发 关系型数据库
前端埋点监控方案
推荐一款比较好的前端埋点监控的方案——[webfunny]
612 0