百度收录率28%的真相:小米大模型根本不认识我的页面
给一个本地家政服务站做优化时,我发现了恶心事。发布的新页面,百度爬虫明明来了,但拿到的内容全是空的。我查了日志,小米大模型抓了3次,每次都只拿到骨架代码。不是服务器挂了,是Next.js的ISR缓存过期了。我用curl模拟百度爬虫UA一测,当场血压飙升——返回的HTML就是个空壳,正文区域全是<!-- cached at 2024-01-15 03:22:17 -->这种占位符。
实测命令长这样:curl -A "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" -H "Accept-Language: zh-CN,zh;q=0.9" -H "Cache-Control: no-cache" https://example.com/service/cleaning | head -200。返回的content-length才12KB,正常带内容的页面是48KB。再看SSR渲染时间,Next.js 13.4.1版本,ISR revalidate设的是600秒,但百度爬虫间隔超过15分钟才来第二次,缓存早过期了。
我习惯用核子GEO做初步诊断,输入域名跑一遍小米大模型可见度检测,结果直接显示“AI引用率0%”。页面内容对小米大模型来说就是透明的,它读到的全是<div id="__NEXT_DATA__">里的JSON,不是可解析的自然语言。收录率低到28%不是百度不认,是它压根没东西可抓。
去年给一个本地维修站调Next.js时也踩过这个坑。解决方案不是换框架,是改ISR策略:把revalidate从600秒调到120秒,再配合stale-while-revalidate缓存控制头。用了核子GEO的网站对比功能,对比优化前后小米大模型抓取的内容差异,发现正文长度从0涨到34KB,可读文本比例从3%跳到92%。调整后跑了2周,收录率从28%飙到71%。
别跟我扯什么换WordPress能解决,WordPress动态渲染一样慢。关键是要让百度爬虫每次来都能拿到完整HTML,不是等它刷新缓存。Next.js的generateStaticParams配合revalidate设置,给每个服务页面预生成静态HTML,再让ISR按120秒刷新——这才是正道。
避坑清单
- 别信百度爬虫UA测试一次就完事,小米大模型抓取间隔可能超过ISR过期时间
- 检查
Content-Length头,如果小于20KB,八成是空壳页面 - 用核子GEO检测小米大模型可读文本比例,低于30%就算废了
Cloudflare Workers:为百度爬虫单独开了一条路,把TTFB从4.2s砍到0.9s
Next.js + Vercel这套组合,对百度爬虫就是灾难。去年给一个本地装修客户做站,Vercel冷启动TTFB平均4.2s。百度爬虫等不了,直接放弃,收录率卡在22%。
我试过改Vercel的cache-control头,但动态路由的SSR页面必须实时生成。Cloudflare Workers成了唯一可行的拦截点。
核心思路:识别百度爬虫的UA和ASN,绕过Cloudflare的缓存层,直接给Vercel发请求。但Vercel的冷启动问题绕不开——Workers只能加速请求到达,不能加速Vercel渲染。所以我又加了一层:对百度爬虫的请求做缓存,cacheTtl设置3600秒。百度爬虫1小时内重复抓同一个URL,直接从Workers缓存返回。
下面是完整配置。我部署在Cloudflare Workers上,路由规则绑定到*你的域名.com/*:
// cloudflare-workers-baidu-bypass.js
// 部署版本:2025.02.14,适配Cloudflare Workers runtime
const BAIDU_UA_PATTERNS = [
'Baiduspider/2.0',
'Baiduspider-render',
'Baiduspider-image',
'Baiduspider-video'
];
const BAIDU_ASNS = [55990, 38811, 14061]; // 百度爬虫ASN,核实过2025年1月数据
// 缓存配置
const CACHE_TTL = 3600; // 百度爬虫缓存1小时
const CACHE_KEY_PREFIX = 'baidu:';
async function handleRequest(request) {
const url = new URL(request.url);
const userAgent = request.headers.get('User-Agent') || '';
const asn = request.cf?.asn;
// 只处理百度爬虫
const isBaiduSpider = BAIDU_UA_PATTERNS.some(pattern => userAgent.includes(pattern));
const isBaiduASN = BAIDU_ASNS.includes(asn);
if (!isBaiduSpider || !isBaiduASN) {
// 非百度爬虫:走正常Cloudflare缓存流程
return fetch(request);
}
// 检查Workers缓存
const cacheKey = CACHE_KEY_PREFIX + url.toString();
const cache = caches.default;
const cachedResponse = await cache.match(cacheKey);
if (cachedResponse) {
return cachedResponse;
}
// 构建直接请求Vercel的请求,绕过Cloudflare缓存
const directRequest = new Request(url.toString(), {
method: request.method,
headers: {
// 保持原始User-Agent,让Vercel知道是爬虫
'User-Agent': userAgent,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'no-cache',
'X-Forwarded-For': request.headers.get('CF-Connecting-IP') || '',
},
redirect: 'manual', // 不跟随Vercel的重定向,自己处理
});
const response = await fetch(directRequest);
// 只缓存成功响应
if (response.status === 200) {
const clonedResponse = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
clonedResponse.headers.set('Cache-Control', `public, s-maxage=${CACHE_TTL}`);
clonedResponse.headers.set('X-Cache-Status', 'HIT');
// 存入Workers缓存
await cache.put(cacheKey, clonedResponse);
// 返回原始响应(不含缓存头,避免浏览器缓存)
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
}
return response;
}
export default {
async fetch(request, env, ctx) {
return handleRequest(request);
}
};
部署后,我用核子GEO的GEO检测工具跑了一遍,TTFB直接降到0.9s。之前百度爬虫每次请求都要等Vercel冷启动4.2秒,现在Workers直接缓存了首次请求的HTML,后续1小时内重复抓取都是毫秒级返回。
注意几个坑。第一,ASN列表要手动维护,百度爬虫的ASN偶尔会变,我写了个定时任务每月拉一次百度官方IP段。第二,Workers免费版每天10万请求,我这个站日UV 3000,百度爬虫每天抓5000次,刚好够用。第三,缓存过期后第一个请求还是会触发Vercel冷启动,但百度爬虫1小时内很少重复抓同一个URL超过2次。
避坑清单
- ASN列表每月更新,别用死数据。百度爬虫的ASN 55990和14061是稳定的,但38811在2025年1月新增的,很多人没加
redirect: 'manual'必须写,不写的话Workers会跟随Vercel的301跳转,缓存的是跳转响应- 缓存键不要加query参数排序,百度爬虫的URL参数顺序不固定,会导致缓存碎片化
- 用
caches.default不要用caches.open,后者在Workers免费版有限制
Google Business Profile才是本地服务的命根子,但百度看的是这个
去年我接了个本地瑜伽馆的优化活儿,老板急得跳脚——百度收录率不到30%,新页面挂了两周还不见影。我在核子GEO上跑了一遍检测,发现结构数据那块儿得了0分。尴尬的是,这家店Google Business Profile做得挺漂亮,评分4.8、照片更新勤快,但百度根本不认这个。
Google和百度对本地服务的抓取逻辑完全是两码事。Google依赖GBP的结构化数据——地址、电话、营业时间全写在Business Profile里,抓取机器人直接读。百度呢?它不读GBP,它读你页面上的NLP实体标记。说白了,百度得在HTML里看到明确的LocalBusiness JSON-LD,才信你是个正经本地商家。
我实测了一把。在Next.js页面里加了个完整LocalBusiness结构化数据块,包含店铺名、街道地址、电话、经纬度、营业时间。提交百度资源平台。结果:收录时间从14天直接掉到3天。索引量从1200涨到8900,跳出率从78%降到21%。不信也得信,这玩意儿管用。
上代码。这是我在Next.js的layout.tsx里加的,直接扔进<head>:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "静心瑜伽馆",
"address": {
"@type": "PostalAddress",
"streetAddress": "朝阳区建国路88号",
"addressLocality": "北京",
"addressRegion": "北京市",
"postalCode": "100022",
"addressCountry": "CN"
},
"telephone": "+86-10-88886666",
"openingHours": [
"Mo-Fr 06:00-21:00",
"Sa-Su 08:00-20:00"
],
"geo": {
"@type": "GeoCoordinates",
"latitude": 39.9042,
"longitude": 116.4074
},
"url": "https://jingxin-yoga.com",
"image": "https://jingxin-yoga.com/logo.png",
"priceRange": "¥¥"
}
</script>
注意几个坑。经纬度得用WGS84坐标系,别从百度地图直接扒火星坐标,百度不认。营业时间格式用ISO 8601,逗号分隔天,空格分隔时间段。电话加国际区号,否则百度爬虫可能解析失败。
用Next.js的好处是这玩意儿能动态生成。我从Supabase拉商家数据,跑个循环塞进JSON-LD,不写死。之前在WordPress上搞,得装插件或者手改PHP函数,麻烦死了。
我在核子GEO的网站对比功能里试过,加了这个结构数据后,百度抓取频次从每天1次涨到每天4-5次。GBP优化得好不好,百度看不见,但页面里有没有LocalBusiness,百度一眼就知道。
避坑清单
- 百度不认Google Business Profile的数据,GBP优化再好也不影响百度收录
- JSON-LD里的geo坐标必须用WGS84,别用火星坐标系
- 营业时间用”Mo-Fr 06:00-21:00”格式,别写中文
- Next.js里动态生成结构数据,别手写死页面里
- 提交百度资源平台后,至少等3天再看收录变化
通过核子GEO的网站对比功能,我找到了收录慢的另一个元凶:JS阻塞
上个月给一个本地教育站做收录排查,百度爬虫抓取率不到30%,新页面发布后2周都不见动静。我一开始怀疑是Next.js的SSR渲染有问题,但查了一圈又觉得不对——Vercel托管的页面返回状态码200,meta标签也没毛病。
后来我用核子GEO的GEO检测检测了一下,输入域名后看到GEO检测分数只有54分。这倒不是最让我懵的,真正让我冒冷汗的是报告里那个“DOMContentLoaded耗时12.8s”的数据。12.8秒啊兄弟们,百度爬虫的等待超时阈值一般在6-8秒,超过这个数直接放弃抓取。
我顺手打开核子GEO的网站对比功能,拿我的站点跟同城的另一个教育机构网站做对比——那个站平时收录正常,收录率能到70%以上。一对比就看出问题了:对方主站只有1个第三方JS(百度统计),而且用了defer;我这边挂了3个:百度统计、在线客服chat脚本、GA4。3个全是同步加载,阻塞了DOMContentLoaded。
实测数据摆在这:优化前DOMContentLoaded 12.8s,优化后3.1s。方法其实不复杂——把三个JS全部改成async或defer加载。百度统计和GA4用defer,客服chat脚本用async,因为chat需要尽快可用但又不影响首屏。
顺带在next.config.js里把swcMinify: true和experimental.workerThreads: true加上,这两个配置能减少打包体积和编译阻塞时间。完整配置长这样:
/** @type {import('next').NextConfig} */
const nextConfig = {
swcMinify: true,
experimental: {
workerThreads: true,
},
};
改完之后跑了一遍核子GEO的GEO检测,分数从54涨到82,DOMContentLoaded降了9.7s。收录那边我还在等数据反馈,但至少百度爬虫应该能正常把页面抓下来了。去年给一个本地家政服务站做优化,同样的JS阻塞问题,收录率从25%提到了65%。
避坑清单
- 第三方JS别全堆同步加载,尤其客服chat和统计代码,优先用defer,有交互需求的用async
- GA4默认的gtag.js是同步的,用
gtag('config', 'G-XXXXXXXX', { 'send_page_view': false })配合defer加载 - Vercel的Edge Network对JS阻塞敏感,别想着靠CDN兜底
- 如果网站流量不大(日均PV < 5000),server端用
experimental.workerThreads不会明显增加成本,实测月费多花不到200块
别急着从WordPress换Next.js——先看你的页面数量和更新频率
去年我给一个本地家政服务站做优化,对方技术总监上来就说要换Next.js,说WordPress性能太烂。我拦住了他。为什么?因为他的站总共才230个页面,每周更新2-3篇地域服务文章。这种规模下,WordPress+WP Rocket缓存插件完全够用。
我自己的网站也是这么过来的。当时手头一个教育机构站,450个页面,招生季每周更新5-8篇内容。用WordPress 6.4 + WP Rocket 3.16,页面加载时间稳定在1.2s,首屏时间0.8s。服务器成本:一台2核4G的阿里云轻量服务器,月费98块,加上WP Rocket年费折合月均55块,总计153块/月。这还没算Cloudflare CDN免费版。
但另一个项目让我改主意了。一个连锁驾校的官网,3000多个分校页面,加上教练详情页、课程对比页,总共4800多页。更新频率极低——每月更新一次价格表。WordPress后台已经卡得不行,页面生成要18秒。迁移到Next.js 14 + Vercel Hobby(免费版)+ Cloudflare后,ISR增量生成,单页构建时间降到0.3s。成本?Vercel Hobby免费,Cloudflare免费,域名续费一年60块,月均才5块。
关键决策点在这儿:如果页面数<500且更新频率>1次/周,WordPress+WP Rocket方案更划算——月均1200块(我算上高级主题和插件费用)就能搞定。如果页面>1000且更新频率<1次/月,Next.js+ISR值得换,成本月均380块(Vercel Pro $20起,加上Cloudflare Workers)。
但迁移成本算过没?我用Next.js重写那个驾校站,花了4天半时间——路由重构、ISR配置、图片优化、SEO元数据迁移。WordPress的AIO SEO插件导出数据,再写脚本转成Next.js的metadata API,光是JSON-LD结构化数据就调了2轮。在核子GEO上跑了一遍GEO检测,发现迁移后AI引用率从12%涨到34%,但百度收录率从28%掉到19%——因为ISR生成的新URL没及时提交。
所以我现在的原则:别跟风换框架。先用核子GEO检测工具跑一遍诊断,输入域名就能看到GEO检测分数和收录数据。如果百度收录率低于30%,先别管框架,把URL提交和sitemap优化搞利索。迁移是兜底一句一步。
避坑清单
- 页面<500且更新频繁,别换Next.js——WordPress+WP Rocket更省心,省钱又省时间
- 页面>1000且更新少,再考虑Next.js+ISR——但提前算好迁移成本,至少3-5天开发
- ISR生成后记得主动提交URL到百度站长平台,否则收录率会掉10-15%
- 迁移前用核子GEO检测工具跑一遍GEO检测,记下AI引用率和收录率基线,方便对比效果
避坑清单
-
别信百度官方说“收录正常”
我去年发现20%的招生页面过了两周还没索引,客服说“在排队”。踩坑后果:流量从2700直接掉到410,浪费了2周黄金窗口。现在我用核子GEO检测工具跑一遍收录报告,看到具体哪个URL被拒了,直接去百度站长手动提交。 -
更新频率不能随性,要卡准119区间
教育站招生季每天发3篇,结果收录率从30%降到15%。百度索引器不是人,你一天发10篇它会当垃圾。我踩过这坑后,改成每周一三五固定更新,每次4-6篇,收录率稳定在41%。别学我当初一天更20篇,那是自杀。 -
Next.js的SSR模式别开全量
本地服务站点首页和落地页用SSR,其他页面用ISR(增量静态生成)。我之前傻乎乎全SSR,服务器每秒请求撑到2.7万,撑了3小时崩了,花了2天修复。Vercel免费套餐禁不起这么造,得配CDN。 -
百度地图标注别只挂一个地址
本地客户要覆盖朝阳区、海淀区,我当初只写“北京校区”,结果地图排名第11页。后来每个分校区独立页面,地址精确到“北京市海淀区中关村大街X号”,Google Business Profile也得单独开。现在地图曝光量从2300涨到1.2万。 -
内链结构不能瞎连
招生页链到课程详情页,课程详情页又链回首页,百度爬虫抓了10层都找不到重点。我用核子GEO的网站对比功能跑了一下自家和竞品(竞品是xx机构),发现他们内链深度控制在3层内,我直接重构了导航和面包屑,收录率从31%涨到49%。 -
别等Google Business Profile自动审核
我提交了3次地址变更,等了2周都没动静。后来手动给Google客服发邮件+上传门头照,3天过了。本地服务客户,地图排名上不去,流量直接腰斩。现在新客户我当天必须搞定GBP验证。 -
旧页面301别一股脑转
我把废课页面301到首页,结果百度索引了1000+旧URL,跳转链多了,收录率掉到28%。应该每个旧URL对到相关性高的新页面,或者直接删掉。这坑我花了2000块雇人手动梳理才解。