爬虫看到的是另一个网站:抓取路径排查

一、那个「什么都没做」的流量断崖

今年三月我在 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,页面前面留下的索引永远不会被清掉。

判断「该用哪个」的简单规则:

  1. 不想让页面出现在结果里,但页面有外链或曾经被索引 → noindex,并且允许抓取;
  2. 纯资源目录(/admin/、/search?q= 的无参数版本、/api/),从来不需要索引 → Disallow 省抓取预算;
  3. 已经 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 数。如果请求总数下降而响应时间上升,因果就很明确:先把服务器响应修好。

  1. 降低 TTFB:加页面缓存(fastcgi_cache 或静态化),把 TTFB 压到 200ms 以内。
  2. 减少页面体积:单页超过 1~2MB 会让抓取预算被大页面吃掉,图片懒加载 + 压缩。
  3. 稳定返回码:同一 URL 有时 200 有时 503,爬虫会认为站点不可靠。503 只能用于维护窗口,并且要带 Retry-After。
  4. 别对 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.com

IP 段要从 Google 官方发布的 googlebot.json 拉,不要用网上抄来的清单,那些清单几个月就过期了。

八、一份每周十五分钟的检查清单

这套检查我每周一早上跑,覆盖了 90% 的「收录异常」:

  1. GSC 覆盖率报告:看错误分类的增量(不是总数),重点看「已抓取但未编入索引」的涨幅;
  2. curl 抽查 5 个核心页面:状态码、Content-Type、canonical、X-Robots-Tag;
  3. 重定向链长度:抽查最近改过的 URL,num_redirects 必须 ≤ 1;
  4. 日志里的爬虫路径:grep 出 Googlebot 的 top30,确认新文章在其中;
  5. robots.txt 与 sitemap:sitemap 里最近新增的 10 条 URL 抽查是否 200;
  6. 软 404:随机访问三个不存在的 URL,必须返回真 404 而不是 200。

最后这条最容易被忽略——很多「收录掉了一半」的问题根源就是软 404 泛滥,导致爬虫把时间浪费在无穷无尽的假页面上。判定方法在 Nginx 日志分析那篇里讲得更细,一句话:「不存在的 URL 返回 404」是 SEO 的地基,地基歪了,上面做什么优化都是在沙子上盖楼。

九、写在最后

做技术的人做 SEO,最大的优势是能看见「搜索引擎眼里的网站」——不是漂亮的主题、不是华丽的动画,而是 HTTP 状态码、响应头、字节数、抓取路径。这些都在你自己的服务器上,用 curl 和日志就能看到,比任何 SEO 工具的猜测都可靠。

所以我的建议是:先别急着研究关键词和外链,花一个下午把自己的站点当成爬虫走一遍,把那些断掉的、绕远的、指错的链路修好。你会发现,很多「莫名其妙没流量」的问题,答案就在自己的 access.log 里。

Last modification:October 23rd, 2026 at 09:25 pm

Leave a Comment