你在 public/robots.txt 或 src/pages/robots.txt.ts 里加了一条 Disallow: /admin/,部署后 Search Console 的 robots.txt 报告看到的还是上一版本,Googlebot 也在继续抓 /admin/。这通常不是语法错。
最快的判断: 跑一条 curl -s "https://yourdomain.com/robots.txt?cb=$(date +%s)"。如果这条带 cache buster 的请求显示的是你的新规则,说明源站没问题,旧版本来自某一层缓存——单独清掉 CDN 里这一个文件,再等 Google 自己的 24 小时缓存过期即可。如果带 buster 的请求显示的还是旧规则,那是部署根本没把新文件发出去,多半是残留的 public/robots.txt 把动态路由覆盖了。
为什么会这样:Google 缓存 robots.txt 最长 24 小时(遇到 5xx 或超时还会更久),CDN 又叠了一层缓存,而且很多项目里其实有两份 robots.txt 在互相覆盖。本文按命中率把它拆成 5 类原因,每类都给一条可以直接 curl 或在 Search Console 里验证的判断方式。
你属于哪一种情况
先把”最短修复路径”Step 1 的两条 curl 跑一遍,再对照下表。
| 带 buster 的 curl 显示 | 不带 buster 的 curl 显示 | 最可能的原因 | 跳到 |
|---|---|---|---|
| 新规则 | 旧规则 | CDN 边缘缓存 | 原因 2,Step 2 |
| 新规则 | 新规则 | Google 自己的 24h 缓存 | 原因 1,Step 4 |
| 旧规则 | 旧规则 | 重复文件,或部署没发出去 | 原因 3,Step 3 |
| 新规则但 URL 仍被收录 | — | Disallow 无法去索引 | 原因 4,Step 4 |
content-type 是 text/html 或非 200 | — | 路由错了,或返回的是错误页 | 原因 5 |
常见原因
按命中率从高到低。
1. Google 的 robots.txt 缓存还没过期
Google 官方文档明确说会缓存 robots.txt 最长 24 小时,遇到超时或 5xx 无法刷新时还会缓存更久。你刚改完,Googlebot 用的还是缓存里那一份。Google 也正因如此建议不要一天内反复改 robots.txt——在缓存换新之前,你那些频繁改动它根本看不到。
如何判断:Search Console → 设置 → robots.txt 报告,看 “Checked on” 时间,那是 Google 最近一次抓取该文件的时刻。如果是几小时前抓的、内容是旧版,就是缓存。
2. CDN 给 robots.txt 加了 edge cache
Cloudflare、Vercel Edge 默认对 .txt 文件按 Cache-Control 头缓存,常见 4 小时甚至 24 小时。源站是对的,但 Googlebot 从 CDN 边缘拉到的还是旧版。
如何判断:
curl -I "https://yourdomain.com/robots.txt"
看响应头里 cf-cache-status / x-vercel-cache:HIT 说明这次是从缓存返回的。age 字段告诉你这份缓存已经放了多少秒。
3. 同时存在两份 robots.txt,静态优先
最坑的一种:你在 Astro / Next 里写了动态路由 src/pages/robots.txt.ts 生成 robots,但 public/robots.txt 里还有一份历史文件没删。绝大多数框架对 public/ 里的静态文件优先输出,动态路由被悄悄覆盖。
如何判断:
ls public/robots.txt src/pages/robots.txt* 2>/dev/null
如果两个都存在,问题就是它:静态的 public/robots.txt 赢了,你的动态路由根本没跑。
4. 用 Disallow 想达到 noindex 的效果
Disallow: /private/ 只是阻止抓取,不阻止索引。如果别处有外链指向那个 URL,Google 仍可能收录(搜索结果里显示 “No information is available for this page”)。你以为 robots.txt 失败了,其实它做了它该做的——只是你需要的是另一个工具。
如何判断:在 Search Console → URL 检查器(URL Inspection)里输入该 URL。如果 “Crawl allowed?” 是 No、但页面仍在索引里,说明 robots 生效了,你真正需要的是 noindex。
5. 大小写、字段名或 content-type 错了
disallow: 必须是 Disallow:(首字母大写)。User-agent: 不能写成 User-Agents:。通配符规则有放置要求——Google 支持 * 和 $,但像 Disallow: */admin/ 这种写法会被宽松处理,部分非 Google 的 bot 干脆完全忽略通配符。更常见的一种翻车:你的路由把规则当成 text/html(或一个 404 页面)返回,而 Google 只认 HTTP 200 + text/plain 正文。
如何判断:Google 已在 2023 年底 / 2024 年下线了独立的 robots.txt 测试工具(tester),Console 里不再有内置测试器。改用以下方式:把具体路径粘到 URL Inspection 看是否被屏蔽;看 robots.txt 报告里列出的 warnings;或者用 Google 的开源 robots.txt 解析器在本地离线校验——那正是 Googlebot 自己用的那套库。
最短修复路径
按收益从高到低,前 3 步通常就能解决 80% 的问题。
Step 1:用 curl + cache buster 拿到真正的源站响应
curl -I "https://yourdomain.com/robots.txt?cb=$(date +%s)"
curl -s "https://yourdomain.com/robots.txt?cb=$(date +%s)"
第一条看响应头,第二条看内容。判断点:
- 状态码必须
200(不是301/404) content-type必须是text/plain(不能是text/html——HTML 正文会被 Google 忽略)cf-cache-status/x-vercel-cache应当是MISS或DYNAMIC(buster 强制重新拉取)- 内容是你最新版本
把结果对照上面的”你属于哪一种情况”表。简单说:带 buster 是新版、不带是旧版 → 缓存问题(走 Step 2);带 buster 也是旧版 → 部署根本没发出这个文件(走 Step 3)。
Step 2:只清 /robots.txt 这一个文件的 CDN 缓存
不要 purge everything——那会无谓地冷启动你整站的缓存。
- Cloudflare:Caching → Configuration → Purge Cache → Custom Purge → “Purge by: URL”,粘
https://yourdomain.com/robots.txt。要用完整的 UTF-8 URL,单文件 purge 不支持通配符。 - Vercel:每次新的生产部署会自动清空 edge cache,所以在项目根目录跑
vercel --prod --force(顺带绕过 build cache),或在 dashboard 点 Redeploy。想重新部署某个已有构建,用vercel redeploy <deployment-url> --target production。 - Netlify:Deploys → Trigger deploy → Clear cache and deploy site。
清完再用 Step 1 不带 buster 的 curl 验证 cf-cache-status: MISS(下次请求会再变回 HIT),并确认内容已更新。
Step 3:确保只有一个 robots.txt 来源
# 删掉 public/robots.txt,只保留动态路由(或反过来)
rm public/robots.txt
# 或:删掉动态路由
rm src/pages/robots.txt.ts
二选一。然后重新 build + 部署:
npm run build
ls dist/robots.txt && head -20 dist/robots.txt
确认 dist/robots.txt 是你想要的那份。(Next.js App Router 下动态文件是 app/robots.ts,规则一样:public/robots.txt 仍然会盖过它。)
Step 4:让 Google 立刻重抓
到 Search Console → 设置 → robots.txt 报告 → 点文件旁边的 more settings(三个点)图标 → Request a recrawl。“Checked on” 时间通常几分钟到一两小时内更新;Google 把这条路径定位为应急通道,对应平时约 24 小时的自动刷新。
如果你想完全去索引(不只是禁止抓取),别在 robots.txt 里 Disallow,改用页面级 meta:
<meta name="robots" content="noindex">
或者在 HTTP 响应头里(PDF 等非 HTML 文件更适合用这种):
X-Robots-Tag: noindex
robots.txt 的 Disallow 实际上会阻止 Google 抓取页面,从而根本看不到那个 noindex 标签,所以两者用法是相反的。要去索引,应当让 URL 保持可抓取、加上 noindex、等它重抓一次,确实想连抓取一起挡时再补 Disallow。
怎么确认修好了
- 不带 buster 的
curl -s https://yourdomain.com/robots.txt返回新规则,200+text/plain,且cf-cache-status: MISS(或age很小)。 - Search Console 的 robots.txt 报告里 “Checked on” 是近期时间,显示的内容和你的新文件一致。
- 对被屏蔽的路径,URL Inspection 显示 “Crawl allowed? No”。对要去索引的路径,显示 “Indexing allowed? No”,并在下次抓取后从 Pages → Indexed 报告里掉出去。
预防建议
- 把 robots.txt 的”来源”写进 README,让团队所有人清楚是
public/还是动态路由,绝不能两份都有。 - 部署前用 Google 的开源 robots.txt 解析器校验改动——Console 里那个内置 tester 已经没了。
- 想去索引就用
noindexmeta /X-Robots-Tagheader,不要用Disallow。 - 部署后加一条冒烟测试,
curl /robots.txt并断言200+text/plain+ 最新内容。 - 给 robots.txt 设较短的
Cache-Control(如max-age=600);Google 会参考较低的max-age,CDN 持有旧版的时间也会更短。
常见问题
改完 robots.txt,Google 多久才会照新的执行? 靠它自己最长 24 小时;在 robots.txt 报告里点 “Request a recrawl” 则通常几分钟到一两小时。CDN 缓存是另一回事,可能再叠几小时——把它清掉(Step 2),Googlebot 下次来才能拉到新版。
我在 robots.txt 里屏蔽了某个 URL,它却还在 Google 索引里,为什么?
Disallow 拦的是抓取,不是索引。Google 已经知道、或被别站外链指向的页面,仍可能带着 “No information is available” 摘要留在索引里。要移除,得先放开抓取再加 noindex(Step 4);继续屏蔽抓取反而会让 Google 看不到那个 noindex。
curl 已经是新文件,但 Googlebot 还在用旧的,怎么办? 说明源站和 CDN 都对了,这是 Google 自己的 24 小时缓存。在 robots.txt 报告里 Request a recrawl,然后等。没有更快的官方手段。
Allow 和 Disallow 规则的先后顺序有影响吗?
对 Googlebot 没有——它按最具体(路径最长)的规则匹配,与书写顺序无关。所以对 /admin/public/page 来说,Allow: /admin/public/ 会胜过 Disallow: /admin/。但有些爬虫用”首条匹配”,所以规则尽量写得不产生歧义。
为什么我的动态 robots.txt.ts 路由被忽略了?
静态的 public/robots.txt(Astro/Vite),或与 app/robots.ts 并存的 public/robots.txt(Next.js),会被优先输出,动态路由根本不运行。删掉静态文件(Step 3)再重新 build 即可。
能屏蔽单个 PDF 或图片不进搜索吗?
Disallow 只挡抓取;要真正去索引非 HTML 文件,用 X-Robots-Tag: noindex 响应头——你没法往 PDF 里塞 meta 标签。