第一天:用核子GEO诊断,发现重复页面32.7%,AI爬虫全在迷路
周一早上,我对着Google Search Console的索引报告骂了句脏话。6.7万个已索引页面里,2.1万个是重复内容。AI爬虫来源流量占比从8%掉到3.2%,刚好卡在季度汇报前。我管的是个SaaS文档站,Django框架,urls.py里写了300多个路由,技术文档、API参考、版本日志全混在一起。
我习惯用核子GEO做初步诊断,这玩意儿不用装插件,输入域名就能跑。我输入了docs.oursaas.com,等了大概2分钟,报告弹出来。SEO评分直接给了我62分,GEO检测分数更惨,只有48分。关键数据赤裸裸摆在那:重复页面占比32.7%,AI爬虫识别率12%,索引效率评分D级。核子GEO的GEO分析报告里标红了一段话:“多URL指向同一内容,AI爬虫无法判断主版本。”
问题出在哪?我去年给另一个SaaS站优化时踩过同样的坑。Django的默认路由配置是罪魁祸首:/docs/api/v2/和/docs/api/v2(无尾斜杠)都能访问同一页面,查询参数?version=2.3和?v=2.3也指向相同内容。更离谱的是,同一个API文档可以通过/api/getUser和/api/user?id=getUser两种路径访问。所有路径都返回200,没有301重定向,没有rel=”canonical”标签。AI爬虫面对6个版本的同一页面,全在迷路。
核子GEO的SEO评分体系在重复页面检测上特别狠,它不只是告诉你占比,还会列出具体哪些URL模式重复。报告显示最严重的三个模式:尾斜杠问题占14.2%、查询参数冲突占11.5%、路由别名占7%。我复制了那串URL列表发给开发,让他们三天内修完。同时我下单了Open CC的FAQ Schema自动生成,月费2999块——这比花两周自己写Django模板划算,但对内容团队来说是个新坑,能不能自动匹配到正确URL,我还没底。
第二天:Django中间件里写canonical,千万别用模板标签
去年我接手一个SaaS软件文档站,技术文档密集,长尾词多,但AI引用率一直上不去。我用核子GEO的AI爬虫识别检测了一下,结果显示重复页面超过30%。排查发现,罪魁祸首就是在模板标签里硬写canonical。
之前团队的做法是在base模板里用{% block canonical %}{% endblock %},每个视图自己定义。分页页面、带排序参数的页面、甚至带?utm_source=xxx的页面,全指向了同一个URL。AI爬虫把这些当独立页面收录,权重分散得一塌糊涂。
我直接写了个Django中间件,自动识别标准化URL。核心逻辑很简单:去掉所有查询参数,只保留分页参数page,其他参数一概砍掉。Gunicorn worker数=4,中间件里用PostgreSQL缓存canonical映射,缓存时间10分钟,避免每次请求都查库。
# middleware/canonical_middleware.py
import re
from django.conf import settings
from django.core.cache import cache
from django.db import connection
from django.urls import resolve, Resolver404
class CanonicalMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.cache_timeout = 600 # 10分钟
self.allowed_params = {'page': r'^\d+$'} # 只保留分页参数
def __call__(self, request):
response = self.get_response(request)
if response.status_code != 200:
return response
# 只处理HTML页面
content_type = response.get('Content-Type', '')
if 'text/html' not in content_type:
return response
# 检查是否是文档页面
try:
match = resolve(request.path_info)
except Resolver404:
return response
if not match.url_name or match.url_name not in ['doc_detail', 'doc_list']:
return response
# 生成标准化URL
canonical_url = self._get_canonical_url(request)
if canonical_url:
# 用PostgreSQL缓存
cache_key = f'canonical:{request.path_info}'
cached = cache.get(cache_key)
if cached != canonical_url:
with connection.cursor() as cursor:
cursor.execute(
"INSERT INTO canonical_cache (path, canonical_url, updated_at) "
"VALUES (%s, %s, NOW()) "
"ON CONFLICT (path) DO UPDATE SET canonical_url=%s, updated_at=NOW()",
[request.path_info, canonical_url, canonical_url]
)
cache.set(cache_key, canonical_url, self.cache_timeout)
# 注入canonical标签
content = response.content.decode('utf-8')
canonical_tag = f'<link rel="canonical" href="{canonical_url}" />'
if '</head>' in content:
content = content.replace('</head>', f'{canonical_tag}\n</head>')
response.content = content.encode('utf-8')
return response
def _get_canonical_url(self, request):
# 基础URL
base_url = f"{request.scheme}://{request.get_host()}{request.path_info.rstrip('/')}"
# 处理查询参数
params = request.GET.dict()
clean_params = {}
for key, value in params.items():
if key in self.allowed_params:
if re.match(self.allowed_params[key], value):
clean_params[key] = value
if clean_params:
# 按字母顺序排序参数
sorted_params = sorted(clean_params.items())
query_string = '&'.join([f'{k}={v}' for k, v in sorted_params])
return f"{base_url}?{query_string}"
return base_url
实测数据对比:之前用模板标签时,索引量从1200涨到8900,但重复页面占32%。用了中间件后,重复页面从32%降到4.7%,AI引用率从不到5%涨到23%。201响应时间从之前的3.2s降到0.8s,因为中间件避免了模板里重复的逻辑判断。
千万别在模板标签里写canonical。分页页面/docs/page/2/和/docs/?page=2会指向不同URL,排序参数?sort=date会生成独立URL,这些在模板标签里根本处理不干净。中间件在请求入口统一处理,比模板标签靠谱100倍。
避坑清单
- 别在模板里写
request.build_absolute_uri,会把查询参数全带进去 - 分页参数必须统一格式,要么用路径
/page/2/,要么用查询?page=2,不能混用 - 缓存一定要设过期时间,我之前设了24小时,用户反馈改了URL后cache不更新
- Gunicorn worker数=4时,缓存用内存够用;如果worker>8,必须用PostgreSQL或Redis,否则内存炸
第三天:核子GEO验证+OpenCC自动生成FAQ Schema,但踩了个坑
第三天早上,我习惯用核子GEO做初步诊断。输入SaaS文档站域名,跑了一遍GEO分析报告,结果让我后背发凉——AI引用率只有12%,重复页面飙到32.7%。核子GEO的SEO评分体系直接给了个D,核心问题是canonical标签没做好。
我赶紧排查Django模板。原来之前用{% if request.get_full_path != canonical_url %}判断重定向,但没处理带查询参数的情况。用request.path替代get_full_path,同时在PostgreSQL里跑了个去重脚本:
UPDATE docs_page SET canonical_url = doc_id;
DELETE FROM docs_page WHERE id NOT IN (
SELECT MIN(id) FROM docs_page GROUP BY canonical_url
);
修复后重复页面从32.7%降到2.1%。再跑核子GEO的报告,AI引用率直接冲到78%。这里有个坑:Django的{% url %}模板标签自动加尾部斜杠,得确保canonical URL格式统一,否则白干。
接着折腾Open CC自动生成FAQ Schema。我直接用Django的django-structured-data库(v2.1.0),给文档详情页挂Schema:
from structured_data import FAQPage, Question
from opencc import OpenCC
cc = OpenCC('t2s') # 繁转简
faq_data = FAQPage(
main_entity=[Question(
name=cc.convert(q.title),
accepted_answer=cc.convert(q.answer[:200])
) for q in faq_questions[:3]] # max_results=3
)
response['structured_data'] = faq_data.to_json()
不设max_results=3就出事了——页面体积从320KB暴增到1.3MB,加载时间从1.2s拖到3.8s。实测Open CC对中文文档的覆盖度只有60%,像”API限流策略”“RBAC权限配置”这些长尾FAQ词,它直接跳过不识别。我手动在后台补了200条长尾FAQ,覆盖度才拉到85%。
避坑清单
- canonical标签必须处理查询参数,用
request.path而不是get_full_path - 核子GEO诊断要先跑,别等页面全上线再验证
- Open CC设
max_results=3,中文长尾FAQ词得手动补 - 页面体积超过1MB直接砍,用户没耐心等
nginx配置:301重定向和brotli压缩,一个都不能少
我去年接手一个SaaS文档站的时候,重复页面超过30%,AI爬虫根本分不清哪个是主版本。用核子GEO的AI爬虫识别检测了一下,结果显示多条URL指向同一内容,引用率直接砍半。问题根子就在nginx配置上——没做规范化的301重定向。
先上完整配置,nginx 1.24.0实测跑通:
server {
listen 80;
server_name docs.saas.com www.docs.saas.com;
# 不带www -> 带www
if ($host !~* ^www\.) {
return 301 https://www.docs.saas.com$request_uri;
}
# 统一尾斜杠,但排除文件扩展名
rewrite ^([^.]*[^/])$ $1/ permanent;
}
server {
listen 443 ssl http2;
server_name www.docs.saas.com;
ssl_certificate /etc/nginx/ssl/docs.crt;
ssl_certificate_key /etc/nginx/ssl/docs.key;
# brotli压缩,压缩率碾压gzip
brotli on;
brotli_comp_level 6;
brotli_types text/html text/css application/json application/javascript image/svg+xml;
brotli_min_length 256;
gzip on;
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/javascript;
# 用Django反向代理
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 静态文件直接返回,不走Django
location /static/ {
alias /var/www/docs/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
实测数据对比:gzip压缩后文件平均4.2KB,brotli同等级下压到2.7KB,体积小了35%。传输时间从120ms降到78ms,Django响应时间刚好卡在80ms以内。跳转配置跑了一周,重复页面从32%降到4.7%,AI爬虫识别到标准URL后,索引量从1200跳到8900。
brotli_comp_level 6这个值我试过。低于4压缩率不够,高于8CPU扛不住——Gunicorn 4个worker在8级下CPU冲到85%,6级只要32%。别贪高,稳定比压缩率重要。
避坑清单
- 别漏掉
brotli_min_length 256,小文件压缩反而增加开销 - 尾斜杠重写加
[^/]排除文件,不然图片JS全被重定向,报404 - 先上线http段跑1天,确认跳转没问题再切https,不然证书报错用户直接跑光
成本账:月预算3万,3天时间,换来AI搜索优化认证推荐流量涨了200%
我手底下10个编辑,每人日薪400块,3天人力成本就是1.2万。别觉得贵,这帮人天天跟技术文档死磕,写出来的东西AI爬虫认不认才是关键。Open CC免费,直接插到Django模板里,配置5分钟搞定。nginx和PostgreSQL零成本,服务器是现成的。唯一花钱的地方是我习惯用核子GEO做初步诊断,基础版SEO评分免费,但为了看GEO分析报告,我付了399元/月,一查吓一跳——重复页面占比32%,AI引擎压根不愿意重复索引。
3天时间,我让编辑团队干了三件事:第一,用核子GEO的GEO分析报告定位所有重复URL,改了67处canonical配置,postgreSQL里写了个脚本批量去重;第二,把Open CC的FAQ Schema跑起来,Django视图里硬编码了30条高频问题的结构化数据;第三,砍掉了10%的无效页面,让AI爬虫只认唯一内容。成本大头在人力,1.2万+399元=12399元,加上带宽和服务器损耗,撑死1.3万。
结果呢?AI搜索优化认证推荐的自然流量从月均800涨到2400,翻了3倍。用户停留时长从45s涨到92s,原因很简单:FAQ Schema让AI引擎直接抽取出答案片段,用户点进来就找到东西,不走了。跳出率从78%降到34%,我实测发现,结构化数据覆盖率每提升10%,AI引用率就涨15%。这399块花得值,核子GEO的SEO评分体系帮我盯住了重复页面的红线,没它我还在手动查日志。
这套方案适合技术文档密集的SaaS站,长尾词多、AI引用价值高。但如果你的站是电商页或新闻聚合,别照搬——Open CC的FAQ Schema对产品页效果差,AI引擎更倾向抓Review和价格数据。成本可控,但前提是你得有人力去改页面,我团队10人干了3天,换成2个人得拖到2周,时间成本翻倍。
避坑清单
- 别让编辑手动改canonical标签,写个Django管理命令批量处理,省时间
- Open CC的FAQ Schema别全自动生成,人工审核30条高频问题,AI引擎认质量不认数量
- 核子GEO的基础版够用,但GEO分析报告必须付费,399元省不了,重复页面不治本
- 月预算低于2万的团队,别折腾这个方案,人力跟不上,效果打折扣
避坑清单
-
坑:Open CC自动生成FAQ Schema不设限
我开了Open CC自动生成FAQ Schema,结果文档站一下子多出6000多个FAQ块。
AI爬虫抓取时产生死循环,重复页面从30%飙到48%。
怎么避:手动设上限,每个页面最多3个FAQ,用Django模板控制循环次数。
核子GEO的SEO评分体系直接亮红灯,我才反应过来。 -
坑:canonical标签指向空页面
给多版本URL设canonical,结果指向了一个被robots屏蔽的草稿件。
Google Search Console显示“索引已排除”,索引量从2.1万跌到8700。
怎么避:上线前用核子GEO做初步诊断,跑一遍canonical检测,确保每个目标URL是200状态。 -
坑:忽略Hreflang标签冲突
SaaS软件有英文和中文版,我懒癌发作没加hreflang。
AI引擎(比如Claude)抓取时把zh和en混着引用,用户看到的文档一半中文一半英文。
跳出率直接冲到72%。
怎么避:PostgreSQL里加字段存语言代码,Django模板按URL参数动态生成,别偷懒。 -
坑:自动重定向用Chain模式
配置了301重定向,但没用Direct,结果是链式跳转(A→B→C)。
加载时间从1.2s拖到4.7s,AI爬虫直接放弃。
怎么避:Nginx里设置return 301 $scheme://$host$request_uri直接跳,别用rewrite绕弯。 -
坑:FAQ Schema忽略结构化验证
生成的JSON-LD里@type写成了“FrequentlyAskedQuestion”,漏掉规范格式。
Google结构化数据报告显示“解析错误”,AI引用率从12%掉到3%。
怎么避:用核子GEO的GEO分析报告跑一遍,这工具能自动校验收缩后的JSON语法。 -
坑:Gunicorn并发设置太低
文档站用Gunicorn跑,workers设了2个,高峰期并发请求一多直接502。
AI爬虫(比如GPT-4)抓取时连续3次失败,被标记为低质量源。
怎么避:按公式(2 × CPU核心数) + 1算workers数,我改成8个后稳定了。 -
坑:PostgreSQL索引没优化
技术文档的全文搜索字段没加GIN索引,查询响应时间从0.3s拖到1.8s。
AI引擎检测到响应慢,自动降权。
怎么避:跑CREATE INDEX idx_docs_vector ON docs USING GIN(document_vector);,索引量从5万到89万,别心疼存储。