概述
信息:Web Attribution 是一项企业功能。请联系您的客户成功经理,为您的账户启用此功能。
本指南介绍如何使用Google Tag Manager(GTM) 实施 Singular Web SDK。此方法非常适合无法直接访问网站代码的团队或希望通过 GTM 管理跟踪的团队。
重要! 请勿在同一网站上同时使用 GTM 和本地 JavaScript 实现。只选择一种方法,以防止重复跟踪和夸大事件计数。Singular 不会自动重复事件。
- 不要同时使用 Native JavaScript 和 Google Tag Manager 方法。只选择一种方法,以避免重复跟踪。
- Singular Web SDK 设计用于在用户浏览器中运行客户端。它需要访问本地存储(localStorage )和文档对象模型(DOM)等浏览器功能才能正常运行。请勿尝试在服务器端运行 SDK(例如,通过 Next.js SSR 或 node.js)--这将导致跟踪失败,因为服务器环境无法访问浏览器 API。
前提条件
在开始之前,请确保您拥有
-
SDK 密钥和 SDK 秘密:
- 在哪里可以找到它们:登录 Singular 账户,导航至 "开发工具" > "SDK 集成" > "SDK 密钥"。
-
产品 ID:
-
它是什么?您网站的唯一名称,最好使用反向 DNS 格式(如
com.website-name)。 - 为什么重要?该 ID 将网站作为 Singular 中的一个应用程序进行关联,并且必须与 Singular 中应用程序页面上列出的 Web AppbundleID相匹配。
-
它是什么?您网站的唯一名称,最好使用反向 DNS 格式(如
- 已在网站上配置Google 标签管理器。
- 在 GTM 中拥有编辑网站标签、触发器和变量的权限。根据定制情况,您可能需要了解如何创建自定义标记和配置标记排序。
- 如果您没有向 Google Tag Manager 发布更改的权限,则需要在测试后请求他人发布更改。
- 了解如何在 GTM 中预览和调试容器和标签。
- 要跟踪的事件列表。查看我们的奇异标准事件:完整列表和按垂直领域推荐的事件,以了解相关想法。
实施步骤
第 1 步:将 Singular Web 跟踪模板添加到 GTM 容器中
- 登录Google Tag Manager账户,选择网站容器。
- 转到标签>新建。
- 为标签命名:"奇异初始标签
- 单击 "标签配置"框开始标签设置。
- 选择标签类型:并选择"在社区模板库中发现更多标签类型"。
- 搜索"Singular "并选择"Singular Web Tracking"。点击"添加到工作区 "按钮。
第 2 步:初始化 SDK
-
在表单字段中填写以下内容:
- 将跟踪类型设为初始化
- 在 Api Key 字段中输入实际的 SDK 密钥
- 在 Secret 字段中输入实际的 SDK Secret
-
输入您的实际产品 ID。它应该看起来像:
com.website-name,并且应该与 Singular 平台应用程序页面上的 BundleID 值相匹配。小贴士:使用特定的产品 ID 进行测试
com.website-name.dev,并在推送到生产环境之前进行更新。这样可以将所有测试数据与 Singular 报告中的生产应用程序分开。 -
可选:
- 日志级别: SDK 调试日志到控制台的配置。默认为无。
- 会话超时: 在 SDK 创建新会话之前,用户必须在多长时间内处于非活动状态。Singular 发送用户会话以计算用户保留时间,并启用重新参与属性。默认值为 30 分钟。
- 跨子域跟踪
- 选择 "触发",配置一个触发器使该标签工作。
- 选择 "新建 "并为 Tigger 命名:"奇异初始触发器"。
- 单击 "触发器配置"并选择"页面视图 - 窗口已加载",然后单击"保存"。
- 再次单击"保存"以保存标签。
-
在标签页面点击"预览",测试是否触发了奇异初始化标签。
成功!如果在 "预览 "控制台的 "已触发标签 "部分看到 "奇异初始化标签",则说明已成功配置初始化标签。
重要! 对于 SPA(单页面应用程序),每次路由到不同页面时都应触发页面访问跟踪类型。不要在加载的第一个页面上调用页面访问,因为初始化已经报告了页面访问。
解决方案概述
- 使用自定义 JavaScript 变量来检测是否是首次加载页面。
-
配置 2 个标签:
- "单个初始化标签"(跟踪类型 = 初始化),仅在首次加载页面时触发。
- "奇异页面访问标签"(跟踪类型 = 页面访问)使用历史更改触发器在每次路径更改(不包括初始加载)时触发。
- 确保您的 SPA 将历史事件推送到路由更改的 dataLayer。
第 3 步:跟踪事件
初始化 SDK 后,当用户在网站上执行重要操作时,您可以跟踪自定义事件。
重要!Singular 不会阻止重复事件!开发人员有责任添加保护措施,防止页面刷新或重复。建议专门针对收入事件采用一些重复数据删除方法,以防止错误的收入数据。有关示例,请参阅下面的 "步骤 5:防止重复事件"。
基本事件跟踪
跟踪简单事件或使用有效的 JSON 添加自定义属性,以提供有关事件的更多上下文:
-
使用 Singular Web Tracking 模板为自定义事件创建新标签。
-
为事件标签命名。
-
选择跟踪类型 = 自定义事件
-
调整 "事件名称 "字段或设置适当的变量。
-
调整 "自定义用户 ID "字段或设置适当的变量。
-
如果需要在事件上传递键/值对,可调整 "属性"。
-
配置合适的触发器,以便仅在预期情况下触发标签。
-
保存触发器和标签,并在预览中进行测试。
跟踪转换事件
跟踪转换事件
-
使用 Singular Web Tracking 模板为自定义事件创建一个新标签。
-
命名转换事件标签。
-
选择跟踪类型 = 转换事件
-
调整 "事件名称 "字段或设置适当的变量。
-
调整 "自定义用户 ID "字段或设置适当的变量。
-
如果需要在事件上传递键/值对,可调整 "属性"。
-
配置合适的触发器,以便仅在预期情况下触发标签。
-
保存触发器和标签,并在预览中进行测试。
跟踪收入事件
使用收入信息跟踪转换事件,并添加可选属性以获得更多信息:
-
使用 Singular Web Tracking 模板为自定义事件创建新标签。
-
命名收入事件标签。
-
选择跟踪类型 = 收入事件
-
调整 "事件名称 "字段或设置适当的变量。
-
调整 "自定义用户 ID "字段或设置适当的变量。
-
调整 "货币 "字段或设置适当的变量。 将收入货币作为三个字母的 ISO 4217 货币代码,如 "USD"、"EUR "或 "INR"。
-
调整 "收入 "字段或设置适当的变量,收入必须以指定的货币表示。
-
如果需要在事件上传递键/值对,可调整 "属性"。
-
配置合适的触发器,以便仅在预期情况下触发标记。
-
保存触发器和标签,并在预览中进行测试。
常见事件实现模式
为页面加载事件创建 GTM 触发器
要使用 Google 标签管理器实施 Singular Web SDK,需要创建一个页面加载触发器,在页面加载时触发。
快速设置:在 GTM 中,导航至" 触发器 新触发器配置 ",然后选择"页面视图 "作为触发器类型。对于大多数实施,选择 "所有页面视图 "可在每次页面加载时触发触发器。
有关完整的设置说明:请参阅 Google 标签管理器官方文档:
为按钮点击创建 GTM 触发器
要通过 Google 标签管理器使用 Singular Web SDK 跟踪用户与按钮的交互,您需要创建点击触发器,当用户点击特定元素时触发。
快速设置:在 GTM 中,导航至" 触发器 新触发器配置",然后选择"所有元素 "来跟踪任何页面元素(按钮、链接、图片)的点击情况,或选择"仅链接 "来仅跟踪 HTML 锚点元素的点击情况。
启用点击变量:在创建点击触发器之前,通过进入变量配置内置变量并选择 "点击 "下的所有选项,启用 GTM 中的内置点击变量。
针对特定元素:为提高性能,请使用"部分点击 "而不是"所有点击",并根据点击 ID、点击类别或点击文本添加条件,以锁定特定按钮。
完整设置说明:请参阅 Google 标签管理器官方文档:
为表单提交事件创建 GTM 触发器
要通过 Google 标签管理器使用 Singular Web SDK 跟踪表单完成情况,您需要创建表单提交触发器,当用户在网站上成功提交表单时触发。
快速设置:在 GTM 中,导航至" 触发器 新触发器配置 ",然后选择"表单提交 "作为触发器类型。启用"检查验证",确保只对成功提交的表单进行跟踪,并考虑启用"等待标签",延迟时间为 2000 毫秒,以便在页面重定向前进行跟踪。
启用表单变量:在创建表单触发器之前,在 GTM 中启用内置表单变量,方法是进入变量配置内置变量并选择"表单 "下的所有选项。
性能优化:为了获得更好的性能,请选择"某些表单 "而不是"所有表单",并添加条件,以针对特定表单或出现表单提交的页面。
替代方法:请注意,现代 AJAX 表单可能无法与标准表单提交触发器配合使用。如果内置触发器无法触发,可考虑使用其他跟踪方法,如成功消息的元素可见性触发器或感谢页面跟踪。
完整设置说明:请参阅 Google 的官方 Tag Manager 文档:
步骤 4:设置客户用户 ID
您可以使用Singular SDK方法向Singular发送内部用户ID。
注意:如果您使用Singular的跨设备解决方案,您必须在所有平台上收集用户ID。
注意:如果多个用户使用一台设备,我们建议实施注销流程,为每次登录和注销设置和取消设置用户 ID。
提示!使用与移动 SDK 相同的客户用户 ID。这样可以实现跨设备归因,并提供跨平台用户行为的完整视图。
只要用户在未登录的情况下在网站上执行操作,事件就会以 Singular 生成的用户 ID 发送到 Singular。但在用户注册或登录后,您可以将事件与网站上使用的用户 ID(如散列电子邮件地址)一起发送到 Singular。
Singular会在用户级数据导出(参见导出归因日志)以及内部商业智能回传(参见配置内部商业智能回传)中使用用户ID。
向 Singular 发送用户 ID 有两种方法:
- 推荐: 如果知道网站打开时的用户 ID,请在初始化 SDK 时在初始化跟踪类型中设置用户 ID。这样,Singular 就能在第一次访问页面时获得用户 ID。
- 或者,你也可以在任何时候触发一个跟踪类型为登录的标签,通常是在验证发生之后。注意:调用此标签不会触发事件。它只会设置用户 ID,并将其添加到今后的任何事件触发器中!
要使用 Singular 设置用户 ID,请添加一个具有"登录 "跟踪类型的 Singular 标签:
- 在 Google 标签管理器账户中,单击标签 > 新建。
- 在 "标签配置 "窗口中,单击 "标签配置",然后在 "标签类型"菜单中选择 "Singular Web Tracking"。
- 在跟踪类型下,选择 "登录"。
- 在 "自定义用户 ID "下,输入包含用户 ID 的 Google 标签管理器变量。
- 单击 "触发 "并添加触发事件:用户登录或注册。
- 单击保存。
要取消设置用户 ID,请添加 "注销 "跟踪类型的标签:
- 在 Google 标签管理器账户中,单击标签 > 新建。
- 在 "标签配置 "窗口中,单击 "标签配置",然后在 "标签类型"菜单中选择 "奇异网络跟踪"。
- 在跟踪类型下,选择 "注销"。
- 单击 "触发 "并添加触发事件:用户注销。
- 单击保存。
注释
- 用户 ID 会一直存在,直到使用注销跟踪类型取消设置或用户删除本地存储。
- 关闭/刷新网站不会取消设置用户 ID。
- 在隐身等私密模式下浏览将阻止 Singular 持久化用户 ID,因为本地存储会在关闭浏览器时自动删除。
步骤 5:防止重复事件
在谷歌标签管理器中防止重复事件
重要!这是最常见的 GTM 执行错误之一。如果没有适当的保护措施,当用户刷新页面、返回导航或重新触发操作时,Singular Web SDK 事件会多次触发,从而严重影响您的指标。
GTM 中为什么会出现重复事件
Google 标签管理器在每次页面加载时都会评估触发器,当用户刷新感谢页面、重新提交表单或使用浏览器的后退/前进按钮导航时,标签就会重新触发。出现这种情况是因为 GTM 缺乏对自定义事件的内置会话感知。
GTM 重复数据删除方法
会话存储方法:在浏览器的 sessionStorage 中存储唯一的事件标识符,以跟踪在用户会话期间哪些事件已经触发。
自定义 JavaScript 变量:创建变量,在允许触发新事件之前检查 localStorage 是否有之前触发的事件。
触发条件:为触发器添加条件逻辑,防止在出现重复指标时触发事件。
实施步骤
创建存储变量:创建自定义 JavaScript 变量,根据事件参数和用户会话为每个 Singular 事件生成唯一标识符。
创建检查变量:创建另一个自定义 JavaScript 变量,用于检查当前事件标识符是否已存在于浏览器存储中。
添加触发条件:修改 Singular Web SDK 触发器,加入 "重复检查 "变量等于 "false "的条件。
触发后存储:在 Singular 事件成功触发后,使用 GTM 的标签序列功能触发自定义 HTML 标签,将事件标识符存储到 sessionStorage 中。
GTM 特定注意事项
标签排序:使用 "高级设置 "的 "标签排序 "功能,确保在主 Singular 标签完成后启动存储标签。
变量评估:请记住,自定义 JavaScript 变量在每次引用时都要进行评估,因此要对性能进行优化。
跨域限制:SessionStorage 是针对特定域的,因此如有需要,可采用基于 cookie 的解决方案进行跨域跟踪。
详细实施:请参考全面的 GTM 重复数据删除指南:
第 6 步:测试您的 GTM 实施
- 打开GTM 预览模式并加载您的网站。
- 检查
-
SDK 加载(网络请求
singular-sdk.js) - 事件按预期触发,且每个操作只触发一次
-
浏览器控制台中没有与
singular相关的错误 -
网络请求被发送到
sdk-api-v1.singular.net
- 使用浏览器开发工具中的 "网络"选项卡验证是否发送了正确的有效载荷
成功!如果您在网络请求中看到正确的 Singular 事件,并且没有重复,那么您就可以上线了!
第 7 步:实施网络到应用程序转发
网络到应用程序转发
使用 Singular Web SDK 跟踪从网站到移动应用的用户旅程,从而将准确的网络营销活动归因于移动应用的安装和重新吸引。 按照以下步骤设置网络到应用转发,包括为桌面用户提供 QR 码支持。
- 按照我们的《网站到移动应用归因转发指南》,为移动网络归因配置 Singular Web SDK。
-
用于桌面网络到应用程序跟踪:
-
创建二维码生成器清理标签
- 在 GTM 中导航到 "标签">"新建",然后选择 "自定义 HTML"。
-
添加代码:
- 注入您选择的 QRCode 库
-
从
window.singularSdk.buildWebToAppLink(baselink);获取网络到应用程序链接 - 从返回的链接生成 QRCode。
- 更新页面上的 QRCode 图像。
- 为标签命名:"Singular - QR 码生成器(清理
- 无需触发器:清理标签从主标签继承触发器
-
配置 Singular Init 标签:
- 点击标签配置
- 转到高级设置 > 标记排序
- 选中 "在[奇异初始标签]触发后触发一个清理标签"。
- 从下拉菜单中选择二维码生成器标签
- 保存标签,并在预览模式下进行测试。
-
创建二维码生成器清理标签
提示!移动应用内浏览器网页视图(如 Facebook、Instagram 和 TikTok 使用的视图)会导致用户移动到设备的本机浏览器时改变 Singular 设备 ID,从而扰乱归因。
为避免这种情况,请始终为每个广告网络使用正确的 Singular 跟踪链接格式:
高级主题
添加全局属性
Singular SDK可以让你定义自定义属性,这些属性将与应用程序发送的每个会话和事件一起发送到Singular服务器。这些属性可以代表关于用户、应用程序模式/状态或其他任何信息。
-
您最多可以将 5 个全局属性定义为有效的 JSON 对象。全局属性会被持久保存在浏览器
localstorage中,直到被清除或浏览器上下文发生变化。 -
每个属性名称和值的长度不超过 200 个字符。如果传递的属性名称或值较长,则会被截断为 200 个字符。
-
全局属性目前反映在 Singular 的用户级事件日志(请参阅导出属性日志)和回传中。
-
如果需要,全局属性可以在从 Singular 发送到第三方的回传中进行匹配。
Google 标签管理器初始化标签目前不支持在 SDK 初始化前设置全局属性。 不过,要在初始化后处理全局属性,必须创建自定义 HTML 标签并执行本地 JavaScript 函数:setGlobalProperties(),getGlobalProperties(),clearGlobalProperties() 。
创建用于处理全局属性的自定义 HTML 标签
-
在 Google 标签管理器界面,单击左侧导航菜单中的标签,然后单击新建按钮创建新标签。
-
单击标签配置,然后从可用标签类型列表中选择自定义 HTML。
-
在 HTML 字段中,粘贴全局属性方法代码。 参见下面的选项:
-
单击触发并为标签指定一个触发选项。
-
在顶部的标签名称字段中给标签起一个描述性的名称,如 "奇异--设置全局属性"。
设置全局属性后,全局属性将在所有事件中持续存在,直至取消或清除。
单击 "预览 "测试您的实现。在浏览器控制台中验证全局属性是否已设置。
高级配置请参阅 Google 官方的自定义 HTML 文档
用于设置全局属性的自定义 HTML 标签代码
<script>
/**
* Set a Singular global property before SDK initialization.
* Allows up to 5 key/value pairs. Optionally overwrites existing value for a key.
*
* @param {string} propertyKey - The property key to set.
* @param {string} propertyValue - The property value to set.
* @param {boolean} overrideExisting - Whether to overwrite the property if it already exists.
*/
// Example usage - customize these values for your implementation
window.singularSdk.setGlobalProperties('user_type', 'premium', true);
window.singularSdk.setGlobalProperties('app_version', '2.1.0', false);
</script>
用于获取全局属性的自定义 HTML 标签代码
<script>
// Get all global properties as a JSON object
var globalProps = window.singularSdk.getGlobalProperties();
console.log('Current global properties:', globalProps);
</script>
用于清除全局属性的自定义 HTML 标签代码
<script>
// Clears all Global Properties.
// Example usage
clearGlobalProperties();
</script>
有机搜索跟踪
创建有机搜索跟踪设置标签
至关重要!- 此标签必须在 Singular SDK 初始化之前触发!
此自定义 HTML 标签修改文档 URL,以添加 Singular Web SDK 在初始化过程中需要读取的有机搜索跟踪参数(wpsrc 和 wpcn)。为确保正确的执行顺序,标签排序至关重要。
本示例是启用有机搜索跟踪的变通方法。代码只能作为示例使用,由网站开发人员根据营销部门的需求进行更新和维护。 有机搜索跟踪可能因广告商而异。 请查看示例,并根据您的需求进行调整。
为何使用?
-
确保正确跟踪有机搜索访问,即使没有广告系列参数。
-
将Singular "来源 "参数
wpsrc与(referrer)值和 "广告系列名称 "参数wpcn作为 "OrganicSearch "附加到 URL,以明确归属。 -
将当前 URL 和推荐人存储在
localStorage中,以便日后使用。 -
纯 JavaScript,零依赖性,易于集成。
工作原理
-
检查页面 URL 是否有来自(Google、Facebook、TikTok、UTM 等)的已知营销活动参数。
-
如果不存在营销活动参数,且推荐人是搜索引擎,则附加:
-
wpsrc(以推荐人作为其值 -
wpcn(以 OrganicSearch 作为其值)
-
-
更新浏览器中的 URL,无需重新加载页面。
-
将
localStorage中的当前 URL 和推荐人存储为sng_url和sng_ref。
使用方法
- 在 GTM 中导航到 "标签">"新建",然后单击 "标签配置",再选择 "自定义 HTML"。
- 如下所示粘贴完整的 JavaScript 代码。
-
跳过此标签的触发器创建。由于此标签必须在奇异初始化标签之前运行,我们将使用标签排序功能将其配置为在初始化之前触发。
- 使用一个描述性的名称,如 "Singular - 有机搜索设置"。
- 打开 Singular Web SDK 初始化标签。
- 点击标签配置
- 转到高级设置 > 标签排序。
- 选中 "在[Singular Init Tag]触发前触发一个设置标签"。
- 从下拉菜单中选择 "Singular - 有机搜索设置 "标签。
- 建议选中 "如果[设置标签]失败,则不触发[奇异初始化标签]",以防止初始化时 URL 修改不完整。
- 保存标签。
-
使用 GTM 的预览模式进行验证:
- 设置标签首先触发并修改 URL。
- Singular Init 标签第二个触发并读取修改后的 URL 参数。
- 标签之间没有时间冲突。
对于高级标签排序:请参阅 Google 官方文档:
<script>
(function() {
// singular-web-organic-search-tracking: setupOrganicSearchTracking.js
// Tracks organic search referrals by appending wpsrc and wpcn to the URL if no campaign parameters exist and the referrer is a search engine.
// Configuration for debugging (set to true to enable logs)
var debug = true;
// List of campaign parameters to check for exclusion
var campaignParams = [
'gclid', 'fbclid', 'ttclid', 'msclkid', 'twclid', 'li_fat_id',
'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'wpsrc'
];
// Whitelist of legitimate search engine domains (prevents false positives)
var legitimateSearchEngines = new Set([
// Google domains
'google.com', 'google.co.uk', 'google.ca', 'google.com.au', 'google.de',
'google.fr', 'google.it', 'google.es', 'google.co.jp', 'google.co.kr',
'google.com.br', 'google.com.mx', 'google.co.in', 'google.ru', 'google.com.sg',
// Bing domains
'bing.com', 'bing.co.uk', 'bing.ca', 'bing.com.au', 'bing.de',
// Yahoo domains
'yahoo.com', 'yahoo.co.uk', 'yahoo.ca', 'yahoo.com.au', 'yahoo.de',
'yahoo.fr', 'yahoo.it', 'yahoo.es', 'yahoo.co.jp',
// Other search engines
'baidu.com', 'duckduckgo.com', 'yandex.com', 'yandex.ru',
'ask.com', 'aol.com', 'ecosia.org', 'startpage.com',
'qwant.com', 'seznam.cz', 'naver.com', 'daum.net'
]);
// Extract main domain from hostname (removes subdomains)
function getMainDomain(hostname) {
if (!hostname) return '';
var lowerHost = hostname.toLowerCase();
// Handle special cases for known search engines with country codes
var searchEnginePatterns = {
'google': function(host) {
// Match google.TLD patterns more precisely
if (host.indexOf('google.co.') !== -1 || host.indexOf('google.com') !== -1) {
var parts = host.split('.');
for (var i = 0; i < parts.length - 1; i++) {
if (parts[i] === 'google') {
return parts.slice(i).join('.');
}
}
}
return null;
},
'bing': function(host) {
if (host.indexOf('bing.co') !== -1 || host.indexOf('bing.com') !== -1) {
var parts = host.split('.');
for (var i = 0; i < parts.length - 1; i++) {
if (parts[i] === 'bing') {
return parts.slice(i).join('.');
}
}
}
return null;
},
'yahoo': function(host) {
if (host.indexOf('yahoo.co') !== -1 || host.indexOf('yahoo.com') !== -1) {
var parts = host.split('.');
for (var i = 0; i < parts.length - 1; i++) {
if (parts[i] === 'yahoo') {
return parts.slice(i).join('.');
}
}
}
return null;
}
};
// Try specific patterns for major search engines
for (var engine in searchEnginePatterns) {
if (lowerHost.indexOf(engine) !== -1) {
var result = searchEnginePatterns[engine](lowerHost);
if (result) return result;
}
}
// Handle other known engines with simple mapping
var otherEngines = {
'baidu.com': 'baidu.com',
'duckduckgo.com': 'duckduckgo.com',
'yandex.ru': 'yandex.ru',
'yandex.com': 'yandex.com',
'ask.com': 'ask.com',
'aol.com': 'aol.com',
'ecosia.org': 'ecosia.org',
'startpage.com': 'startpage.com',
'qwant.com': 'qwant.com',
'seznam.cz': 'seznam.cz',
'naver.com': 'naver.com',
'daum.net': 'daum.net'
};
for (var domain in otherEngines) {
if (lowerHost.indexOf(domain) !== -1) {
return otherEngines[domain];
}
}
// Fallback: Extract main domain by taking last 2 parts (for unknown domains)
var parts = hostname.split('.');
if (parts.length >= 2) {
return parts[parts.length - 2] + '.' + parts[parts.length - 1];
}
return hostname;
}
// Get query parameter by name, using URL.searchParams with regex fallback for IE11
function getParameterByName(name, url) {
if (!url) url = window.location.href;
try {
return new URL(url).searchParams.get(name) || null;
} catch (e) {
if (debug) console.warn('URL API not supported, falling back to regex:', e);
name = name.replace(/[\[\]]/g, '\\$&');
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
var results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
}
// Check if any campaign parameters exist in the URL
function hasAnyParameter(url, params) {
for (var i = 0; i < params.length; i++) {
if (getParameterByName(params[i], url) !== null) {
return true;
}
}
return false;
}
// Improved search engine detection - only checks hostname, uses whitelist
function isSearchEngineReferrer(referrer) {
if (!referrer) return false;
var hostname = '';
try {
hostname = new URL(referrer).hostname.toLowerCase();
} catch (e) {
// Fallback regex for hostname extraction (IE11 compatibility)
var match = referrer.match(/^(?:https?:\/\/)?([^\/\?#]+)/i);
hostname = match ? match[1].toLowerCase() : '';
}
if (!hostname) return false;
// First check: exact match against whitelist
if (legitimateSearchEngines.has(hostname)) {
if (debug) console.log('Exact match found for:', hostname);
return true;
}
// Second check: subdomain of legitimate search engine
var hostParts = hostname.split('.');
if (hostParts.length >= 3) {
// Try domain.tld combination (e.g., google.com from www.google.com)
var mainDomain = hostParts[hostParts.length - 2] + '.' + hostParts[hostParts.length - 1];
if (legitimateSearchEngines.has(mainDomain)) {
if (debug) console.log('Subdomain match found for:', hostname, '-> main domain:', mainDomain);
return true;
}
// Try last 3 parts for country codes (e.g., google.co.uk from www.google.co.uk)
if (hostParts.length >= 3) {
var ccDomain = hostParts[hostParts.length - 3] + '.' + hostParts[hostParts.length - 2] + '.' + hostParts[hostParts.length - 1];
if (legitimateSearchEngines.has(ccDomain)) {
if (debug) console.log('Country code domain match found for:', hostname, '-> cc domain:', ccDomain);
return true;
}
}
}
if (debug) {
console.log('Hostname not recognized as legitimate search engine:', hostname);
}
return false;
}
// Main function to update URL with organic search tracking parameters
function setupOrganicSearchTracking() {
var url = window.location.href;
var referrer = document.referrer || '';
// Store URL and referrer in localStorage
try {
localStorage.setItem('sng_url', url);
localStorage.setItem('sng_ref', referrer);
} catch (e) {
if (debug) console.warn('localStorage not available:', e);
}
if (debug) {
console.log('Current URL:', url);
console.log('Referrer:', referrer);
}
// Skip if campaign parameters exist or referrer is not a search engine
var hasCampaignParams = hasAnyParameter(url, campaignParams);
if (hasCampaignParams || !isSearchEngineReferrer(referrer)) {
if (debug) console.log('Skipping URL update: Campaign params exist or referrer is not a legitimate search engine');
return;
}
// Extract and validate referrer hostname
var referrerHostname = '';
try {
referrerHostname = new URL(referrer).hostname;
} catch (e) {
if (debug) console.warn('Invalid referrer URL, falling back to regex:', e);
var match = referrer.match(/^(?:https?:\/\/)?([^\/]+)/i);
referrerHostname = match ? match[1] : '';
}
// Extract main domain from hostname
var mainDomain = getMainDomain(referrerHostname);
if (debug) {
console.log('Full hostname:', referrerHostname);
console.log('Main domain:', mainDomain);
}
// Only proceed if main domain is valid and contains safe characters
if (!mainDomain || !/^[a-zA-Z0-9.-]+$/.test(mainDomain)) {
if (debug) console.log('Skipping URL update: Invalid or unsafe main domain');
return;
}
// Update URL with wpsrc and wpcn parameters
var urlObj;
try {
urlObj = new URL(url);
} catch (e) {
if (debug) console.warn('URL API not supported, cannot modify URL:', e);
return;
}
// Set wpsrc to the main domain (e.g., google.com instead of tagassistant.google.com)
urlObj.searchParams.set('wpsrc', mainDomain);
// Set wpcn to 'Organic Search' to identify the campaign type
urlObj.searchParams.set('wpcn', 'Organic Search');
// Update the URL without reloading (check if history API is available)
if (window.history && window.history.replaceState) {
try {
window.history.replaceState({}, '', urlObj.toString());
if (debug) console.log('Updated URL with organic search tracking:', urlObj.toString());
} catch (e) {
if (debug) console.warn('Failed to update URL:', e);
}
} else {
if (debug) console.warn('History API not supported, cannot update URL');
}
}
// Execute the function
setupOrganicSearchTracking();
})();
</script>
跨子域跟踪
默认情况下,Singular 网站 SDK 会生成一个 Singular 设备 ID,并使用浏览器存储将其保存。由于该存储无法在子域之间共享,SDK 最终会为每个子域生成一个新 ID。
如果要跨子域持久化 Singular 设备 ID,可以使用以下选项之一:
方法 B(高级):手动设置单个设备 ID
如果不想让Singular SDK自动持久化设备ID,可以跨域手动持久化ID,例如使用顶级域cookie或服务器端cookie。ID值应该是Singular之前生成的有效uuid4格式的ID。
注:在调用 Init track-type 标签后,定义一个自定义 JavaScript 变量并调用 singularSdk.getSingularDeviceId(),即可读取 Singular 设备 ID。
常见的 GTM 实施问题
| 所需的设备标识符 | |
|---|---|
| 事件触发多次 | 完善或限制触发器,使用 "每页一次 "或添加条件 |
| 产品 ID 格式不正确 |
产品 ID 必须使用反向 DNS 符号 (com.company.site) |
| 未跟踪事件 |
检查事件名称的拼写和大小写;确保在事件标签触发前加载singularSDK |
| SDK 脚本被阻止 | 广告拦截器或限制性内容安全策略;如果问题持续存在,考虑转用本地 JS 实现 |
最佳实践
- 保持 GTM 井井有条:明确命名标签和触发器,并记录其用途。
- 定期审核未使用或遗留的标记。
- 尽量减少触发器的数量,以降低重复事件的风险。
- 更改后,一定要在 GTM 的预览模式中进行全面测试,然后再推送上线。
- 如果您使用基于 cookie 的跟踪(跨子域跟踪),请相应更新您的隐私政策。