前端添加埋点四种方式和原理总结
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">前言</a></li><li><a href="#_label1">方法一:使用 Image Beacon(最经典、最可靠的方法)</a></li><li><a href="#_label2">方法二:使用 Navigator.sendBeacon(现代浏览器推荐)</a></li><li><a href="#_label3">方法三:使用 Fetch API with keepalive(不推荐)</a></li><li><a href="#_label4">方法四:直接使用神策官方 SDK(最推荐)</a></li><ul class="second_class_ul"><li><a href="#_lab2_4_0">神策请求参数详解</a></li></ul><li><a href="#_label5">总结与选择</a></li><ul class="second_class_ul"></ul><li><a href="#_label6">常见问题:神策使用 Image Beacon 是怎么获取用户数据的?</a></li><ul class="second_class_ul"><li><a href="#_lab2_6_1">核心原理:URL 即消息</a></li><li><a href="#_lab2_6_2">为什么选择用图片(Image Beacon)?</a></li><li><a href="#_lab2_6_3">与现代方法的对比</a></li><li><a href="#_lab2_6_4">总结</a></li></ul><li><a href="#_label7">案例代码</a></li><ul class="second_class_ul"></ul><li><a href="#_label8">总结</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>前言</h2><p><strong>核心实现思路</strong> 埋点本质上就是向埋点平台的数据接收 URL 发送一个 HTTP 请求,请求中携带了格式化的数据(通常是 URL 参数或 JSON)。您提供的响应头表明这是一个 GET 请求,并且服务器配置为不缓存且允许跨域。</p>
<p class="maodian"><a name="_label1"></a></p><h2>方法一:使用 Image Beacon(最经典、最可靠的方法)</h2>
<p>这是最传统且兼容性最好的方式,利用图片请求没有跨域限制的特性。</p>
<p>代码如下:</p>
<div class="jb51code"><pre class="brush:js;">// 将属性对象转换为URL参数字符串
function formatParams(params) {
return Object.keys(params)
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(params)}`)
.join("&");
}
// 使用 Image Beacon
document.getElementById("ImageBeacon").addEventListener("click", () => {
// 构建埋点URL和参数
const saUrl = "https://..."; // 数据接收地址
const project = "fosun_test1"; // 您的项目名
const event = "ButtonClick"; // 事件名称,如 'PageView', 'ButtonClick'
const distinctId = "user_123"; // 用户ID
const properties = {
// 事件属性
$url: window.location.href,
button_name: "提交按钮"
};
const finalUrl = `${saUrl}?project=${project}&event=${event}&distinct_id=${distinctId}&${formatParams(properties)}`;
// 创建Image对象发送请求
const beacon = new Image(1, 1); // 创建一个1x1像素的图片
beacon.src = finalUrl; // 设置src即发起GET请求
// 可选的错误处理
beacon.onerror = function () {
console.error("Sensors beacon request failed.");
};
beacon.onload = function () {
console.log("Sensors beacon request successful.");
};
});
</pre></div>
<p>截图如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026011209330696.png" /></p>
<p class="maodian"><a name="_label2"></a></p><h2>方法二:使用 Navigator.sendBeacon(现代浏览器推荐)</h2>
<p>这是 HTML5 专门为日志上报设计的新 API,非常适合埋点场景。</p>
<p>代码如下:</p>
<div class="jb51code"><pre class="brush:js;">// 使用 sendBeacon 发送数据
document.getElementById("sendBeacon").addEventListener("click", () => {
// 构建数据对象
const data = {
project: "fosun_test1",
event: "ButtonClick",
distinct_id: "user_123",
properties: {
$url: window.location.href,
button_name: "提交按钮"
}
};
// 注意:sendBeacon 通常以 POST 方式发送字符串化的JSON
const blob = new Blob(, { type: "application/x-www-form-urlencoded" });
const success = navigator.sendBeacon("https://...", blob); // 数据接收地址
if (success) {
console.log("Beacon enqueued successfully!");
} else {
console.error("Beacon failed to queue.");
}
});
</pre></div>
<p>截图如下:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026011209330635.png" /></p>
<p><strong>优点:</strong></p>
<ul><li>专为日志设计:即使页面卸载也会保证发送。</li><li>低优先级:不会阻塞页面卸载过程或与关键操作竞争网络资源。</li></ul>
<p class="maodian"><a name="_label3"></a></p><h2>方法三:使用 Fetch API with keepalive(不推荐)</h2>
<p><code>Fetch API </code>提供了 <code>keepalive</code> 选项,用于在页面卸载后继续发送请求。这在需要确保数据上报的场景下非常有用。</p>
<div class="jb51code"><pre class="brush:js;">// 使用 fetchKeepalive 发送数据
document.getElementById("keepalive").addEventListener("click", () => {
const data = new URLSearchParams();
data.append("project", "your_project_name");
data.append("event", "ButtonClick");
data.append("distinct_id", "user_123");
data.append(
"properties",
JSON.stringify({
$url: window.location.href,
button_name: "提交按钮"
})
);
fetch("https://...", {
method: "POST",
body: data,
keepalive: true, // 关键参数:确保请求在页面卸载后仍能继续
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}).catch((error) => {
console.error("Fetch beacon failed:", error);
});
});
</pre></div>
<p class="maodian"><a name="_label4"></a></p><h2>方法四:直接使用神策官方 SDK(最推荐)</h2>
<p>实际上,您不需要手动实现这些。神策提供了非常完善的官方 SDK,只需简单初始化即可。</p>
<ol><li><p><strong>安装 SDK</strong>:</p>
<div class="jb51code"><pre class="brush:ps;">npm install sa-sdk-javascript
</pre></div></li><li><p><strong>初始化并调用</strong>:</p>
<div class="jb51code"><pre class="brush:js;"><script>
// 初始化神策分析
var sensors = window["sensorsDataAnalytic201505"];
sensors.init({
server_url: "https://your-sensors-data-server.com/sa", // 数据接收地址
heatmap: {
// 可选:点击图/触达图配置
clickmap: "default",
scroll_notice_map: "default"
}
});
// 标识用户
sensors.login("user_123");
// 发送事件
sensors.track("ButtonClick", {
button_name: "提交按钮"
});
</script>
</pre></div></li></ol>
<p><strong>这是最推荐的方式</strong>,因为官方 SDK 处理了所有的兼容性、批量上报、重试机制等复杂问题。</p>
<p class="maodian"><a name="_lab2_4_0"></a></p><h3>神策请求参数详解</h3>
<p>无论用哪种方法,最终发送给神策服务器的 URL 通常包含以下核心参数:</p>
<table><thead><tr><th>参数</th><th>含义</th><th>示例</th></tr></thead><tbody><tr><td><code>project</code></td><td>项目名称</td><td><code>production</code></td></tr><tr><td><code>event</code></td><td>事件名称</td><td><code>$pageview</code> (页面浏览)</td></tr><tr><td><code>distinct_id</code></td><td>匿名/用户 ID</td><td><code>123456</code></td></tr><tr><td><code>properties</code></td><td>事件属性</td><td><code>{ "$url": "https://example.com" }</code></td></tr><tr><td><code>time</code></td><td>事件时间戳(可选)</td><td><code>1631234567890</code></td></tr></tbody></table>
<p class="maodian"><a name="_label5"></a></p><h2>总结与选择</h2>
<table><thead><tr><th>方法</th><th>适用场景</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td><strong>Image Beacon</strong></td><td>需要极致兼容性(如旧版 IE)</td><td>兼容性最好,无跨域问题</td><td>无法发送大量数据</td></tr><tr><td><strong>sendBeacon</strong></td><td>现代浏览器,页面卸载时上报</td><td>专为日志设计,不阻塞卸载</td><td>兼容性稍差(IE 不支持)</td></tr><tr><td><strong>fetch keepalive</strong></td><td>现代浏览器,需要更多控制</td><td>功能强大,可控制请求细节</td><td>兼容性比 sendBeacon 差</td></tr><tr><td><strong>官方 SDK</strong></td><td><strong>所有生产环境</strong></td><td><strong>功能完整,稳定可靠,省心</strong></td><td>需要引入 SDK</td></tr></tbody></table>
<p><strong>最终建议</strong>:对于生产环境,<strong>强烈推荐直接使用神策官方 SDK</strong>。它封装了所有最佳实践,您只需要关心业务逻辑(触发什么事件),而不需要关心网络传输的实现细节。只有在一些非常特殊的场景下,才需要考虑自己实现埋点请求。</p>
<p class="maodian"><a name="_label6"></a></p><h2>常见问题:神策使用 Image Beacon 是怎么获取用户数据的?</h2>
<p>像神策这种发送一个图片给后端,他们是怎么通过图片分析数据的?</p>
<p><strong>神策并不是真的去“分析”图片本身的内容</strong>(比如图片里的像素、颜色等),而是<strong>巧妙地利用浏览器请求图片的行为来传递数据</strong>。</p>
<p>可以这样理解:<strong>图片的 URL(地址)不是指向一张真实的图片,而实际上是一个精心编排的、包含了你所有埋点数据的“代码字符串”</strong>。</p>
<p>整个过程就像一个秘密通信协议:</p>
<p class="maodian"><a name="_lab2_6_1"></a></p><h3>核心原理:URL 即消息</h3>
<p>埋点系统会动态生成一个 1x1 像素的透明 GIF 图片的 URL,但这个 URL 的路径和参数部分被用来编码数据。</p>
<p><strong>举个例子:</strong> 一个真实的埋点图片请求 URL 可能长这样:</p>
<div class="jb51code"><pre class="brush:js;">https://data-sensors.com/sa.gif?project=my_web&event=pageview&user_id=123&page_url=https%3A%2F%2Fexample.com&button_name=checkout&time=1631234567890
</pre></div>
<p><strong>分解这个 URL:</strong></p>
<table><thead><tr><th>URL 部分</th><th>作用</th><th>说明</th></tr></thead><tbody><tr><td><code>https://data-sensors.com/sa.gif</code></td><td><strong>接收端点</strong></td><td>神策服务器的地址,路径叫 <code>sa.gif</code> 只是为了<strong>伪装</strong>成图片。</td></tr><tr><td><code>?project=my_web</code></td><td><strong>查询参数(数据载体)</strong></td><td>从问号 <code>?</code> 开始的部分才是关键!这些参数就是埋点数据。</td></tr><tr><td><code>&event=pageview</code></td><td>事件名称</td><td>告诉服务器用户进行了“页面浏览”这个行为。</td></tr><tr><td><code>&user_id=123</code></td><td>用户标识</td><td>告诉服务器是哪个用户做的。</td></tr><tr><td><code>&page_url=...</code></td><td>事件属性</td><td>告诉用户是在哪个页面做的。</td></tr><tr><td><code>&time=...</code></td><td>时间戳</td><td>告诉服务器事件发生的确切时间。</td></tr></tbody></table>
<p>当浏览器尝试加载这个“图片”时,会发生以下事情:</p>
<ol><li><strong>浏览器</strong>:认为这是一个普通的图片资源,向 <code>https://data-sensors.com/sa.gif</code> 发起一个 <strong>HTTP GET 请求</strong>。</li><li><strong>神策服务器</strong>:<ul><li>接收到这个请求,<strong>完全忽略</strong>请求的是 <code>.gif</code> 这个事实。</li><li>服务器端的程序(如 Nginx、Node.js、Java 等)会<strong>解析 URL 中的查询参数</strong>(即 <code>?</code> 后面的部分)。</li><li>将这些参数(<code>project=my_web</code>, <code>event=pageview</code>…)解析成结构化的 JSON 数据。</li><li><strong>将这些数据存入数据库或大数据平台</strong>,用于后续的分析和计算。</li><li>最后,服务器返回一个<strong>极其微小的 1x1 像素的透明 GIF 图片</strong>作为响应内容(就是你响应头里 <code>content-type: image/gif</code> 对应的内容)。</li></ul></li><li><strong>浏览器</strong>:接收到这个微小的图片数据,并渲染它(当然,在页面上你什么都看不到)。</li></ol>
<p class="maodian"><a name="_lab2_6_2"></a></p><h3>为什么选择用图片(Image Beacon)?</h3>
<p>这种技术被称为 <strong>“Image Beacon”</strong>(图片信标),它被广泛采用是因为有诸多优点:</p>
<table><thead><tr><th>优点</th><th>解释</th></tr></thead><tbody><tr><td><strong>没有跨域问题</strong></td><td>图片资源不受同源策略的限制,可以从任何域名下加载。这是最大的优势。</td></tr><tr><td><strong>兼容性极好</strong></td><td>所有浏览器,包括非常古老的版本,都支持图片加载。</td></tr><tr><td><strong>简单可靠</strong></td><td>实现起来非常简单,只需要创建一个 <code>Image</code> 对象并设置 <code>src</code> 属性即可。</td></tr><tr><td><strong>不阻塞页面</strong></td><td>图片加载是异步的,不会阻塞页面的渲染或卸载过程。</td></tr><tr><td><strong>开销极小</strong></td><td>请求的图片只有 1x1 像素,几乎是所有网络请求中数据量最小的,对性能影响微乎其微。</td></tr></tbody></table>
<p class="maodian"><a name="_lab2_6_3"></a></p><h3>与现代方法的对比</h3>
<p>虽然 Image Beacon 非常经典,但现在也有更现代的技术:</p>
<table><thead><tr><th>技术</th><th>工作原理</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td><strong>Image Beacon</strong></td><td>伪装成图片的 GET 请求,数据在 URL 中</td><td>兼容性最好,无跨域问题</td><td>URL 长度有限制,不适合传输大量数据</td></tr><tr><td><strong>navigator.sendBeacon()</strong></td><td>HTML5 新 API,专门用于发送少量数据</td><td>即使页面关闭也会保证发送,不阻塞</td><td>兼容性稍差(IE 完全不支持)</td></tr><tr><td><strong>Fetch API</strong></td><td>使用 JavaScript 发起异步请求</td><td>功能强大,可控制性强</td><td>可能受跨域策略限制</td></tr></tbody></table>
<p class="maodian"><a name="_lab2_6_4"></a></p><h3>总结</h3>
<p>所以,神策并不是分析图片,而是<strong>挂羊头卖狗肉</strong>:</p>
<ul><li><strong>羊头(请求头)</strong>:<code>Content-Type: image/gif</code> -> 让浏览器以为这是个图片。</li><li><strong>狗肉(请求体和目的)</strong>:<code>?event=xxx&user_id=xxx</code> -> 在 URL 参数中携带数据,服务器解析后存入数据库。</li></ul>
<p>最终,您在海量数据中看到的<strong>用户行为分析报表、转化漏斗、留存分析</strong>等,全都是基于<strong>解析这些 URL 参数后存入数据库的数据</strong>计算出来的,与那张微小的透明图片本身没有任何关系。</p>
<p class="maodian"><a name="_label7"></a></p><h2>案例代码</h2>
<p>上面的案例代码如下:</p>
<div class="jb51code"><pre class="brush:js;"><!DOCTYPE html>
<html>
<head>
<title>Markdown Renderer</title>
<style>
button {
display: block;
margin: 20px;
font-size: 16px;
}
</style>
</head>
<body>
<div id="result"></div>
<button id="ImageBeacon">ImageBeacon添加埋点</button>
<button id="sendBeacon">Navigator.sendBeacon添加埋点</button>
<button id="keepalive">fetchKeepalive添加埋点</button>
<script>
// 将属性对象转换为URL参数字符串
function formatParams(params) {
return Object.keys(params)
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(params)}`)
.join("&");
}
// 使用 Image Beacon
document.getElementById("ImageBeacon").addEventListener("click", () => {
// 构建埋点URL和参数
const saUrl = "https://..."; // 数据接收地址
const project = "fosun_test1"; // 您的项目名
const event = "ButtonClick"; // 事件名称,如 'PageView', 'ButtonClick'
const distinctId = "user_123"; // 用户ID
const properties = {
// 事件属性
$url: window.location.href,
button_name: "提交按钮"
};
const finalUrl = `${saUrl}?project=${project}&event=${event}&distinct_id=${distinctId}&${formatParams(properties)}`;
// 创建Image对象发送请求
const beacon = new Image(1, 1); // 创建一个1x1像素的图片
beacon.src = finalUrl; // 设置src即发起GET请求
// 可选的错误处理
beacon.onerror = function () {
console.error("Sensors beacon request failed.");
};
beacon.onload = function () {
console.log("Sensors beacon request successful.");
};
});
// 使用 sendBeacon 发送数据
document.getElementById("sendBeacon").addEventListener("click", () => {
// 构建数据对象
const data = {
project: "fosun_test1",
event: "ButtonClick",
distinct_id: "user_123",
properties: {
$url: window.location.href,
button_name: "提交按钮"
}
};
// 注意:sendBeacon 通常以 POST 方式发送字符串化的JSON
const blob = new Blob(, { type: "application/x-www-form-urlencoded" });
const success = navigator.sendBeacon("https://...", blob);
if (success) {
console.log("Beacon enqueued successfully!");
} else {
console.error("Beacon failed to queue.");
}
});
// 使用 fetchKeepalive 发送数据
document.getElementById("keepalive").addEventListener("click", () => {
const data = new URLSearchParams();
data.append("project", "your_project_name");
data.append("event", "ButtonClick");
data.append("distinct_id", "user_123");
data.append(
"properties",
JSON.stringify({
$url: window.location.href,
button_name: "提交按钮"
})
);
fetch("https://...", {
method: "POST", // 也可能是GET,取决于神策服务器的配置
body: data,
keepalive: true, // 关键参数:确保请求在页面卸载后仍能继续
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}).catch((error) => {
console.error("Fetch beacon failed:", error);
});
});
</script>
</body>
</html>
</pre></div>
<p class="maodian"><a name="_label8"></a></p><h2>总结</h2>
頁:
[1]