如何判断你的搜索控制台中的展示数据是人为操作产生的,还是由机器自动生成的
您的Search Console报告显示,在90天的时间里,某个页面在谷歌搜索结果的第一页获得了3,068次展示机会,但在同一时期内,该页面并没有获得任何点击量。 通常人们会认为这个页面存在点击率低的问题,因此就会修改标题、优化元描述,然后等待效果出现。但实际上这种分析很可能是错误的,据此采取行动只会浪费精力。因为根本没有人会进行这样的搜索操作,所以那些数据也就没有任何实际意义。 本教程将教您如何区分网站所获得的两种类型的展示机会。您可以使用自己的Search Console数据运行一个简单的脚本,根据搜索排名将这些展示机会分为不同的类别,读取查询日志以分析机器生成的搜索请求信息,并将可疑页面
您的Search Console报告显示,在90天的时间里,某个页面在谷歌搜索结果的第一页获得了3,068次展示机会,但在同一时期内,该页面并没有获得任何点击量。
通常人们会认为这个页面存在点击率低的问题,因此就会修改标题、优化元描述,然后等待效果出现。但实际上这种分析很可能是错误的,据此采取行动只会浪费精力。因为根本没有人会进行这样的搜索操作,所以那些数据也就没有任何实际意义。
本教程将教您如何区分网站所获得的两种类型的展示机会。您可以使用自己的Search Console数据运行一个简单的脚本,根据搜索排名将这些展示机会分为不同的类别,读取查询日志以分析机器生成的搜索请求信息,并将可疑页面与同一网站上的对照页面进行对比分析。
“搜索排名区间”是指一组平均搜索位置,并不是一个具体的数字。这个脚本将搜索结果分为四个区间:前3个搜索结果、第1页的其他位置、第2页以及第3页及以后的位置。这种分类方法非常重要,因为单一的平均值无法反映实际情况——例如,某个页面可能在少数几次搜索中排名第8,而在其他大多数情况下排名却为第30。
对照页面就是同一网站上的另一个页面,您已经知道这个页面确实有真实用户会阅读它,因此它可以作为衡量可疑页面效果的标准。
通过这些分析,您最终就能分辨出哪些页面真正拥有真实的读者群体,哪些页面则没有;这样,您就可以停止为那些根本不存在的读者群进行优化工作了。
下面列出的所有数据都来自我的个人网站。
目录
先决条件
在开始之前,您需要准备以下条件:
一个已验证的Google Search Console账户,且该账户至少拥有90天的数据记录。免费账户即可满足需求。
Node.js 18或更高版本。该脚本使用了内置的`fetch`函数,因此不需要额外的HTTP库。
一个启用了Search Console API的Google Cloud服务账户,并且需要将该账户添加为您的账户的用户。请下载该账户的JSON密钥文件。
两个npm包:`google-auth-library`用于处理授权令牌,`tsx`用于直接运行TypeScript文件。
一个可疑页面和一个对照页面。可疑页面是指那些展示次数很多但点击量很少的页面;对照页面则是您网站中表现最好的文章,也就是您确定会有真实用户阅读的页面。
大约20分钟的时间来完成这些操作。
添加相应的依赖项,并将标准凭据变量设置为你的服务账户密钥路径:
npm i google-auth-library tsx
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your-service-account.json
如果你之前没有启用过该API,请在Google Cloud项目中开启“Google Search Console API”,然后在“设置”->“用户和权限”中将服务账户的电子邮件地址添加为Search Console的正式用户。
为什么会有机器生成的点击量
Google的AI模式和AI概要功能并不会通过直接运行用户输入的问题来生成答案。它们采用了一种称为“查询扩展”的技术:系统会将用户的查询语句分解成一系列更具体的子查询,分别从相应的数据源中获取结果,最后将这些结果合并起来形成最终的答案。
每一个子查询实际上都是针对真实索引进行的搜索操作。当其中一个子查询被执行并返回结果时,Search Console就会记录下这次搜索所对应的点击量。
这种点击量是真实的,对应的位置也是真实的。但是因为没有人真正看到了这些搜索结果页面,所以也就没有人会点击这些链接。这种“点击”现象的出现并不是因为你的标题不够吸引人,而是因为系统本身的设计使得这样的点击行为在技术上是不可能发生的。
这一点非常重要,因为常规的分析工具根本无法区分这些人工点击和机器生成的点击量。那些用于检测实际点击率与预期值之间差距的脚本,如果只选择那些实际点击率低于预期值的记录,就会把这些记录排在最前面——因为0除以任何正数都会得到最大的“差距”数值。这样一来,那些实际上表现最差的记录反而会被错误地视为表现最好的记录。
步骤1:获取页面数据统计信息
将这段代码保存为phantom-check.ts,这就是整个工具的实现代码。
#!/usr/bin/env npx tsx
import { GoogleAuth } from 'google-auth-library';
const SITE = process.argv[2];
const PAGE = process.argv[3];
const DAYS = Number(process.argv[4] ?? 90);
if (!SITE || !PAGE) {
console.error('使用方法:npx tsx phantom-check.ts <网站地址> <页面地址> [天数]');
process.exit(1);
}
// Search Console的数据会有大约两天的延迟,因此统计窗口的时间范围应设置为这两天。
const iso = (d: Date) => d.toISOString().slice(0, 10);
const endDate = iso(new Date(Date.now() - 2 * 864e5));
const startDate = iso(new Date(Date.now() - (DAYS + 2) * 864e5));
async function getToken(): Promise {
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/webmasters.readonly']
});
const client = await auth.getClient();
const token = await client.getAccessToken();
if (!token.token) throw new Error('无法生成访问令牌。');
return token.token;
}
type Row = { keys: string[]; clicks: number; impressions: number; ctr: number; position: number };
async function query(token: string, body: Record {
const url =
`https://www.googleapis.com/webmasters/v3/sites/${encodeURIComponent(SITE)}/searchAnalytics/query`;
const res = await fetch(url, {
-method: 'POST',
.headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
startDate,
endDate,
dimensionFilterGroups: [
{ filters: [{ dimension: 'page', operator: 'equals', expression: PAGE }] }
],
...body
})
});
if (!res.ok) throw new Error(`Search Console返回了HTTP错误代码 ${res.status}`);
const json = (await res.json()) as { rows?: Row[] };
return json.rows ?? [];
}
function band(position: number): string {
if (position <= 3) return '前3名';
if (position <= 10) return '页面第1部分';
if (position <= 20) return '页面第2部分';
return '页面第3部分及以后';
}
(async () => {
const token = await getToken();
const totals = await query(token, { dimensions: ['page'] });
const t = totals[0];
.console.log(`\n${PAGE}`);
-console.log(`统计窗口:从 ${startDate} 到 ${endDate},共${DAYS}天\n`);
if (!t) {
console.log('在此时间范围内没有记录到任何点击量。');
return;
}
_console.log(
`总点击量:${t.impressions}次 点击率:${t.ctr * 100}.00% 位置:${t.position.toFixed(1)}`
);
const rows = await query(token, { dimensions: ['query'], rowLimit: 1000 });
const bands = new Map>();
for (const r of rows) {
const b = band(r.position);
const cur = bands.get(b) ?? { imp: 0, clk: 0, queries: 0 };
cur.imp += r.impressions;
(cur.clk += r.clicks);
.cur.queries += 1;
bands.set(b, cur);
}
.console.log('各位置段的点击量分布');
for (const b of ['前3名', '页面第1部分', '页面第2部分', '页面第3部分及以后']) {
const v = bands.get(b);
if (!v) continue;
console.log(
` ${b.padEnd(15)} ${String(v.imp).padStart(6)}次 ` +
`${String(v.clk).padStart(4)}次 查询次数:${v.queries}`
);
}
const top = bands.get('前3名');
if (top && top.impressions >= 100 && top.clk === 0) {
.console.log(
`\n 结论:排名前3的位置中,有${top.impressions}次点击量,但都没有产生任何点击行为。`
);
console.log(' 这就是机器生成的统计结果。请查看下面的查询列表。');
}
-console.log('按点击量排序的前20个查询');
for (const r of rows.sort((a, b) => b.impressions - a.impressions).slice(0, 20)) {
console.log(
` ${r.keys[0].slice(0, 60).padEnd(60)}次 点击量:${String(r.impressions).padStart(5)}次 位置:${r.position.toFixed(1)}`
);
}
.console.log();
). 下面是该脚本的具体执行步骤。
该脚本会从命令行接收三个参数:你拥有的Search Console账户信息、想要分析的特定页面,以及要回顾多少天的数据。默认情况下,回顾的时间为90天。如果你省略了其中任何一个参数,脚本会输出一条使用说明后退出,因为缺少这两个参数的话,后续的所有操作都将毫无意义。
接下来,脚本会确定数据查询的时间范围。由于Search Console的数据会有大约两天的延迟,因此脚本会在今天之前的两天结束这个时间范围的统计。如果在今天结束时进行统计,就会得到不完整的数据,从而导致最近一段时间的统计数据显示得不准确。iso()这个辅助函数会将JavaScript日期对象转换为API所期望的YYYY-MM-DD格式字符串。
getToken()这个函数负责处理身份验证。它会创建一个GoogleAuth客户端,并设置其权限范围为webmasters.readonly,然后使用你的服务账户密钥来获取一个临时访问令牌。由于该权限范围仅为只读权限,因此脚本只能查看你的账户信息,而无法对其进行任何修改。如果Google没有返回令牌,脚本会抛出错误,而不会继续执行并使用空的Authorization头信息进行请求。
query()这个函数才是真正与API进行交互的环节。它会使用包含令牌的Authorization: Bearer头部,向你的账户对应的searchAnalytics/query接口发送POST请求。其中关键的参数是dimensionFilterGroups:这个参数中包含了一个针对page维度的过滤条件,其操作符为=。正是这个过滤条件使得原本针对整个网站的统计结果能够转化为针对某个特定URL的详细数据。如果没有这个过滤条件,你就无法获取到关于某个具体页面的详细信息,因此本文中的各种对比分析也就无从进行了。
随后,脚本会发起两次独立的请求。这两次请求之间的时间间隔正是本文的重点所在。第一次请求会获取page维度的数据,由于过滤条件已经将结果范围缩小到了某个特定的URL,因此返回的结果会是一条包含该页面的总点击次数、总展示次数、点击率以及平均排名等信息的汇总行。第二次请求会获取query维度的数据,并设置结果条目的数量上限为1000条,这样就能得到产生那些展示次数的具体搜索记录。在汇总报告中,某个页面可能看起来很普通,但在搜索记录列表中,它很可能就是被机器筛选出来的。
band()这个函数会将每次搜索的平均排名分为四个不同的区间,然后脚本会生成两份报告:一份是按照排名区间分类的展示次数统计结果,另一份则是展示次数最多的20次搜索记录。如果某个页面在指定的时间范围内没有任何展示次数,脚本也会明确指出这一点并停止后续操作。
你可以将这个脚本应用到你怀疑有问题的页面上,使用方法如下:
npx tsx phantom-check.ts https://your-site.com https://your-site.com/your-suspect-page 90
例如,当我使用这个脚本分析我的文章“ChatGPT和Perplexity如何决定引用哪些来源”时,得到的结果如下:
https://chudi.dev/blog/aeo-answer-engine-optimization-explained
窗口时间:2026-05-19至2026-08-17(共90天)
总计:7480次展示,7次点击,平均排名8.5,点击率0.09%在7,480次展示中仅有7次点击,这意味着点击率为0.09%。平均排名为8.5,位于首页的中间位置。
如果你更喜欢通过Search Console界面来查看这些数据,同样的结果也会出现在那里。这个脚本的存在就是为了让你能够在第4步中直接进行对比分析,而无需手动逐一查看这两个指标。
如果仅看这些数据,你可能会认为这个页面的排名还不错,但转化率却很低。正是这种错误的结论导致了不必要的标题修改。页面层面的平均数据掩盖了实际的分布情况,而真正的答案就在这种分布规律之中。
步骤2:按位置区间划分展示次数
这个脚本已经完成了这项工作。请阅读它输出的结果中的下一部分:
位置区间
前3位 100次展示 0次点击 16次查询
页面其余部分 3068次展示 0次点击 92次查询
第2页 110次展示 0次点击 25次查询
第3页及以后 82次展示 0次点击 23次查询
结论:前3位位置上的100次展示并未产生任何点击。
这就是机器分析得出的结果。请查看下面的查询列表。
看看第二行数据。在Google首页上,除了前3位之外,这个页面在92次不同的查询中获得了3,068次展示机会,但仍然没有产生任何点击。
为了便于理解:通常情况下,排名在4到10位的页面会获得2%到10%的点击量。既然这个页面获得了3,068次展示机会,那么预计应该会有60到300次点击才对。但实际上点击次数为零。这说明标题并没有问题——因为即使标题不佳,也至少还会有一些点击产生的。
另外,请注意前3位位置上的100次展示同样没有产生任何点击。对于人类用户来说,排名在前3位的页面通常会有10%到40%的转化率。既然有100次展示机会,就应该至少会产生一些点击才对。
所有位置区间的点击次数都为零,这就是典型的机器分析结果。人类的搜索行为往往比较分散,因此会有一些点击量被遗漏;而机器生成的搜索数据则更加整齐,所以不会出现点击量缺失的情况。
在继续进行分析之前,请注意一点:各个位置区间内的展示次数总和为3,360次,而页面的总展示次数却是7,480次;同时,页面上显示的点击次数仅为7次,而每一条查询记录都显示没有点击发生。这并不是脚本的错误。Search Console会隐藏那些不足以识别具体用户的查询记录,因此查询数据与页面数据显示出的总数并不一定一致。使用这些位置区间数据主要是为了了解其分布情况,而不是将其作为完整的统计数据来使用。
步骤3:查看查询列表
这些排名信息表明某些地方出了问题,而查询列表则能告诉你具体是哪里出了问题。
以下是从该页面获取的实际数据记录,完全按照脚本输出的格式呈现。由于输出列的限制,每条记录的文本长度被截断了,最多只显示60个字符:
按点击量排名前20的查询:
如何让ChatGPT和Perplexity这类问答引擎决定该引用哪些来源? 1313次展示 0次点击 排名8.7
一种能够检测ChatGPT从我的网站中引用了哪些URL的工具 206次展示 0次点击 排名3.7
作为北美一家中型企业的SEO负责人,我应该怎么做? 193次展示 0次点击 排名6.2
最基本的AO优化方案是什么? 128次展示 0次点击 排名3.3
如何提高我的网站在问答引擎中的可见度? 115次展示 0次点击 排名4.5
一种能解释ChatGPT为何会改变引用来源的工具 106次展示 0次点击 排名5.6
与AI内容引用标准相关的问答引擎优化方法 105次展示 0次点击 排名7.9
AI内容引用标准在AO优化中的应用 95次展示 0次点击 排名8.3
生成式引擎优化技术究竟是如何工作的? 61次展示 0次点击 排名4.6
一种能解释为什么某个页面不再被引用的工具 60次展示 0次点击 排名8.7
在问答引擎开始引用你的内容之前,它们必须先满足什么条件? 53次展示 0次点击 排名4.8
为什么引用次数对AO优化如此重要? 47次展示 0次点击 排名6.7
请总结https://wildseo.co/网站上的5个关键要点。 44次展示 0次点击 排名2.8
一种用于诊断为什么我的页面不再被ChatGPT引用的工具 43次展示 0次点击 排名8.6
哪种GPT模型使用了与……相同的全局基础设施? 43次展示 0次点击 排名8.3
与AI内容引用标准相关的问答引擎源选择方法 40次展示 0次点击 排名8.7
与AI内容引用标准相关的问答引擎源选择方法 31次展示 0次点击 排名9.9
生成式引擎优化技术究竟是如何工作的? 27次展示 0次点击 排名4.2
问答引擎的引用数据 26次展示 0次点击 排名16.4
最基本的AO优化方案是什么? 25次展示 0次点击 排名1.0
Search Console显示的这些记录没有被截断,因此值得一看——因为以特定人物视角构建的查询语句在完整形式下更容易被识别。
第一条记录值得特别注意。这条查询内容其实就是我自己的标题,几乎是一字不差地复述了我的标题。那些已经选择了你的页面的用户,很可能会搜索你的标题,这就是为什么在某些情况下,排名最高的记录往往就是你的网站本身。这条记录获得了1,313次展示次数,但最终没有产生任何点击结果。
现在,让我们将这些情况与人们实际在搜索框中输入的内容进行对比。有四种输入方式会暴露机器的识别机制:
1. 完整的自然语言句子,包括标点符号。人类通常会直接输入“aeo tools”这样的关键词进行搜索,而不会像这样写:“作为北美一家中型公司的SEO负责人……”这种带有特定身份定位的表述会被系统视为任务指令,最终产生了193次搜索记录。 2>指令性语言而非疑问句。像“请从https://wildseo.co/中提取5个关键信息”这样的内容并不是真正的搜索请求,而是人类向助手下达的任务,助手才会去执行搜索操作。这类请求排在第2.8位,共产生了44次搜索记录。 3>几乎完全相同的短语变体。观察以“ai content citation criteria answer engine”开头的那些搜索记录,前20条中有4条其实是同一个短语的不同排列形式,比如“optimization”、“optimization aeo”以及“source selection”,它们分别出现在不同的位置上。人类通常只会输入一次这样的关键词,而系统则会以多种方式重复生成类似的请求,因此每种变体都会被单独记录为一次搜索记录。 4>机器生成的原始文本。上面提到的两条记录其实属于测验题目,并非真正的搜索请求,例如“在AI系统能够引用你的内容之前,它们必须先满足什么条件……”以及“哪种GPT模型使用了与……相同的全局基础设施”。在排名前20的搜索结果列表下方,这类机器生成的文本更加明显。由于脚本会检索多达1,000条记录,因此如果将`slice(0, 20)`的范围扩大,就能看到所有这些内容。在我的示例中,还包含了字符串`chatgpt://generic-entity?number=6`、简单的`yes`、占位符`yoursite.com`、以“true or false?”开头的题目、德语的完整句子查询,以及助手系统生成的提示信息片段“context: location: united states (not for language). do not in…”。显然,没有人会把这些内容输入到Google搜索框中。如果你发现其中某一种情况,也许只是巧合;但如果在同一页面上看到了这四种现象,那么就可以确定这是由系统重复生成的请求带来的流量。
在Search Console界面中还可以进行另一个检查。打开相关页面,查看“搜索显示”这一选项卡。“搜索显示”功能会根据结果类型对搜索记录进行分类,比如是AI概览、丰富结果、视频还是常见问题解答等,而不仅仅是普通的蓝色链接。不过,Google只会为那些它选择报告的结果类型填充这些数据,因此如果这个面板为空,也不一定意味着什么。
对于这个页面来说,“搜索显示”选项卡根本没有显示任何记录,这说明Google并没有将这个页面的AI概览结果纳入报告范围。当然,没有相关数据也不能说明什么问题,所以不要把空白的“搜索显示”面板当作某种证据。只需记下这一点,然后继续下一步操作即可。
步骤4:与对照页面进行对比
单独来看一个页面,很难得出有意义的结论。也许你的网站所有页面的标题都设计得不够恰当,而使用对照页面就可以排除这种可能性。
<请选择你最优秀的文章——也就是你知道真正会有人阅读的那篇文章,然后在同一窗口中使用相同的脚本来运行它:》npx tsx phantom-check.ts https://your-site.com https://your-site.com/your-best-page 90
同一个网站,使用相同的脚本,在同样的90天时间内,我发布的那篇模型对比文章《Fable 5 vs Opus 4.8: Every Reasoning Tier Benchmarked》的搜索数据如下:
https://chudi.dev/blog/claude-fable-5-vs-opus-4-8
测试时间:2026-05-19至2026-08-17(共90天)
总展示次数:30,704次;总点击次数:767次;平均排名:6.8位;点击率:2.50%
排名区间分布:
前3名:140次展示,6次点击,44次查询
页面其余部分:6,515次展示,273次点击,294次查询
第2页:790次展示,5次点击,127次查询
第3页及以后:222次展示,0次点击,65次查询
将这张图表与第一步中的图表进行对比。可以看到,这两条数据线是同步变化的,这正符合人类用户的搜索行为规律;而在“幻影页面”上,点击次数始终处于零值状态。
把这两组数据并排展示如下:
| 页面 | 展示次数及排名(4至10位) | 点击次数及排名(4至10位) |
|---|---|---|
| "ChatGPT与Perplexity如何决定引用哪些来源" | 3,068 | 0 |
| "Fable 5 vs Opus 4.8: Every Reasoning Tier Benchmarked" | 6,515 | 273 |
展示次数大约是前者的两倍,但点击次数仅为273次;而另一篇文章的展示次数虽然也差不多,但点击次数却为零。这两个文章来自同一个域名,由同一位作者撰写,通过相同的发布流程,在同样的90天时间内使用相同的脚本进行测试。
这些查询语句在类型上存在差异,因此表现也不同。对照页面的搜索结果主要是针对一些简短的关键词片段产生的,例如“fable low vs opus high”获得了691次展示和38次点击,“fable high vs opus max”获得了280次展示和25次点击;而这些查询语句其实是人类用户会输入的完整句子。