侧边栏壁纸
  • 累计撰写 164 篇文章
  • 累计收到 2 条评论

网站改版换域名收录断崖式下跌?排查 301 重定向链、死循环与参数丢失我踩过的几个坑

2026-10-5 / 0 评论 / 7 阅读
AI 摘要由 AI 生成

网站改版后域名收录断崖式下跌,排查发现是由于301重定向链过长、死循环和参数丢失等问题导致。文章强调了重定向链过多会消耗爬虫抓取预算,影响权重传递,并提供了排查工具和定位方式。建议直接重定向到新域名最终地址,避免权重损失。

上个月给一个运营了三年的老站换域名兼做目录结构升级,本来预期是一次平稳的 301 权重过渡,结果上线第二周,站长后台的数据曲线像坐了过山车一样直线下坠。百度索引量在几天内掉了近四成,Google Search Console 更是报出了几百个“网页存在重定向错误”和“排查未编入索引的页面”。

去终端里挑了几个以前权重最高的长尾词 URL,顺着用 curl 挨个追踪,才发现好端端的 301 早就变成了一张四处漏风的网:有的链接在中间多绕了两道弯,有的因为正则配置不严谨把查询参数截断了,甚至还有几个关键栏目在 CDN 和源站之间玩起了无限死循环。

很多做技术和运维的朋友习惯把 301 当成纯粹的配置项,以为在 Nginx 里写一句跳转就万事大吉。但在搜索引擎爬虫的眼里,301 是权重视图重建的敏感通道。只要中间有一环卡住,权重传递就会在断裂点戛然而止。


坑一:多跳重定向链(Redirect Chain),把爬虫的抓取预算耗光了

所谓重定向链,就是从用户或爬虫请求的初始地址到最终目标页面之间,经历了两次或更多次的中间跳转。

在我的排查现场,一个旧文章页的实际跳转路径是这样的:

http://old.domain.com/posts/1024.html
  -> (301) https://old.domain.com/posts/1024.html      [HTTP 强制跳 HTTPS]
  -> (301) https://new.domain.com/posts/1024.html      [旧域名跳新域名]
  -> (301) https://new.domain.com/article/1024.html    [目录结构迁移 posts -> article]
  -> (301) https://www.new.domain.com/article/1024.html [根域名统一规范跳 www]

这一个页面在最终加载前,足足跳了 4 次。

在现代浏览器的宽带环境下,4 次跳转可能也就消耗两百多毫秒,普通访客甚至很难感知到中间的停顿。但搜索引擎爬虫对每个站点的抓取资源(Crawl Budget)是有严格配额限制的。无论是 Googlebot 还是 Baiduspider,爬虫客户端在遇到连续 301 时都会设定重试阈值。一旦跳转超过 2 跳,爬虫大概率会判定这个路径存在低效循环的风险,中途放弃对最终 URL 的内容抓取,或者把抓取优先级降到最低。

更严重的问题在于权重损耗。每一次跳转,搜索引擎在评估页面相关性和链接信任度时都会打折扣。4 跳下来,老页面积累多年的权重基本在路上散了个干净。

排查工具与定位方式

想知道全站有没有隐蔽的重定向链,别用浏览器测试,直接在终端里跑一条 curl 命令看链路:

curl -sIL -o /dev/null -w "%{http_code} -> %{url_effective} (总耗时: %{time_total}s, 重定向次数: %{num_redirects})\n" "http://old.domain.com/posts/1024.html"

或者用以下单行脚本把每一次跳转的中间状态码和 Location 头都抓出来:

curl -s -k -L -v "http://old.domain.com/posts/1024.html" 2>&1 | grep -E "(< HTTP/|< Location:)"

如果输出里出现了两个以上的 HTTP/1.1 301 或 HTTP/2 301,就说明链条过长,必须打散压缩。

根治方案:源站一步到位直接跳最终 URL

旧域名和旧规则的维护原则永远是:不管旧地址来的是什么协议、什么子域、什么旧路径,直接一步到位重定向到新域名最终规范化的绝对地址。

在旧域名的 Nginx 虚拟主机配置中,不要一层套一层地跳,直接在最外层解析映射:

# 旧域名旧服务配置:无论来的是 http 还是 https,直接一步打到新域名的最终规范化地址
server {
    listen 80;
    listen 443 ssl http2;
    server_name old.domain.com www.old.domain.com;

    ssl_certificate /etc/nginx/ssl/old.domain.com.crt;
    ssl_certificate_key /etc/nginx/ssl/old.domain.com.key;

    # 针对旧目录结构直接映射新路径,保留原有文件名与参数
    location ~* ^/posts/(.*)$ {
        return 301 https://www.new.domain.com/article/$1$is_args$args;
    }

    # 其他未命中的旧链接统一一步跳到新域名对应路径
    location / {
        return 301 https://www.new.domain.com$request_uri;
    }
}

改完后再次执行 curl 检查,确保 num_redirects 严格等于 1。


坑二:rewrite 规则贪婪匹配,把查询参数与分页参数吃掉了

很多技术站点和电商列表页带有大量的查询参数,比如文章标签页的分页 ?page=2、筛选条件 ?sort=date&cate=tech。

在改版过程中,我为了把一批格式怪异的伪静态地址重写,随手写了这么一行规则:

# 踩坑配置:错误截断了查询参数
rewrite ^/category/([a-z]+)\.html$ /tags/$1.html permanent;

表面看从 /category/linux.html 跳到 /tags/linux.html 很正常。但当访客或者蜘蛛抓取 /category/linux.html?page=2 时,问题出现了:跳转后的地址变成了 /tags/linux.html,后面的 ?page=2 直接蒸发了。

在搜索引擎眼里,原本包含了几百页高质量历史归档的内容,被这一行规则直接全部拍平成了第 1 页。蜘蛛顺着分页抓不到后续内容,导致全站几万篇长尾文章失去了内部入口,搜索引擎后台紧接着就提示“大量页面内容高度重复”。

为什么参数会丢失?

在 Nginx 的 rewrite 指令中,默认行为确实会自动把原始请求的 query string 附加到替换目标后。但有两种场景会导致参数意外丢失:

  1. 目标 URL 的末尾显式带有了问号(例如目标结尾写成了 ...$1?),这会触发 Nginx 清空原始参数的机制。
  2. 在复杂的 location 分支配合反向代理或内部 try_files 时,变量传递发生覆盖,导致 $args 为空。

规范配置

处理此类带参重定向时,推荐直接使用现代的 return 301 搭配 $request_uri,因为 $request_uri 是原始请求中未经解码的完整 URI(包含路径和问号后的全部查询字符串):

# 推荐做法:使用 return 301 和显式参数透传
location ~* ^/category/(?<cat_name>[a-zA-Z0-9_-]+)\.html$ {
    return 301 https://www.new.domain.com/tags/$cat_name.html$is_args$args;
}

注意这里的 $is_args$args:

  • 如果原始请求带有参数,$is_args 会自动变成 ?,并拼上 $args;
  • 如果原始请求没有参数,$is_args 为空,生成的 URL 就保持干净,绝不会出现末尾多出一个多余问号的丑陋情况。

用 curl 带参跑一次实测验证:

curl -I "https://old.domain.com/category/linux.html?page=3&sort=hot"

检查响应头里的 Location 字段是否完整包含了 https://www.new.domain.com/tags/linux.html?page=3&sort=hot。确认没有多截断哪怕一个字符。


坑三:CDN 灵活 SSL 与源站 Nginx 掐架,酿成重定向死循环

上线第 5 天,百度抓取频次突然从平时的每日数万次骤降到几百次。打开百度站长工具的抓取诊断测试,抓取状态全部飘红,报错信息赫然写着:重定向过多,抓取失败。

我自己打开 Chrome 访问某些栏目,偶尔也会弹出 ERR_TOO_MANY_REDIRECTS。

复盘排查过程

架构拓扑其实很常见:客户端访问 CDN 节点,CDN 节点回源抓取后端的 Nginx 源站服务器。

问题就出在 CDN 回源配置和源站 HTTPS 强制跳转的冲突上:

  1. CDN 后台开启的是 Flexible(灵活)SSL 模式。也就是说,浏览器到 CDN 节点走的是安全的 443 端口 HTTPS,但 CDN 到源站服务器之间,出于省事的考虑,默认走了 80 端口 HTTP 回源。
  2. 源站 Nginx 服务器上,配置了一段强制跳转 HTTPS 的标准规则:
    server {
       listen 80;
       server_name www.new.domain.com;
       return 301 https://$host$request_uri;
    }
  3. 冲突逻辑就此闭环:
    • 爬虫访问 https://www.new.domain.com/article/100.html。
    • CDN 节点接到请求,用 HTTP 协议去打源站的 80 端口。
    • 源站 Nginx 一看,“来的是 HTTP”,立刻返回 301 https://www.new.domain.com/article/100.html。
    • CDN 节点收到 301,以为源站要它重定向,于是再次用 HTTP 回源去要这个新地址。
    • 两台服务器像打乒乓球一样互相踢皮球,在节点和源站之间循环跳了 20 次,最终触发了浏览器的死循环拦截。

人类访客如果碰巧赶上本地浏览器缓存了之前的证书,可能还不会立刻报错;但对爬虫来说,每一个新的 IP 节点来抓取都会撞上这个闭环,判定为死链。

彻底解决冲突的两步操作

第一步,修改 CDN 回源策略。把 Flexible SSL 改为 Full(完全加密) 或 Strict(严格模式),强制 CDN 节点在回源时也必须通过 443 端口发起 TLS 握手。

第二步,在源站 Nginx 中识别真实协议代理头。有时候前端还有反向代理或负载均衡,源站不应该仅凭本地收到的是不是 80 端口来粗暴判断,而要检查 X-Forwarded-Proto:

server {
    listen 80;
    listen 443 ssl http2;
    server_name www.new.domain.com;

    # 如果上游代理或者 CDN 已经确认客户端是 https,源站不再重复跳转
    if ($http_x_forwarded_proto = "http") {
        return 301 https://$host$request_uri;
    }

    # 如果客户端是直接直连 80 端口且没有代理头,才做跳转
    if ($scheme = "http") {
        set $need_redirect 1;
    }
    if ($http_x_forwarded_proto = "https") {
        set $need_redirect 0;
    }
    if ($need_redirect = 1) {
        return 301 https://$host$request_uri;
    }

    # 正常的业务 location 配置...
}

这样源站就能明确分辨出“这是 CDN 已经代理过的安全请求”,不会再盲目甩出 301。


坑四:无法一对一映射的旧页面,偷懒全量 301 甩给首页

老站改版时往往会下线一部分陈旧业务,或者某些专题目录在新架构里根本没有对应的落地页。

我当时为了图省事,心想“反正别让爬虫抓到 404,全弄到首页还能给首页堆权重”,就在 Nginx 最后写了一条兜底:

# 极其危险的做法:把所有未匹配的旧 URL 全跳到首页
location / {
    return 301 https://www.new.domain.com/;
}

结果在 Google Search Console 的“网页编制”报告里,收到了大量 软 404(Soft 404) 警告。

为什么 301 到首页会被当成软 404?

Google 和百度在官方 SEO 技术文档中早就明确阐述过这一点:301 重定向的前提是“目标页面的内容与源页面等价或高度相关”。

如果一篇讲“Linux 磁盘空间排查”的老技术文章,被重定向到了一个罗列全站最新资讯的博客首页,爬虫在对比两边文本时会发现主题完全不相干。爬虫会立刻认定这不是正当的页面迁移,而是一种误导性跳转,直接将该目标标记为 Soft 404。

更糟糕的是,如果这种无脑甩给首页的软 404 积累到了几千条,搜索引擎会判定该站点的目录管理混乱,甚至拉低整站的质量评级。

合理的淘汰与归档策略

对于改版中淘汰的页面,正确的处理逻辑分为两类:

  1. 有近似内容的:重定向到该类目的列表页或相关主题文章,而不是全站首页。例如 /old-topic/database-fix 重定向到 /category/database/。
  2. 彻底废弃且无替代内容的:坚决不要做 301,老老实实返回标准的 404 Not Found,甚至更精准的 410 Gone(表示资源永久下线)。同时把这批废弃 URL 整理成死链文件,在站长平台主动提交死链,让爬虫按正规渠道将其从索引库注销。

在 Nginx 中显式返回 410 的配置非常直观:

# 显式告知爬虫这些旧服务模块已被永久删除,不再提供
location ^~ /legacy-service/ {
    return 410;
}

爬虫收到 410 状态码后,会迅速注销历史索引,不会把抓取配额浪费在徒劳的反复重试上。


坑五:临时重定向 302 混水摸鱼,新旧页面互踩索引

在后端框架(如 Spring Boot、Django 或 Laravel)里写重定向逻辑时,很多开发者随手调用 response.sendRedirect(url) 或者 return redirect(url)。

在绝大多数 Web 框架中,这类默认重定向方法返回的 HTTP 状态码都是 302 Found 或 307 Temporary Redirect,而不是 301。

302 的语义是“临时重定向”。当搜索引擎蜘蛛抓到 302 时,它的逻辑是:

  • 既然是临时的,那旧 URL 依然是合法的权威页面,继续保留在搜索结果列表里。
  • 新 URL 只是临时借用,不会把旧 URL 积累的 PageRank 和外链权重迁移给新 URL。

这直接导致了最尴尬的局面:老域名在搜索结果里排着名,但点进去会跳新站;新站收录慢如蜗牛,迟迟无法接管关键词排名。新旧两个 URL 在同一个搜索词下互相竞争、互相拉扯。

验证全站返回码的自动化脚本

为了避免框架层或个别规则写错状态码,上线前后必须拿一份几百条核心页面的旧 URL 样本,跑一轮自动化批量检测。

用下面这个轻量 Python 脚本批量校验,能快速挑出那些混杂在 301 里的 302 和 200:

import http.client
from urllib.parse import urlparse

# 抽样待检查的旧 URL 清单
test_urls = [
    "http://old.domain.com/posts/1.html",
    "http://old.domain.com/category/tech.html",
    "http://old.domain.com/about.html",
    "http://old.domain.com/posts/100.html?ref=baidu",
]

def check_redirect(url):
    parsed = urlparse(url)
    conn = (
        http.client.HTTPSConnection(parsed.netloc, timeout=10)
        if parsed.scheme == "https"
        else http.client.HTTPConnection(parsed.netloc, timeout=10)
    )

    path = parsed.path + ("?" + parsed.query if parsed.query else "")
    conn.request("HEAD", path, headers={"User-Agent": "Mozilla/5.0 (compatible; Baiduspider/2.0)"})
    resp = conn.getresponse()
    location = resp.getheader("Location", "")
    conn.close()

    status = resp.status
    if status == 301:
        print(f"[OK 301] {url} -> {location}")
    elif status in (302, 307):
        print(f"[WARN {status}] 临时重定向,权重无法迁移: {url} -> {location}")
    elif status == 200:
        print(f"[ERROR 200] 未发生跳转,仍直接返回原页面: {url}")
    else:
        print(f"[{status}] {url}")

for u in test_urls:
    try:
        check_redirect(u)
    except Exception as e:
        print(f"[FAIL] {u} 请求异常: {e}")

只有当终端里跑出的结果清一色都是 [OK 301] 并且 Location 符合新规范时,迁移才算稳妥。


部署后的收尾与日志盯盘

改版上线的头两个星期,别急着把旧服务器关停。

每天早晚各做一件事:从 Nginx 访问日志里过滤出主流搜索引擎爬虫的请求记录,观察抓取状态码的分布比例:

# 查看今日 Baiduspider 抓取旧域名的响应状态码分布
grep "Baiduspider" /var/log/nginx/old_domain_access.log | awk '{print $9}' | sort | uniq -c | sort -nr

正常健康的过渡期日志中,301 应该占据 90% 以上,伴随少量预期的 404/410。

如果日志里出现了密集的 500、502、或者非预期的 200,说明跳转规则里还有漏网之鱼。把有问题的路径挑出来,直接针对性修补到 Nginx 规则表的最前端。

等到站长平台里的“网站改版”规则通过验证、新域名的收录量逐步追平老域名时,这一套平移工作才算真正落了地。

评论一下?

OωO
取消