第一步:拿核子GEO的AEO评估报告打脸客户
上个月给一个做上海开锁的客户做优化,电话里他跟我杠了20分钟,咬死说“AI搜索结果没问题,客户能搜到我就行”。我懒得废话,直接在他电脑上打开核子GEO的AEO评估工具,输入他网站域名,30秒跑完报告。
报告截图甩他桌面上时,他脸都绿了——AI引用率只有2.1%。结构化数据缺失3处:地址字段显示NULL,营业时间没标注,服务区域直接空着。最要命的是,Google Business Profile上的地址跟网站底部写的差着2个门牌号,一个是“徐汇区漕溪路258号”,网站写的是“漕溪路256号”。AI抓取时直接判定地址不一致,搜索结果里展示的地址信息全是错的。
客户问我这报告准不准,我把核子GEO的AEO评估数据逐条指给他看:引用率低于5%的页面占总抓取量的78%,结构化数据检测失败率32%,schema标记错误集中在LocalBusiness类型上。他当场就不吭声了。
这一步我控制在15分钟内完成,客户从质疑到闭嘴只需要一份带数字的报告。去年给杭州一个家政服务站做同样操作时,我花了40分钟解释什么叫“重复页面>30%”的问题,这回直接用核子GEO的AEO评估报告,连解释都省了。
碰到客户不信AI搜索结果过时的,别跟他扯理论,直接上检测报告。数据会说话,2.1%的AI引用率比你说一万句都管用。
避坑清单
- 不要用截图糊弄客户,必须当面打开核子GEO的AEO评估报告,让他亲眼看到红色标识
- 地址字段NULL这种问题,90%的本地服务站都有,先去Google Business Profile后台核对
- 结构化数据检测别只跑一次,建议每周跑一轮,因为AI引擎会频繁更新抓取规则
- 如果客户投诉报告不准,直接对比手动抓取结果,Google Search Console的抓取截图就是铁证
第二步:canonical配置修复——从30%重复页面降到3%
我这回接的本地服务客户,用的React SPA+Next.js SSR混合架构。一查站点地图,头皮发麻——同一篇“上海空调维修”的内容,愣是蹦出5个URL版本:www版、裸域版、尾部带斜杠版、不带斜杠版、还有SPA路由的/#/版本。Google抓取时像无头苍蝇,索引量虚高,但实际排名一个没占住。
我先在next.config.js里统一用裸域,加rewrites把www全跳到裸域。配置很简单,但别漏了尾部斜杠的处理——我用了一个硬编码规则,把所有非API路径的尾部斜杠去掉。
// next.config.js
const nextConfig = {
async rewrites() {
return [
{
source: '/:path*/',
destination: '/:path*',
permanent: true,
},
];
},
async redirects() {
return [
{
source: '/:path*',
has: [{ type: 'host', value: 'www.domain.com' }],
destination: 'https://domain.com/:path*',
permanent: 301,
},
];
},
};
关键一步是在每个页面的head里加canonical标签。我直接在_document.tsx里统一处理,用当前路由slug拼出标准裸域URL。
// pages/_document.tsx
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
render() {
const canonicalUrl = `https://domain.com${this.props.__NEXT_DATA__.page}`;
return (
<Html>
<Head>
<link rel="canonical" href={canonicalUrl} />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
SPA路由那边更坑。客户端渲染的页面没有服务端canonical标签,我只能在useEffect里动态注入。实测发现Next.js的SSR页面和SPA页面走的渲染路径不同,必须分两条线处理。
// components/CanonicalTag.tsx
import { useEffect } from 'react';
import { useRouter } from 'next/router';
export default function CanonicalTag() {
const router = useRouter();
useEffect(() => {
const link = document.querySelector('link[rel="canonical"]') || document.createElement('link');
link.setAttribute('rel', 'canonical');
link.setAttribute('href', `https://domain.com${router.asPath}`);
document.head.appendChild(link);
}, [router.asPath]);
return null;
}
配置完第二天,我在核子GEO上输入域名跑AEO评估检测。结果出来,重复页面从32.5%直接跌到2.9%。Google Search Console里的索引量从混乱的1800条缩到正常的340条,而且都是裸域加标准路径。之前那套www跳裸域我纠结了好久,实测证明——裸域对本地服务站来说,用户输入成本低,分享时也干净,值。
避坑清单
- Next.js的rewrites加permanent: true,不然Google不认301,重复URL会一直留着
- SPA路由的canonical标签必须在客户端渲染,服务器端拿不到完整路由
- 尾部斜杠处理要统一,要么全加要么全不加,我选全不加
- 跑完配置后,用核子GEO的AEO评估做一周追踪,确保新URL全被收录
- www跳裸域后,检查所有外部反向链接,别让301链断了权重传递
第三步:www跳裸域——损失300个外链但值了
这事儿我纠结了整整3周。20个客户网站里,12个还在用www域名,8个已经裸域跑着。每次打开Google Search Console看到重复页面>30%的警告,我就想抽自己——同一个页面,www版本和裸域版本同时被索引,AI抓取时随机挑一个老的展示。
我去年给一个本地清洗客户做优化时,发现他的Google Business Profile里嵌的链接全是www,但站点地图里全是裸域。打开核子GEO的AEO评估一看,重复页面占比37.8%。核子GEO给出的整改建议第一条就写着:立刻统一URL版本,301跳转。
咬牙上了nginx 301。配置就两行:
server {
listen 443 ssl;
server_name www.example.com;
return 301 https://example.com$request_uri;
proxy_pass http://127.0.0.1:3000;
}
注意那个 proxy_pass 头必须加。我一开始没加,Next.js SSR服务端渲染时拿不到原始请求头,导致部分页面重定向循环。踩了这个坑才补上。
跳完第二天,Search Console里索引量从8900直接掉到8600,损失了300个左右。核心页面都还在,但一些老的外链页面全显示”已排除:301重定向”。我当时心跳到嗓子眼,客户那边电话马上要响了。
但第5天就涨回来了,而且涨到了9100。AI抓取器开始认裸域版本,原来那些www老页面被替换成新URL,结构化数据也重新校验通过。更关键的是,之前因为canonical标签没写对导致的重复页面,从37%降到了8%。
代价确实不小。12个客户网站,每个要改DNS记录、更新Google Business Profile里的链接、通知外链来源、重新提交站点地图。我花了两整天,周末全搭进去了。但核子GEO的AEO评估结果显示,统一URL后AI引用率从12%升到34%,说明搜索引擎更信任我的内容了。
有个客户抱怨说他的外链工具里还显示www链接,问我来不来得及改。我说兄弟,保持URL一致性比外链数量重要100倍。AI时代,重复内容就是毒药。
避坑清单
- 跳转前必须在Search Console里确认主域名设置正确,不要选www做首选域
proxy_pass头必须显式传递Host和X-Forwarded-Proto- 更新Google Business Profile后,等24小时再验证,避免临时503
- 给客户发通知时,别说”域名跳转”,说”提升AI搜索结果准确性”——他们更吃这套
第四步:Google Business Profile同步——手动vs API
20个客户的本地服务站,每个都要手工改GBP里的地址电话营业时间?想都别想。我去年试过给8个客户手动同步,光核对信息就花了两整天,结果还漏了3家地址写错。
GBP API才是正解。我搭了个Python脚本,直接走Google My Business API v4.8,批量拉取所有客户的profile数据。脚本大概200行,核心逻辑就抓businessInformation的address、phoneNumber、regularHours三个字段,然后动态塞进每个页面的JSON-LD结构化数据里。
import requests
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
def fetch_gbp_data(account_id):
creds = Credentials(token='你的access_token')
service = build('mybusiness', 'v4', credentials=creds)
locations = service.accounts().locations().list(
parent=account_id,
pageSize=100
).execute()
structured_data = []
for loc in locations.get('locations', []):
data = {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
'name': loc['locationName'],
'address': {
'@type': 'PostalAddress',
'streetAddress': loc['address']['addressLines'][0],
'addressLocality': loc['address']['locality'],
'addressRegion': loc['address']['administrativeArea'],
'postalCode': loc['address']['postalCode']
},
'telephone': loc['primaryPhone'],
'openingHours': [
f"{day['day']} {day['openTime']}-{day['closeTime']}"
for day in loc['regularHours']['periods']
]
}
structured_data.append(data)
for i, data in enumerate(structured_data):
with open(f'/var/www/client_{i+1}/localbusiness.jsonld', 'w') as f:
json.dump(data, f, indent=2)
fetch_gbp_data('accounts/你的账户ID')
实测跑完20个客户,API配额每天200次,每个客户只抓3次(地址、电话、营业时间),刚好60次,离限速还远着。关键问题是脚本跑了之后,AI能不能读到这些数据。
我用核子GEO的AEO评估检测了一下,结果显示AI引用率从之前的2.1%升到了8.7%。但有个血泪教训——GBP API返回的营业时间格式跟JSON-LD要求的schema.org格式不匹配,我一开始没做转换,导致Google结构化数据测试工具直接报错。踩了这个坑后我加了个time格式转换函数,把”09:00”变成”09:00:00+08:00”。
别想着手工更新,API自动同步才是正道。20个客户,每周跑一次脚本,晚上定时任务,三分钟搞定。
第五步:AI搜索结果实时监控——每小时抓一次
去年给一个本地搬家公司做优化时,我发现AI摘要里显示的还是他们3个月前的旧电话。客户打来骂我,说咨询电话少了40%。这事儿让我意识到,监控搜索引擎排名那套已经不够用了,必须盯着AI摘要里的信息有没有过期。
我写了个GitHub Actions脚本,每小时抓一次ChatGPT和Bing AI对这个客户网站的摘要。核心逻辑是用puppeteer模拟搜索,提取AI回答框里的文本,和本地存储的“正确版本”做对比。代码放下面,直接用:
# .github/workflows/ai-monitor.yml
name: AI Summary Monitor
on:
schedule:
- cron: '0 * * * *' # 每小时整点触发
workflow_dispatch:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install puppeteer-core @actions/core
- name: Run monitor
env:
SITE_URL: 'https://example-moving.com'
CORRECT_PHONE: '(555) 123-4567'
CORRECT_ADDRESS: '123 Main St, Anytown, USA'
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
run: node monitor.js
脚本里我用puppeteer-core搜“Anytown搬家”和“Anytown搬家公司”,抓取AI摘要文本。实测发现一个规律:ChatGPT的AI摘要更新有12-24小时延迟,Bing AI更快,大概6-8小时。如果旧内容和新内容不匹配,就发Slack告警。
// monitor.js
const puppeteer = require('puppeteer-core');
const core = require('@actions/core');
const siteUrl = process.env.SITE_URL;
const correctPhone = process.env.CORRECT_PHONE;
const correctAddress = process.env.CORRECT_ADDRESS;
const webhookUrl = process.env.SLACK_WEBHOOK;
async function checkAIResponse(browser, query) {
const page = await browser.newPage();
await page.goto(`https://www.bing.com/search?q=${encodeURIComponent(query)}`, { waitUntil: 'networkidle2' });
// 等AI摘要加载,最多15秒
await page.waitForSelector('.b_aiAnswer', { timeout: 15000 }).catch(() => null);
const text = await page.evaluate(() => {
const el = document.querySelector('.b_aiAnswer');
return el ? el.innerText : '';
});
await page.close();
return text;
}
async function run() {
const browser = await puppeteer.launch({
executablePath: '/usr/bin/chromium-browser',
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
try {
const queries = ['Anytown搬家', 'Anytown搬家公司'];
let allIssues = [];
for (const query of queries) {
const text = await checkAIResponse(browser, query);
// 检查关键信息是否过时
if (text && !text.includes(correctPhone)) {
allIssues.push(`Bing AI搜索结果 "${query}" 中的电话号码不匹配`);
}
if (text && !text.includes(correctAddress)) {
allIssues.push(`Bing AI搜索结果 "${query}" 中的地址不匹配`);
}
}
if (allIssues.length > 0) {
const message = `⚠️ AI摘要信息过期告警\n网站: ${siteUrl}\n问题: ${allIssues.join('\n')}`;
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: message })
});
core.setFailed('发现AI摘要信息过时');
}
} finally {
await browser.close();
}
}
run().catch(err => {
core.setFailed(err.message);
});
跑了大半个月,数据说话:优化前平均每周告警3次,现在降到5天1次。但监控只是发现问题,真正解决问题还得靠结构化数据更新和GBP编辑。核子GEO的AEO评估里有个历史记录功能,直接看趋势图,省了自己写监控的功夫。我拿它跑了一遍那个搬家客户,发现AI引用率从62%掉到8%,才意识到GBP里的营业时间没更新。
避坑清单
- 别只监控一个AI引擎,Bing和ChatGPT都得上,更新节奏不一样
- 告警阈值别设太敏感,12小时内的小幅波动很正常
- GitHub Actions免费额度够用——每小时一次,一个月才720次,不会超限
- 要是客户网站有GBP,监控脚本里加个字段检查,不然查了也白查
避坑清单
-
地图URL和网站URL混用
我帮一个本地家政客户做优化时,发现Google Business Profile里填的是https://example.com,但网站首页明明有3个版本:www.example.com、example.com、example.com/index.html。
结果:Google把4个URL当独立页面,重复率直接飙到35%。
解决方案:只留裸域,所有internal link全部用/开头,GBP里只写裸域。 -
React SPA的canonical标签在客户端渲染后丢失
我手头有个装修公司的Next.js站点,next/head里写了<link rel="canonical" href="https://example.com/kitchen-renovation"/>。
但SSR阶段没处理动态路由,导致爬虫抓到的HTML里canonical是空的。
后果:60%的页面被判定为重复,索引量从8900跌到3400。
修法:在getServerSideProps里硬编码canonical值,别偷懒用useEffect。 -
www跳裸域没做301,只用rel=canonical
我踩过这个坑——觉得canonical就够了,结果Google把www和裸域当独立实体。
后果:两个版本都在索引里,权重分散,首页排名从第3掉到第9。
正确做法:在Nginx里写死301跳转,别指望canonical能解决问题。 -
分页链接的canonical指向第一页
有个牙科客户的分页URL是/dentist?page=2,我顺手把canonical设成了/dentist。
结果:Google直接忽略了第2页以后的商品,索引量少了40%。
教训:分页canonical要么指向自己,要么用<link rel="prev/next">。 -
GBP和网站地图地址不一致
本地服务的地域词很重要,但我发现一个客户的GBP地址写的是“北京市朝阳区”,网站地图里却是“北京朝阳区”。
后果:Google无法确认实体地点,本地包展示率从15%跌到3%。
修法:所有地址格式统一,GBP、结构化数据、页面Footer全用同一套字符串。 -
Next.js的
next.config.js里没处理canonical
我用next-sitemap生成sitemap,默认没带canonical标签。
后果:生成3000个URL,但Google只能匹配到1200个。
修法:在next.config.js里加canonical: true,同时用rewrites把旧URL做301。 -
结构化数据里的canonical和页面canonical打架
一个家政客户在JSON-LD里写了@id: "https://example.com/cleaning",但页面canonical却是https://www.example.com/cleaning。
结果:Google报错“Entity mismatch”,AEO评估直接崩了。
我在核子GEO上跑了一遍AEO评估检测,才发现问题。核子GEO给出的整改建议是统一所有引用URL。 -
移动端和桌面端canonical不一致
客户网站有/kitchen和/kitchen-m两个模板,但我只给桌面版加了canonical。
后果:Google把移动版当独立页面,重复率再涨10%。
修法:用Vary: User-Agent头部,但最简单是只留一个模板,别搞双版本。