一、那个「什么都没做」的流量断崖
今年三月我在 Search Console 里看到一个诡异的现象:某个站的自然搜索展示量在一周内从每天 4000 掉到 600,排名没有整体下移,索引页数也没变,后台没有任何手动操作处罚。翻了七天的改动记录:只改了一个 .htaccess——为了修一个 301 问题,加了一条 RewriteRule。
最后定位到:那条规则让所有带 /tag/ 的 URL 多了一跳 301,而 Google 的抓取预算被这一跳吃掉了一大半。更糟的是,多跳 301 在爬虫看来是「不稳定信号」,它停止了重新抓取这些页面,几周后这些页面的排名缓慢消失。
这件事教给我一个道理:收录和排名,很多时候不是内容问题,也不是外链问题,而是「爬虫能不能顺着干净的链路走到你的页面」。这篇文章讲的是「抓取路径」——从 robots.txt 到 canonical,从内链深度到重定向链,那些决定爬虫能否顺利见到内容的工程细节。
二、先搞清楚爬虫到底看到了什么
做 SEO 排查最常见的错误是「用浏览器判断」。浏览器有登录态、有缓存、执行 JS、会跟随重定向,而爬虫基本不执行 JS、不带 Cookie、看的是原始 HTML。判断「爬虫看到什么」,只能用 curl:
# 完整模拟一次爬虫访问:不跟随重定向,看首跳
curl -s -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
-H "Accept-Encoding: gzip" -o /dev/null -D - https://www.example.com/some-page
# 跟随重定向,看完整链路和总耗时
curl -sL -o /dev/null -w 'hops=%{num_redirects} code=%{http_code} total=%{time_total}\n' \
https://www.example.com/some-page第一个命令得到的响应头,是判断「收录问题」的第一手证据。八个关键点:
HTTP/2 200或301:状态码,决定爬虫继不继续;Location::跳转目标,看是否跳到了错误的地方;Content-Type:必须是text/html,返回text/plain或application/octet-stream的页面 Google 不会当网页索引;X-Robots-Tag:HTTP 头的 noindex,很多 CDN/WAF 会偷偷加;Set-Cookie:给每个访客发新 session cookie 会破坏 CDN 缓存,还可能导致爬虫得不到一致内容;Vary: User-Agent:如果你对 UA 返回不同内容,必须声明,否则 CDN 会把爬虫的版本缓存给用户(或反过来);Link: rel=canonical:HTTP 头形式的 canonical,容易被忽略但与 HTML 里的冲突;Cache-Control:太短的 max-age 会让爬虫每次都回源,浪费抓取预算。
三、robots.txt:不是「不想收录就 Disallow」
这是最普遍、后果最严重的误解。规则很简单,但后果不对称:
Disallow阻止抓取,不阻止收录。一个 URL 如果没有任何其他信号,爬虫不能抓就无法知道它是 404 还是应该被移出索引,结果往往是没有描述的收录(只显示 URL,或者从外链锚文本猜标题)。- 想阻止收录,正确工具是
noindex(meta 或 HTTP 头)——而noindex必须能被抓到才生效。 - 两者混用是死锁:
Disallow: /private/加上/private/页面里的noindex,爬虫看不到 noindex,页面前面留下的索引永远不会被清掉。
判断「该用哪个」的简单规则:
- 不想让页面出现在结果里,但页面有外链或曾经被索引 →
noindex,并且允许抓取; - 纯资源目录(
/admin/、/search?q=的无参数版本、/api/),从来不需要索引 →Disallow省抓取预算; - 已经
noindex并确认退出索引后,才能加Disallow省预算。
还有个细节:Disallow: / 会让爬虫连 robots.txt 之后的第一个页面都不抓,看起来「保护了内容不被抄」,实际是把整站从索引里删掉。我见过有人为了防采集加这行,一个月后流量归零。
3.1 robots.txt 里容易被忽略的两条
# 指向 sitemap(不是必须,但强烈建议)
Sitemap: https://www.example.com/sitemap.xml
# 屏蔽带参数的搜索页,但不要屏蔽 /search 本身(防止与 noindex 冲突)
Disallow: /search?*注意 Allow 的优先级:同长度时 Allow 优先,更长的规则优先。所以 Disallow: /private/ 配合 Allow: /private/public/ 是有效的,这套「白名单」写法在需要开放某个子目录时很实用。
四、canonical:合并权重的唯一正确方式,也是最容易被写坏的
canonical 的作用是告诉搜索引擎「这几个 URL 是同一个内容,请以我指定的那个为准」。它在三种情况下会失效:
4.1 canonical 指错方向
分页页面的常见错误:第 2 页的 canonical 指向第 1 页。这会让第 2 页的内容永远不参与排名,而且如果第 1 页没有指向第 2 页的链接,第 2 页上的文章可能永远不被发现。正确做法是每页 canonical 指向自己(self-referencing),只有真正重复的内容(比如带 ?utm= 的变体)才指向主版本。
<link rel="canonical" href="https://www.example.com/post/123">
<!-- 带参数的版本指向无参数版本 -->
<!-- /post/123?utm_source=weixin 的页面里写: -->
<link rel="canonical" href="https://www.example.com/post/123">4.2 相对路径与协议不一致
href="/post/123" 这种相对写法的解析规则和浏览器一致,一般没问题,但如果页面同时能通过 http 和 https 访问,或者 www 和非 www 都能访问,canonical 必须写绝对 URL 且带 www 与 https,与主域名完全一致。我见过 canonical 写 example.com 而站点跳 www.example.com,结果两个版本各自被索引。
4.3 与 noindex、hreflang 冲突
同一个页面既有 canonical 又有 noindex 是自相矛盾的信号——等于告诉搜索引擎「别看这个,但看那个」和「别索引这个」。这种页面通常两边都不收录。多语言站也一样:hreflang 指向的页面必须是可索引的,不能指向 noindex 页面。
4.4 怎么批量检查 canonical
手点几十个页面不现实,脚本化最快:
#!/bin/bash
# 批量检查 canonical 是否与自己一致
while read -r u; do
canon=$(curl -sL --max-time 15 -A "Mozilla/5.0" "$u" \
| grep -oiE '<link[^>]+rel="canonical"[^>]+>' | head -1 \
| grep -oiE 'href="[^"]+"' | cut -d'"' -f2)
echo "$u -> $canon"
done < urls.txt重点关注两类异常:canon 为空(忘了写)、canon 与 u 不一致但不是「带参数→无参数」的合理情况。跑一遍几十个核心页面,五分钟能揪出大半问题。
五、内链深度:爬虫能不能「走到」你的新文章
抓取预算是有限的,而预算的分配很大程度上取决于页面距离首页的点击深度。首页是爬虫最常来、也是权重最高的地方。一个页面如果只有 sitemap 里的链接而没有站内链接指向它,它被称为「孤岛页面」——Google 对孤岛页面的抓取频率极低,很多页面几个月都不会被抓。
5.1 三个制造孤岛的操作
- 翻页只给最新 N 页:首页只列 10 篇、归档页也只有 10 篇,第 11 篇以后的文章没有任何站内入口,只能靠 sitemap。正确做法是保留完整的按月份归档,或者至少给「上一页/下一页」做成分页链接(不是 AJAX 加载)。
- 标签页/分类页 noindex:很多人把分类页 noindex,认为「重复内容」。但分类页是最好的内链枢纽,noindex 之后爬虫仍会抓(除非 Disallow),可参考价值下降。我的做法是分类页保留可索引但写好描述。
- JS 加载的相关文章:如果「相关推荐」是 AJAX 拉的,爬虫通常看不到,等于不存在。想让它成为内链,就得服务端渲染进 HTML。
5.2 用 Nginx 日志验证爬虫的实际路径
GSC 的「抓取统计信息」是个黑盒,不如直接看自己的日志。一条命令看 Googlebot 最近抓了哪些页面:
grep -i 'googlebot' /var/log/nginx/access.log \
| awk '{print $7}' | sed 's/?.*//' | sort | uniq -c | sort -rn | head -30如果 top30 全是首页、分类页和几年前的老文章,而新文章一条都没有,说明新文章没有被有效内链。这时候去查:新文章发布后是否出现在首页列表?是否有「上一篇/下一篇」?归档页是否更新?
还有一个值得看的指标:爬虫抓取的响应时间。Googlebot 会主动降低对慢站的抓取频率。如果服务器对爬虫的响应经常超过 1 秒,抓取频率会一路走低,直到你「看起来没被惩罚但就是没流量」:
grep -i 'googlebot' /var/log/nginx/access.log \
| awk '{rt=0; for(i=1;i<=NF;i++) if($i ~ /^rt=/){split($i,a,"="); rt=a[2]} if(rt>0.5) c++} END{print "slow-bot-requests=", c+0}'六、重定向链:SEO 里的「隐形成本」
每一次 301 都会消耗一点抓取预算,而且链条越长,权重传递的衰减越明显(Google 官方说 PageRank 经过多次跳转会衰减,所以建议一跳到位)。检查链条长度:
curl -sIL -o /dev/null -w 'num_redirects=%{num_redirects}\n' https://www.example.com/old-page理想情况是 num_redirects=0(直接 200)或 1(一跳到位)。典型的多跳链路是这样形成的:
http://example.com/old → https://example.com/old(强制 HTTPS)→ https://www.example.com/old(强制 www)→ https://www.example.com/new(内容迁移)
三跳。修法是在一条规则里完成全部变换:
# Nginx 里把 http/非www 的旧地址直接跳到最终的 https+www 新地址
server {
listen 80;
server_name example.com www.example.com;
location = /old {
return 301 https://www.example.com/new;
}
location / {
return 301 https://www.example.com$request_uri;
}
}
server {
listen 443 ssl;
server_name example.com;
return 301 https://www.example.com$request_uri;
}关键点是:location = /old 的精确匹配优先于 location /,所以 http 上的 /old 会一次跳到最终的 https+www+new,省掉两跳。这类规则迁移几十上百条的时候,用 map 表更省事:
map $uri $legacy_redirect {
default "";
/old https://www.example.com/new;
/old-post-1 https://www.example.com/new-1;
/tag/foo https://www.example.com/topics/foo;
}
server {
if ($legacy_redirect != "") {
return 301 $legacy_redirect;
}
}这里的 if 是在 server 块里配 return,属于「if is evil」文档里明确安全的用法(return 和 rewrite ... last 是安全的,其余不行)。
七、被抓取频次掉了,怎么办
GSC 的「抓取统计信息」里能看到三组数字:抓取请求总数、平均响应时间、抓取到的 KB 数。如果请求总数下降而响应时间上升,因果就很明确:先把服务器响应修好。
- 降低 TTFB:加页面缓存(fastcgi_cache 或静态化),把 TTFB 压到 200ms 以内。
- 减少页面体积:单页超过 1~2MB 会让抓取预算被大页面吃掉,图片懒加载 + 压缩。
- 稳定返回码:同一 URL 有时 200 有时 503,爬虫会认为站点不可靠。503 只能用于维护窗口,并且要带
Retry-After。 - 别对 Googlebot 限速:
limit_req对真 Googlebot 返回 429,会直接被判定为降频信号。用geo+map给已验证的爬虫放开。
验证爬虫身份最稳的方式不是看 UA,而是反向解析 IP:
# 先反解,再正解比对,两者一致才是真 Googlebot
dig +short -x 66.249.66.1
# 得到 crawl-66-249-66-1.googlebot.com
dig +short crawl-66-249-66-1.googlebot.comIP 段要从 Google 官方发布的 googlebot.json 拉,不要用网上抄来的清单,那些清单几个月就过期了。
八、一份每周十五分钟的检查清单
这套检查我每周一早上跑,覆盖了 90% 的「收录异常」:
- GSC 覆盖率报告:看错误分类的增量(不是总数),重点看「已抓取但未编入索引」的涨幅;
- curl 抽查 5 个核心页面:状态码、Content-Type、canonical、X-Robots-Tag;
- 重定向链长度:抽查最近改过的 URL,
num_redirects必须 ≤ 1; - 日志里的爬虫路径:grep 出 Googlebot 的 top30,确认新文章在其中;
- robots.txt 与 sitemap:sitemap 里最近新增的 10 条 URL 抽查是否 200;
- 软 404:随机访问三个不存在的 URL,必须返回真 404 而不是 200。
最后这条最容易被忽略——很多「收录掉了一半」的问题根源就是软 404 泛滥,导致爬虫把时间浪费在无穷无尽的假页面上。判定方法在 Nginx 日志分析那篇里讲得更细,一句话:「不存在的 URL 返回 404」是 SEO 的地基,地基歪了,上面做什么优化都是在沙子上盖楼。
九、写在最后
做技术的人做 SEO,最大的优势是能看见「搜索引擎眼里的网站」——不是漂亮的主题、不是华丽的动画,而是 HTTP 状态码、响应头、字节数、抓取路径。这些都在你自己的服务器上,用 curl 和日志就能看到,比任何 SEO 工具的猜测都可靠。
所以我的建议是:先别急着研究关键词和外链,花一个下午把自己的站点当成爬虫走一遍,把那些断掉的、绕远的、指错的链路修好。你会发现,很多「莫名其妙没流量」的问题,答案就在自己的 access.log 里。