网站改版后域名收录断崖式下跌,排查发现是由于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 附加到替换目标后。但有两种场景会导致参数意外丢失:
- 目标 URL 的末尾显式带有了问号(例如目标结尾写成了
...$1?),这会触发 Nginx 清空原始参数的机制。 - 在复杂的
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 强制跳转的冲突上:
- CDN 后台开启的是 Flexible(灵活)SSL 模式。也就是说,浏览器到 CDN 节点走的是安全的 443 端口 HTTPS,但 CDN 到源站服务器之间,出于省事的考虑,默认走了 80 端口 HTTP 回源。
- 源站 Nginx 服务器上,配置了一段强制跳转 HTTPS 的标准规则:
server { listen 80; server_name www.new.domain.com; return 301 https://$host$request_uri; } - 冲突逻辑就此闭环:
- 爬虫访问
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 积累到了几千条,搜索引擎会判定该站点的目录管理混乱,甚至拉低整站的质量评级。
合理的淘汰与归档策略
对于改版中淘汰的页面,正确的处理逻辑分为两类:
- 有近似内容的:重定向到该类目的列表页或相关主题文章,而不是全站首页。例如
/old-topic/database-fix重定向到/category/database/。 - 彻底废弃且无替代内容的:坚决不要做 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 规则表的最前端。
等到站长平台里的“网站改版”规则通过验证、新域名的收录量逐步追平老域名时,这一套平移工作才算真正落了地。
文章标题:网站改版换域名收录断崖式下跌?排查 301 重定向链、死循环与参数丢失我踩过的几个坑
文章链接:https://llbbs.cn/jishujaocheng/222.html
本站文章均为原创,未经授权请勿用于任何商业用途
评论一下?