把一段代码贴进网站 <head>,几秒后控制台就能看到访客——包括他们是从豆包、DeepSeek、通义千问还是 ChatGPT 过来的。这页讲清楚贴什么、为什么这么设计、怎么埋转化、以及数据没出来时怎么查。
Paste one snippet into your site's <head> and visitors show up in the console within seconds — including which AI engine referred them (Doubao, DeepSeek, Qwen, ChatGPT and more). This page covers what to paste, why it is built this way, how to track conversions, and what to check when data does not appear.
<head>——越靠前越好,这样访客一进页面就开始计时。一次性工作,之后我们更新采集逻辑你不用再动。
Paste it into <head> — as early as possible, so engagement timing starts the moment the page opens. This is a one-off; you never need to touch it again when we ship updates.
网站是 Vue / React / Next.js 这类单页应用也一样贴,路由切换会自动算作新的页面浏览,不需要额外配置。 Single-page apps (Vue / React / Next.js) work the same way — route changes are counted as page views automatically, with no extra configuration.
把 VG-XXXXXXXX 换成你自己的 Tag ID(控制台里直接复制的已经替换好了):
Replace VG-XXXXXXXX with your own Tag ID (the copy in the console already has it filled in):
<script>(function(w,d,s,id){
w.geotag=w.geotag||function(){(w.geotag.q=w.geotag.q||[]).push(arguments)};
w.geotag('config',id);
var js=d.createElement(s);js.async=1;
js.src='https://cdn.jsdelivr.net/npm/@geo-analytics/tag@1/dist/vtag.js?id='+id;
js.onerror=function(){var f=d.createElement(s);f.async=1;
f.src='https://geo-analytics.info/vtag.js?id='+id;d.head.appendChild(f)};
d.head.appendChild(js)
})(window,document,'script','VG-XXXXXXXX');</script>
默认从 CDN(jsDelivr)加载,全球边缘节点就近分发。onerror 那一行是保险:万一 CDN 在你的访客网络下不可达,会自动换成我们自己的服务器加载,不会因为 CDN 抖动就丢一整段数据。这行只在加载失败时触发,正常情况下不会重复加载。
The script loads from a CDN (jsDelivr) by default, served from the nearest edge node. The onerror line is the safety net: if the CDN is unreachable on your visitors' network, loading falls back to our own server automatically, so a CDN hiccup does not silently cost you a chunk of data. It only fires on load failure — nothing is loaded twice in normal operation.
无论脚本从哪个地址加载,数据都上报到同一个地方——上报地址是编译进脚本里的固定值,不随加载来源变化。 Wherever the script is loaded from, data is reported to the same place — the collector endpoint is a constant compiled into the script and does not depend on the loading origin.
只用我们的服务器,不走第三方 CDN——公司安全策略不允许引入外部 CDN 时用这个: First-party only, no third-party CDN — use this if your security policy forbids external CDNs:
<script async src="https://geo-analytics.info/vtag.js?id=VG-XXXXXXXX"></script>
锁定版本 + 完整性校验——需要审计"这个文件永远不会变"时用。注意锁版后你不会自动收到修复,升级要手动改;而且 SRI 与浮动版本 @1 互斥,哈希必须对应那个确切版本。
Pinned version with Subresource Integrity — for audits that require the file to be immutable. Note that pinning means you no longer receive fixes automatically, and SRI is incompatible with the floating @1 tag: the hash must match that exact version.
<script async
src="https://cdn.jsdelivr.net/npm/@geo-analytics/tag@1.0.0/dist/vtag.js?id=VG-XXXXXXXX"
integrity="sha384-…" crossorigin="anonymous"></script>
早期贴码用的全局函数名是 VigilathTag。它继续有效,不需要动——新版脚本把这个名字保留为永久别名,你页面上已有的 VigilathTag('event', …) 调用照常记录。想换成新贴码也可以,两者不会冲突。
Earlier snippets used the global name VigilathTag. It still works and needs no change — the current script keeps that name as a permanent alias, so existing VigilathTag('event', …) calls on your pages keep recording. You may switch to the new snippet at any time; the two do not conflict.
贴上就有,不用写一行代码: These require no code beyond the snippet:
| 事件Event | 什么时候触发When it fires |
|---|---|
first_visit | 访客第一次到访A visitor's first ever visit |
session_start | 每段会话开始At the start of each session |
page_view | 每次页面浏览,含单页应用的路由切换Every page view, including SPA route changes |
scroll | 页面滚动到 90%Page scrolled to 90% |
click | 点击跳出本站的外链A click on an outbound link |
user_engagement | 页面处于可见状态的累计时长(换页或离开时结算)Accumulated visible time, settled on navigation or unload |
这是 GEO Tag 与普通统计工具的区别:很多 AI 引擎跳转时不带 referrer,普通工具只能把这些访客算成「直接访问」。我们额外识别引擎在链接上注入的参数指纹——比如通义千问会给出站链接拼 spm=5176.* 且完全不传 referrer,靠这个指纹仍能归因到它。
This is where GEO Tag differs from conventional analytics: several AI engines strip the referrer on outbound links, so ordinary tools bucket those visits as "Direct". We additionally recognise the parameters those engines inject — Qwen, for example, appends spm=5176.* and sends no referrer at all, and that fingerprint is enough to attribute it.
归因记两份:首次来源(这个访客最初是从哪来的,终身保留)和本次会话来源(这一段访问是从哪来的)。两者互不覆盖——同一个人上周从豆包来、今天从 DeepSeek 来,不会被算成一次。 Attribution is recorded twice: first touch (where this visitor originally came from, kept for their lifetime) and session source (where this particular visit came from). Neither overwrites the other — someone who arrived via Doubao last week and via DeepSeek today is not collapsed into one.
新会话在以下任一情况开始:30 分钟无操作、跨自然日、utm_source 变化,或入口引擎变化。最后一条很关键——不然同一个人 30 分钟内先后从豆包和千问进来,后一次的来源会被前一次盖掉。
A new session starts on any of: 30 minutes of inactivity, a day boundary, a change of utm_source, or a change of entry engine. That last one matters — otherwise a Qwen visit arriving inside a still-live Doubao session would lose its source entirely.
有些引擎连参数指纹也没有。实测 DeepSeek 网页版给出站链接加了 rel="noreferrer",referrer 被完全抹掉、也不注入任何参数——这种访客和「有人直接输网址」在数据里长得一模一样,谁也分不出来。
Some engines leave no parameter fingerprint either. Measured: DeepSeek on the web puts rel="noreferrer" on its outbound links, so the referrer is gone entirely and no parameter is added — such a visit is indistinguishable from someone typing your address by hand.
浏览器还会发一个 Sec-Fetch-Site 请求头,它属于 Fetch Metadata,不受 Referrer-Policy 影响,引擎抹不掉。JS 读不到请求头,但你的服务器可以把它回传给页面。在你的站点配置里加一行:
Browsers also send a Sec-Fetch-Site request header. It is Fetch Metadata and is independent of Referrer-Policy — an engine cannot suppress it. JavaScript cannot read request headers, but your server can echo it back to the page. Add one line to your site config:
add_header Server-Timing 'sfs;desc="$http_sec_fetch_site"' always;
加完之后,原本笼统的「来源未知」会拆成两档:外部来源(来源被抹)——确实从别的站点跳来,只是对方抹掉了身份;直接访问——地址栏或书签。不加也完全不影响其它功能,只是这一档拆不开。 With that in place, the single "Source unknown" bucket splits in two: External (source erased) — the visit really did come from another site that hid its identity; and Direct — a typed URL or bookmark. Skipping this changes nothing else; you simply keep the combined bucket.
要点:「外部来源」不等于「AI 带来的」。任何抹掉来源的入口都会落进去——App 内嵌浏览器、带 rel="noreferrer" 的普通链接都算。它给的是 AI 归因的上界,不是 AI 流量本身。我们宁可这么标,也不愿把它算成「直接访问」让你以为 AI 没带来人。
Important: "External" is not the same as "brought by AI". Any entry point that hides its origin lands there — in-app browsers, ordinary rel="noreferrer" links. It is an upper bound on AI referrals, not AI traffic itself. We would rather label it this way than count it as "Direct" and let you conclude AI sent you nobody.
自动采集不知道你的「成功」是什么。在关键动作上加一行,报表里就能看到哪个 AI 引擎真正带来了转化,而不只是带来了流量: Automatic collection cannot know what "success" means on your site. Add one line at the key action and the reports will show which AI engine actually drives conversions, not just traffic:
<button onclick="geotag('event','conversion',{conversion_name:'signup',value:199,currency:'CNY'})">
免费试用Start free trial
</button>
也可以在自己的 JS 里调用,比如表单提交成功后: You can also call it from your own JavaScript, for example after a form submits successfully:
geotag('event', 'conversion', { conversion_name: 'contact_form' });
geotag('event', 'sign_up', { plan: 'pro' }); // 自定义事件名也可以// custom event names work too
事件名和参数名建议用小写加下划线,并且全站统一——报表按名称聚合,signUp 和 sign_up 会被算成两个不同的事件。
Use lower_snake_case for event and parameter names, and keep them consistent across the site — reports aggregate by name, so signUp and sign_up count as two different events.
默认是「已同意」。脚本会在首次访问时往访客浏览器的 localStorage 写一个匿名 ID,用来区分新老访客——这发生在任何同意动作之前,属于 opt-out 模型。 The default is "granted". On the first visit the script writes an anonymous ID to the visitor's localStorage to distinguish new from returning visitors — this happens before any consent signal, i.e. an opt-out model.
如果你的访客受 GDPR / ePrivacy 或其它要求「先同意后收集」的法规约束,请在脚本运行之前声明拒绝,或者把整段贴码放在你的同意横幅后面按需加载: If your visitors fall under the GDPR/ePrivacy regime, or any other regime requiring prior consent, declare denial before the script runs, or load the whole snippet behind your consent banner:
geotag('consent', 'denied'); // 只发匿名计数,不写任何持久化标识// anonymous counts only, nothing persistent is stored
geotag('consent', 'granted'); // 用户同意后再打开// switch on after the user agrees
拒绝状态下仍会上报页面浏览等匿名计数,但不写 localStorage、访客 ID 记为 anon,因此无法区分新老访客。合规责任在站点方——你需要自行确认在你所处的司法辖区部署本工具的法律依据。
Under denial, anonymous counts such as page views are still reported, but nothing is written to localStorage and the visitor ID is recorded as anon, so new and returning visitors cannot be distinguished. Compliance is the site owner's responsibility — you must establish the lawful basis for deploying this in your jurisdiction.
采集的是第一方匿名 ID,存在访客自己浏览器里。不做跨站追踪、不用第三方 Cookie、不做设备指纹。IP 只在服务端解析成国家/省/市粗粒度地理位置,不保存原始 IP。 What is collected is a first-party anonymous ID stored in the visitor's own browser. No cross-site tracking, no third-party cookies, no device fingerprinting. IP addresses are resolved server-side to coarse geography (country / region / city) and are not stored in raw form.
打开你的网站,按 F12 打开浏览器开发者工具: Open your site and press F12 to open the browser developer tools:
vtag.js——应该看到一条状态 200 的请求
Network tab, filter for vtag.js — you should see one request with status 200typeof geotag——应返回 "function"
Console tab, type typeof geotag — it should return "function"collect——页面打开几秒后应有一条 204 的上报请求
Network tab, filter for collect — a request returning 204 should appear a few seconds after the page loads| 可能原因Likely cause | 怎么确认How to check |
|---|---|
| 域名不在白名单Domain not in the allowlist | 上报请求返回非 204。控制台「站点管理」确认你的域名已登记——写主域即可覆盖所有子域 The collect request returns something other than 204. Check that your domain is registered under Sites — the apex domain covers all subdomains |
| Tag ID 贴错或贴串了Wrong or mismatched Tag ID | Network 里 vtag.js?id= 后面的值,要和控制台里这个站点的 Tag ID 一致
The value after vtag.js?id= in the Network tab must match this site's Tag ID in the console |
| 广告拦截插件挡住了An ad blocker is blocking it | 用无痕窗口或关掉拦截插件再试。若确实被拦,改用只走我们服务器的写法 Retry in a private window or with the blocker disabled. If it is genuinely blocked, switch to the first-party-only snippet |
贴码没进 <head>Snippet not in <head> |
查看页面源代码搜 geotag;有些 CMS 会过滤掉 <script>,需要用它自带的「自定义代码」入口
View source and search for geotag; some CMSes strip <script> and require their own "custom code" field |
| CSP 拦截Blocked by CSP | 源代码里有贴码、Network 里却看不到 vtag.js 的请求 —— 多半是站点的内容安全策略挡下了。见下一节
The snippet is in the source but vtag.js never appears in the Network tab — most likely your Content Security Policy. See the next section |
如果你的服务器下发了 Content-Security-Policy 响应头,而其中没有放行本产品的域名,浏览器会在脚本加载前就把它拦掉。这种失败很难自己发现:页面一切正常、贴码明明在源代码里、也不报任何用户可见的错误,只是数据一条都不进来。
If your server sends a Content-Security-Policy header that does not allow our domains, the browser blocks the script before it ever loads. This failure is hard to spot on your own: the page works fine, the snippet is right there in the source, and nothing user-visible breaks — you simply get no data at all.
怎么确认:按 F12 打开控制台(Console),如果看到类似下面这行,就是它: How to confirm: press F12 and open the Console. A line like this means CSP is the cause:
Loading the script 'https://cdn.jsdelivr.net/npm/@geo-analytics/tag@1/dist/vtag.js'
violates the following Content Security Policy directive: "script-src 'self'"
需要放行两处,少一处都收不到数据: Two directives must be updated — missing either one still yields no data:
| 指令Directive | 要加什么What to add | 为什么Why |
|---|---|---|
script-src |
https://cdn.jsdelivr.nethttps://geo-analytics.info |
允许加载采集脚本。两个来源都要加 —— 贴码默认走 CDN,失败时回落自托管,只放行一个的话另一条路仍会被拦 Allows the script to load. Add both — the snippet uses the CDN by default and falls back to first-party hosting, so allowing only one still blocks the other path |
connect-src |
https://geo-analytics.info |
允许把数据发回来。只放行 script-src 是不够的 —— 脚本能跑起来,但上报会被 connect-src 拦掉,结果还是零数据
Allows the data to be sent. script-src alone is not enough — the script would run but reporting would be blocked by connect-src, still leaving you with no data |
改完大致长这样(其余指令保持你原来的设置,不需要放松): The result looks roughly like this — keep your other directives as they were, nothing needs loosening:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://geo-analytics.info;
connect-src 'self' https://geo-analytics.info;
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self';
base-uri 'self';
frame-ancestors 'none';
只想放行一个域名?把贴码改成只用自托管那一行(见前文「其它引用方式」),然后 script-src 与 connect-src 都只加 https://geo-analytics.info 即可。代价是失去 CDN 边缘加速 —— 对访客主要在国内的站点,这个代价很小。
Prefer to allow just one domain? Switch to the first-party-only snippet (see "Other ways to reference it" above), then add only https://geo-analytics.info to both script-src and connect-src. You lose CDN edge acceleration — a minor cost if your visitors are mostly in one region.
CSP 通常配在 Nginx / Apache 的响应头或应用框架的中间件里,不在页面 HTML 中。改完记得清缓存重试 —— 响应头常被 CDN 或反向代理缓存。 CSP is normally set in your Nginx/Apache response headers or a framework middleware, not in the page HTML. After changing it, clear caches and retry — response headers are often cached by a CDN or reverse proxy.
还是不对?把你的站点地址和遇到的现象发给我们,我们可以从服务端直接查这个 Tag ID 有没有收到数据。 Still not right? Send us your site address and what you are seeing — we can check server-side whether any data has arrived for your Tag ID.