Web 埋点方案怎么定:从目标到落地
Web 埋点方案怎么定:从目标到落地
埋点指在页面或应用里按约定采集用户行为或业务事件(曝光、点击、停留、转化等),把数据发到分析平台或自建服务,用于产品迭代、运营效果、故障与体验优化。本文从目标 → 模型 → 采集方式 → 传输与性能 → 合规顺序,给一套可落地的选型思路,不绑定某一厂商 SDK;§9 用四个案例(博客阅读、电商曝光与漏斗、SPA 路由、批量与关页)说明字典与伪代码怎么写。
1. 先定目标:要回答什么问题
| 类型 | 典型问题 | 常见事件 |
|---|---|---|
| 流量 | 哪些页面被访问、从哪来 | 页面浏览 PV/UV、来源、落地页 |
| 行为 | 用户点了什么、路径是否走通 | 按钮点击、表单提交、步骤漏斗 |
| 内容 | 哪篇文章/商品被看完 | 列表曝光、详情停留、滚动深度 |
| 性能与异常 | 慢在哪、错在哪 | LCP、JS 错误、接口失败(常与埋点并列,用 RUM/错误监控) |
目标决定事件粒度和优先级:不必一上来「全站每个像素都报」。
2. 事件模型:统一「谁在何时何地做了什么」
建议每条上报至少包含:
| 维度 | 说明 |
|---|---|
| 事件名 | 英文蛇形或点分,如 page_view、cta_click、order_paid |
| 时间 | 客户端时间戳 + 服务端接收时间(用于对时与延迟分析) |
| 用户/会话 | 匿名设备 ID、登录用户 ID、会话 ID(同一会话多页面串联) |
| 页面/环境 | URL、路由名、渠道、App/Web、版本号 |
| 业务属性 | 如 article_id、sku_id、button_name(键名稳定、枚举可收敛) |
文档化一份 《埋点字典》(事件名 + 属性 + 触发时机),前后端、产品、数据对齐,避免「同名不同义」。
3. 三种常见采集方式
| 方式 | 做法 | 优点 | 注意 |
|---|---|---|---|
| 代码埋点 | 在交互处显式调用 track('event', props) | 语义清晰、可控 | 发版才能改,需评审字典 |
| 全埋点 / 无埋点 | SDK 监听大量 DOM 点击或路由 | 上线快、覆盖面大 | 噪声多、清洗成本高 |
| 可视化圈选 | 运营在后台圈选元素绑定事件 | 少改代码 | 页面改版易失效,仍需规范 |
实际项目多为 代码埋点为主(核心转化路径)+ 少量全埋点/圈选(探索性分析)的混合。
4. 前端落地:放哪、何时发
页面级
- 多页应用(MPA):每个 HTML 加载后上报
page_view。 - 单页应用(SPA):路由变化时上报(监听
history.pushState/ 路由afterEach),否则只会在首屏记一次。
- 多页应用(MPA):每个 HTML 加载后上报
交互级
- 在按钮、卡片等稳定业务入口调用埋点,避免绑在易变的
class上。
- 在按钮、卡片等稳定业务入口调用埋点,避免绑在易变的
曝光级(列表项进入视口)
- 使用
IntersectionObserver,注意节流与去重(同一 item 只报一次或按策略重复)。
- 使用
批量与卸载
- 高频事件可先入队,定时批量发送;页面关闭时用
navigator.sendBeacon(若仍可用)降低丢失率。
- 高频事件可先入队,定时批量发送;页面关闭时用
5. 传输与性能
| 手段 | 说明 |
|---|---|
| HTTPS | 传输加密,防窃听与篡改。 |
| 异步、不阻塞主线程 | fetch/sendBeacon/Image 像素(兼容老环境);避免同步 XHR。 |
| 采样 | 超大流量可对非关键事件做比例采样。 |
| 失败重试 | 队列 + 退避,避免打爆采集端。 |
6. 合规与隐私(必做)
- 告知与同意:Cookie/设备标识用于分析时,需符合当地法律与平台政策(如个人信息保护法、GDPR 等),隐私政策中说明用途。
- 最小必要:不上报密码、完整身份证号、未脱敏手机号等;敏感字段脱敏或哈希。
- 可关闭:提供关闭统计或仅必要 Cookie 的选项(视产品形态而定)。
7. 平台选型(思路)
| 方向 | 适用 |
|---|---|
| SaaS(如常见统计与增长分析产品) | 省运维、报表多;注意数据出境与合同。 |
| 开源可自建(如部分 Web 分析项目) | 数据自持、轻量 PV;功能需自己补。 |
| 完全自建 | 业务网关 + 日志管道 + OLAP;成本高、灵活度最高。 |
选型时同步考虑:是否支持 SPA、是否提供服务端埋点(订单等以服务端为准)、是否与现有 ID 体系统一。
8. 验收与治理
- 联调:用抓包或平台「实时调试」核对事件名、属性是否与字典一致。
- 监控:采集失败率、延迟突增要告警。
- 版本:埋点 SDK 或协议变更做 版本号,避免新老客户端混用导致统计断层。
9. 案例说明(从业务到代码)
下面用四个互不冲突的场景,把前文落到「字典长什么样、代码写在哪」。
案例一:技术博客 —— 读什么、有没有读完
业务问题:哪篇文章被打开得多?用户是否滚到文末(粗略代表「读完」)?
埋点字典(节选)
| 事件名 | 何时触发 | 主要属性 |
|---|---|---|
page_view | 文章详情页展示 | page_type: 'article', article_id, title, author |
article_scroll | 滚动超过 25% / 50% / 75% / 100%(各报一次,去重) | article_id, depth_percent |
cta_click | 点击文末「转载须知」「目录」等 | article_id, cta_name |
伪代码(滚动深度,注意去重)
const reported = new Set();
function onScroll() {
const el = document.documentElement;
const p = (el.scrollTop + el.clientHeight) / el.scrollHeight;
const milestones = [0.25, 0.5, 0.75, 1];
for (const m of milestones) {
const key = `${articleId}_${m}`;
if (p >= m && !reported.has(key)) {
reported.add(key);
track("article_scroll", { article_id: articleId, depth_percent: m * 100 });
}
}
}
案例二:电商活动页 —— 曝光 + 下单漏斗
业务问题:列表里哪些商品「被看到」?从看到到加购、下单是否在某一步大量流失?
漏斗事件(服务端支付成功建议以服务端为准)
| 步骤 | 事件名 | 属性示例 |
|---|---|---|
| 商品卡片进入视口 | product_impression | sku_id, position, activity_id |
| 点进详情 | product_view | sku_id |
| 加购 | add_to_cart | sku_id, quantity |
| 结算页曝光 | checkout_view | order_id 或匿名会话 |
| 支付成功(建议服务端) | purchase | order_id, amount |
列表曝光(IntersectionObserver,同一 SKU 只报一次曝光)
const seen = new Set();
const io = new IntersectionObserver(
(entries) => {
entries.forEach((e) => {
if (!e.isIntersecting) return;
const sku = e.target.dataset.skuId;
if (seen.has(sku)) return;
seen.add(sku);
track("product_impression", {
sku_id: sku,
position: e.target.dataset.position,
activity_id: ACTIVITY_ID,
});
});
},
{ threshold: 0.5 }
);
document.querySelectorAll("[data-sku-id]").forEach((el) => io.observe(el));
案例三:Vue / React SPA —— 路由切换算一次「页面」
问题:只加载一个 index.html,若不在路由变化时上报,整站 PV 会严重偏低。
Vue Router 3/4(概念示例)
router.afterEach((to) => {
track("page_view", {
path: to.path,
name: to.name,
query_keys: Object.keys(to.query),
});
});
React Router 6(useLocation + useEffect)
const location = useLocation();
useEffect(() => {
track("page_view", { path: location.pathname + location.search });
}, [location]);
案例四:高频点击与关页 —— 队列 + sendBeacon
问题:活动页短时间大量点击,若每次 fetch 一次,既占连接又费电;关页时队列里若还有数据,可能发不出去。
思路:内存队列,每 3 秒或满 10 条批量发送;visibilitychange 为 hidden 时用 navigator.sendBeacon 把剩余 JSON 发到采集接口(需服务端支持 POST + text/plain 或 application/json)。
const queue = [];
function track(event, props) {
queue.push({ event, props, t: Date.now() });
if (queue.length >= 10) flush();
}
let timer = setInterval(flush, 3000);
function flush() {
if (!queue.length) return;
const batch = queue.splice(0, queue.length);
fetch("/collect", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ batch }),
keepalive: true,
}).catch(() => {
queue.unshift(...batch);
});
}
document.addEventListener("visibilitychange", () => {
if (document.visibilityState !== "hidden") return;
if (!queue.length) return;
const body = JSON.stringify({ batch: queue.splice(0, queue.length) });
navigator.sendBeacon("/collect", new Blob([body], { type: "application/json" }));
});
说明:
sendBeacon与采集网关的 Content-Type、鉴权 要事先对齐;若不支持 Beacon,可退化为fetch(..., { keepalive: true })。
10. 小结
一套可维护的埋点方案 = 清晰的问题目标 + 统一事件模型与字典 + 代码埋点为主、配合 SPA 路由与性能手段 + 合规与治理。上文四个案例分别对应 内容阅读深度、电商曝光与漏斗、SPA 页面浏览、批量与关页可靠性;按业务选章复用,比一次性「全埋」更容易长期迭代。
