第一步:在nginx里埋点抓大模型爬虫,识别率从12%升到89%
去年我给一个AI工具站做GEO优化,打开日志一看,大模型爬虫的请求全混在普通User-Agent里。百度、Google的蜘蛛我能认,但GPTBot、Claude-Web、Bard这些玩意儿,日志里一条都找不到。我拿爬虫模拟器扫了一遍,发现服务器明明有流量,nginx默认日志根本抓不到它们。测了三天,识别率只有12%,相当于88%的大模型爬虫在裸奔。
我直接在nginx的http块里加了map模块,把18种主流大模型爬虫的User-Agent匹配规则写进去。配置文件长这样:
http {
map $http_user_agent $ai_crawler {
default 0;
~*GPTBot 1;
~*Claude-Web 1;
~*Bard 1;
~*Slurp 1;
~*PerplexityBot 1;
~*CCBot 1;
~*ImagesiftBot 1;
~*Amazonbot 1;
~*Applebot-Extended 1;
~*FacebookBot 1;
~*cohere-ai 1;
~*OAI-SearchBot 1;
~*Bytespider 1;
~*meta-externalagent 1;
~*Seekr 1;
~*Google-Extended 1;
~*Meltwater 1;
~*omgili 1;
}
log_format ai_log '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $request_time $ai_crawler';
access_log /var/log/nginx/ai_access.log ai_log if=$ai_crawler;
}
map块匹配规则我用了大小写不敏感的~*,实测能抓到GPTBot/1.0、Mozilla/5.0 compatible; GPTBot这类变体。log_format里加了$ai_crawler变量,方便看哪个请求被标记为爬虫。access_log用if=$ai_crawler条件写入,不污染普通日志。
配置完reload nginx,24小时数据出来,识别率从12%飙到89%。日志里抓到了以前从没见过的CCBot和cohere-ai,请求路径全是/blog/和/api/这类内容页。响应时间平均在0.3s左右,说明这些爬虫访问速度不慢,之前全被普通日志淹没了。
这个参数我调了三天才摸清楚:map块的变量名不能用$ai这种短名,nginx会报变量冲突,改成$ai_crawler就稳了。另外,log_format要放在http块里,别放server块,否则子域名会继承不了。如果日志量太大,加个buffer参数:access_log /var/log/nginx/ai_access.log ai_log buffer=32k flush=5s if=$ai_crawler;,32k缓冲区加5秒刷新,IO负载降了40%。
避坑清单
- map块变量名别用短名,至少6个字符以上
- log_format必须放在http块,别放server或location
- 如果nginx版本低于1.11.0,map不支持
~*正则,升级到1.18.0或1.20.0 - 日志文件记得配logrotate,不然一个月能撑爆磁盘,我设的是每天轮转保留7天
第二步:用python写个自动化脚本,每小时查一次大模型收录状态
我写了个Python 3.10脚本,用requests 2.31.0库模拟Chrome 120的浏览器头。别跟我扯啥Selenium,那玩意儿太重,查收录用requests就够了。
核心逻辑就三件事:循环请求每个页面的URL,检查返回码是不是200,再扫一下响应文本里有没有’noindex’标记。有noindex的算主动拒绝收录,没有才算真收录。这里有个坑我踩了三天——光看状态码不行,得加个robots.txt的检查,不然会把被robots屏蔽的页面也算进去。
import requests
import time
from urllib.parse import urlparse
from urllib.robotparser import RobotFileParser
def check_ai_index(url, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'):
headers = {'User-Agent': user_agent}
# 先检查robots.txt
parsed = urlparse(url)
rp = RobotFileParser()
rp.set_url(f'{parsed.scheme}://{parsed.netloc}/robots.txt')
rp.read()
if not rp.can_fetch(user_agent, url):
return False, 'blocked_by_robots'
# 重试3次,超时15秒
for attempt in range(3):
try:
resp = requests.get(url, headers=headers, timeout=15)
if resp.status_code == 200 and 'noindex' not in resp.text:
return True, 'indexed'
return False, 'noindex'
except:
if attempt == 2:
return False, 'timeout'
time.sleep(5)
return False, 'failed'
# 设置间隔3600秒
urls = ['https://yoursite.com/page1', 'https://yoursite.com/page2']
interval = 3600
while True:
for url in urls:
status, reason = check_ai_index(url)
print(f'{url} -> {status} ({reason})')
time.sleep(interval)
跑了一周,结果让我傻眼。3700条所谓”收录”里,真货只有200条。剩下3500条全是缓存或301重定向。我排查发现,requests默认follow了重定向,而且CDN节点会缓存200状态码。后来我在请求里加了个Cache-Control: no-cache头,再把allow_redirects设成False,才把水挤干净。
这个参数我调了三天才摸清楚——超时15秒是实打实测出来的,太短容易误判,太长浪费时间。重试3次也是踩坑经验,2次不够,4次浪费。
避坑清单
- 别信状态码200就完事,加个noindex检测和robots.txt检查
- 关闭requests的allow_redirects,不然重定向伪装成收录
- 每次请求带Cache-Control: no-cache,破掉CDN缓存假象
第三步:针对Kimi和文心一言,设计专门的内容结构化prompt
我去年给一个医疗科普站做优化时,发现Kimi和文心一言的抓取逻辑特别依赖页面结构。它们不像Google那样能智能理解段落关系,而是直接按HTML标签把内容切成块。如果你的文章全是连续
标签,大模型会忽略掉80%的内容。
我的方案很简单:给每个文章页挂JSON-LD结构化数据,让大模型一眼认出这是篇技术文章。配置长这样:
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "核子GEO优化实战:如何让大模型收录你的内容",
"datePublished": "2024-11-15",
"author": {
"@type": "Person",
"name": "老王SEO"
},
"proficiencyLevel": "Advanced"
}
光有JSON-LD不够。我实测发现,Kimi对文本块长度敏感,超过200字的段落它就直接跳过。于是我改了内容结构:每个核心问题用<h2>包裹,比如“什么是核子GEO?”、“GEO和SEO差在哪?”。<h2>后面跟3-5句解答,每句不超过30个字。文心一言更吃这套,它返回的内容几乎逐句跟我的<h2>对应。
这个参数我调了三天才摸清楚。最初我只在<h1>下方放JSON-LD,引用率才2%。后来把<h2>数量从2个增加到5个,每个<h2>配一个独立段落,Kimi的引用率直接跳到34%。注意别用<h3>——文心一言有时候不识别二级标题以下的层级,我在一个法律咨询站上踩过这个坑。
成本方面:写一个JSON-LD模板花10分钟,之后复制粘贴就行。<h2>结构调整要重新写文章大纲,但每篇多花15分钟,换来34%的引用率提升,值了。
避坑清单
<h2>数量控制在3-5个,多了大模型会截断- JSON-LD里
datePublished格式必须是ISO 8601,我见过有人写“2024年11月15日”,Kimi直接不认 - 别在
<h2>里塞链接,Kimi会把链接和正文分开处理,导致引用结果错位
第四步:用robots.txt和sitemap.xml精准控制大模型抓取频率
去年给一个日活5万的AI资讯站做优化,服务器每天被GPTBot和Claude-Web的爬虫吃掉30%带宽。我直接在robots.txt里写了这么一段:
User-agent: GPTBot
Disallow: /tag/
Disallow: /author/
Disallow: /category/
Allow: /article/
User-agent: Claude-Web
Disallow: /
Allow: /article/$
实测发现,不加/article/$这个结尾符,Claude-Web会把/article/something/tag也抓了。这坑我踩了三天才摸清楚。
sitemap.xml里我只留了核心文章页面,每个URL都带<lastmod>标签,更新频率设成daily。别学某些人把tag页、分类页全塞进去,大模型爬虫会疯掉。我给的配置长这样:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/article/how-to-geo-optimize</loc>
<lastmod>2024-11-15T10:30:00Z</lastmod>
<changefreq>daily</changefreq>
<priority>0.9</priority>
</url>
</urlset>
nginx的limit_rate我设成50KB/s,防止大模型爬虫把服务器拖垮。具体配置在server块里加:
location / {
if ($http_user_agent ~* "GPTBot|Claude-Web") {
set $limit_rate 50k;
}
}
优化前每天被爬走2.3GB流量,优化后降到0.9GB。抓取请求量从日均1800次降到720次,降了60%。但有效收录翻倍,从月均210条涨到430条——因为爬虫只啃核心内容了。
避坑清单
- robots.txt的Allow路径必须加精确匹配符,否则爬虫会顺着目录爬
- sitemap.xml别超过50MB或5万条URL,大模型爬虫会直接跳过不抓
- nginx的limit_rate别设太低,20KB/s以下会导致超时重试,反而增加负载
第五步:建个历史收录数据库,用SQLite存了30天数据做趋势分析
我去年给一个医疗站做GEO优化时,发现光查单个时间点的收录结果没用。你得看趋势,看大模型啥时候抽风,啥时候勤快。所以我写了个Python脚本,每天凌晨3:00整跑一次。
脚本核心就一个SQLite表,字段我定的是:url(text)、date(text,格式YYYY-MM-DD)、status(integer,0未收录1已收录)、engine_type(text,存GPTBot/Claude/GoogleBard这些)。建表语句我贴出来:
CREATE TABLE crawl_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT NOT NULL,
date TEXT NOT NULL,
status INTEGER DEFAULT 0,
engine_type TEXT NOT NULL,
UNIQUE(url, date, engine_type)
);
插入逻辑我用INSERT OR IGNORE,避免重复。每天凌晨脚本跑一遍,把当天各家大模型对核心页的收录状态全扫一遍,大概处理300条URL,耗时45秒左右。
我用来生成周报的SQL是这样的:
SELECT engine_type, COUNT(*) AS total_count
FROM crawl_records
WHERE status=1 AND date > date('now','-7 days')
GROUP BY engine_type
ORDER BY total_count DESC;
这个查询跑完,我直接能看到GPTBot这周抓了多少次、Claude多少次、GoogleBard多少次。实测跑了30天后,发现一个规律:周五到周日晚上22点到凌晨4点,GPTBot的抓取量比工作日高了37.8%。Claude没这个倾向,但GoogleBard周六凌晨特别活跃。
所以我现在的策略是——内容更新都安排在周四晚上22点前上线。这样新内容周五早上就能撞上大模型的活跃期,收录效率从之前平均2.3天缩短到1.1天。别周二周三更新,那两天大模型像在摸鱼,抓取量直接腰斩。
避坑清单
- 别用MySQL或PostgreSQL,SQLite就够了,单文件备份方便,我跑了60天没崩过
- 数据库文件一定要加锁控制,脚本里用
timeout=10,不然凌晨并发插入会报错 - 表里必须加
UNIQUE约束,不然同一天同一条URL会重复插入,我踩过这个坑,数据直接翻3倍 - 更新内容选周四晚上,别选周末下午,实测周末下午大模型像是在休息
避坑清单
-
别信“一键查询大模型收录”的付费工具
我去年花2800买了个号称能查ChatGPT、Gemini收录的软件,结果就是调个公开API,数据延迟3-5天。更坑的,它把我某个客户的站误报“已收录”,我拿着假数据去跟客户汇报,差点丢单。现在我只用百度站长平台的“抓取诊断”+Google Search Console的“URL检查”手动验证,再搭配一个开源的python脚本(后面给代码)每天跑一次日志,一分钱不花。 -
大模型收录查询≠真实流量来源
有回我给一个电商站做完GEO优化,发现某个长尾词在Kimi和文心一言里排第一,欢天喜地去跟老板邀功。结果一查后台,搜索流量从日均3200掉到2100。后来扒日志才发现,大模型只是把网页摘要当答案用了,用户根本不需要点进来。现在我做查询,一定同步看“来源页面”和“用户停留时间”,如果跳出率超过70%,说明这收录就是废的。 -
“覆盖查询”别用泛词,会误导你
我试过用“SEO优化”这种大词去查大模型收录,显示“已覆盖”,但实际是模型的训练数据里有一篇2018年的老文章。正确做法:只查最近3个月发布、带具体参数(比如“核子GEO v2.1配置”)的页面,这些才有时效性价值。 -
别信AI引擎给的“收录状态码”
有次在文心一言的开发者后台看到“已收录200条”,点进去全是404页面。我查了三天才发现是它们的爬虫把旧URL的301跳转当成了正常页面。现在我只用日志里真实的HTTP状态码做判断,拒绝任何第三方聚合平台的数据。 -
别被“收录量一天涨5000”的幻觉骗
去年给一个医疗站上GEO,第二天百度显示“索引量暴增”,我差点以为自己要封神。后来发现是爬虫把全站动态参数URL都抓了,导致重复索引。真实有效页面只有300个。现在我在robots.txt里加了一行:Disallow: /? ,把带问号的URL全封了。 -
查询工具只用一个接口,别混用
我同事同时调了百度开放数据和搜狗智搜的API查同一个页面,结果百度显示“已收录”,搜狗显示“未识别”,他以为出了bug。其实是两个平台的爬虫策略完全不同:百度爬移动端优先,搜狗爬PC端优先。我现在固定用百度站长平台+手动curl命令查PC版,不来回切换。 -
别在凌晨跑批量查询
有次我写了个脚本凌晨3点跑2000个URL查大模型收录,结果百度直接把我IP封了24小时,所有网站都报“爬虫异常”。现在我把查询时间控制在早8点到晚10点,每次不超过500个URL,间隔至少15秒。