搜索全站
Google SEO 9 分钟阅读

批量 301 重定向规划与验证:映射表、跳转链与循环排查

改版动辄上千条 URL 需要跳转。本文给出重定向映射表的生成方法、Nginx map 批量配置、跳转链与循环的检测脚本,以及上线前的核对清单。

U
UFO_PF 2016-03-06 · 阅读 9 分钟
批量 GOOGLE SEO
阅读提示
本文约 3,521 字,预计阅读 9 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:改版后流量腰斩,问题出在跳转表

站点改版更换固定链接结构是最常见的流量事故场景。有人把 /2023/05/hello-world.html 换成 /blog/hello-world/,上线时只配了首页和几个栏目的跳转,剩下几千篇文章直接 404。等 GSC 报告出来,自然流量已经掉了六成,外链带来的权重全部漏光。

批量重定向的难点从来不是”写一条跳转”,而是三件事:如何完整拿到旧 URL 清单、如何建立可信的映射关系、如何在上线前验证没有链条和循环。这三步缺任何一步,上线就是赌运气。

原理分析:映射关系从哪来

旧 URL 清单有四个来源,优先级从高到低:

来源 覆盖度 说明
GSC”网页”报告已索引 URL 高 必做,直接给出被收录的地址
GSC”链接 → 外部链接”报告 中 找出有外链的页面,权重最关键
GA4 落地页历史数据 中 补充有流量但未被索引的页面
数据库历史数据 高 用于生成 slug 映射

映射关系的建立有两种思路:规则型(旧结构可通过正则推导出新结构)和枚举型(新旧 slug 无规律,必须一对一列举)。规则型优先,配置简洁、性能最好;枚举型用 map 实现,注意条目过多时要评估内存占用。

关于跳转链:A→B→C 的链条会让信号在每一跳衰减,同时增加 TTFB。理想状态是任何旧 URL 一次跳到最终地址。循环则是致命错误,浏览器直接报 ERR_TOO_MANY_REDIRECTS,页面彻底不可访问。

保姆级实操

步骤 1:导出旧 URL 与外链清单。 先从 GSC 导出”网页”报告全部 URL,再导出”链接”报告中的”链接最多的网页”,合并去重后作为必须跳转的集合。

步骤 2:从数据库生成 slug 映射。 若只是改了固定链接结构,文章 ID 与 slug 不变,可直接生成映射文本。

-- 按旧结构(/%year%/%monthnum%/%postname%.html)生成旧->新映射
SELECT CONCAT('https://example.com/', DATE_FORMAT(post_date, '%Y/%m'), '/', post_name, '.html') AS old_url,
       CONCAT('https://example.com/blog/', post_name, '/')                                     AS new_url
FROM   wp_posts
WHERE  post_type = 'post'
  AND  post_status = 'publish'
ORDER  BY post_date DESC;

导出为 TSV 后,直接转成服务器配置。

步骤 3:规则型跳转用 Nginx 正则。 结构可推导时,几条规则就能覆盖全部。

# 规则型:旧日期型链接 → 新 slug 链接
location ~ ^/(\d{4})/(\d{2})/([^/]+)\.html$ {
    return 301 https://example.com/blog/$3/;
}

步骤 4:枚举型跳转用 Nginx map。 无规律的一对一映射用 map 集中管理,注意把 map 放在 http 块中。

# 放在 http {} 块内
map $request_uri $redirect_to {
    default              "";
    /old-about           /about/;
    /products/item-12    /shop/item-12/;
    /news/2024/notice    /blog/2024-notice/;
}

# 放在 server {} 块内
if ($redirect_to != "") {
    return 301 https://example.com$redirect_to;
}

map 的键区分大小写,默认不区分查询参数;若旧 URL 带参数,需要先用 $uri 而非 $request_uri。

步骤 5:上线前批量验证。 写个脚本把映射表逐条跑一遍,检查状态码、跳转次数和最终地址是否符合预期。

#!/usr/bin/env bash
# verify_redirects.sh 用法: ./verify_redirects.sh map.tsv
# map.tsv 每行格式: 旧URL<TAB>期望目标URL
while IFS=$'\t' read -r old want; do
  got=$(curl -sIL -o /dev/null -w "%{http_code} %{url_effective} %{num_redirects}" "$old")
  code=$(echo "$got" | awk '{print $1}')
  final=$(echo "$got" | awk '{print $2}')
  hops=$(echo "$got" | awk '{print $3}')
  if [ "$final" != "$want" ] || [ "$hops" != "1" ]; then
    echo "异常: $old -> 状态码 $code, 跳 $hops 次, 最终 $final (期望 $want)"
  fi
done < "$1"
echo "验证完成"

脚本会同时暴露两类问题:跳转目标不对、跳转次数大于 1(存在链条)。

步骤 6:检测循环。 对可疑 URL 单独跑,看到 num_redirects 异常增大或 curl 报 “Maximum redirects followed”,就是循环,需回到配置逐条排查。

步骤 7:上线后监控。 在 GSC 的”网页”报告中观察 404 数量是否上升,用” Crawl Stats”报告看服务器响应码分布;同时保留映射表至少一年,因为外链指向的旧地址可能在很久之后才被访问。迁移完成后的索引情况,可配合 Google Search Console 使用教程 持续跟踪。

常见踩坑

  1. 案例: 某站在 WordPress 后台改了固定链接结构后,用 Redirection 插件自动记录了 404 并逐条补跳转,但规则写成”包含匹配”,导致 /blog/a 也匹配到 /blog/abc 的规则,两千多个 URL 跳到了错误页面。改成精确匹配并逐条验证后才修正。
  2. 用 rewrite ... permanent 时带上了 $args,把查询参数一起拼进新地址,产生带怪参数的重复 URL。需要保留参数时显式写 $is_args$args,不需要时在末尾加 ?。
  3. map 放在 server 块内不生效(必须在 http 块),上线后发现全部跳转失效。
  4. 只跳首页:把匹配不到的旧 URL 全部 301 到首页,Google 会判定为”不相关的重定向”,按 404 处理,权重照样丢失。无法映射的应老实返回 404。
  5. 跳转链:HTTP→HTTPS、www→非 www、旧路径→新路径三段分开配置,累积成三跳。应在一条规则里一步到位,可参考 301 302 307 重定向区别。
  6. 改完配置没有 nginx -t 校验语法,也没有 systemctl reload,配置根本没生效却以为已完成。
  7. 忽略大小写与结尾斜杠:/About 与 /about/ 是不同 URL,映射表要覆盖两种形态,否则产生成片 404。

总结

批量重定向的质量标准可以量化:映射覆盖率(有外链的旧 URL 100% 覆盖)、单跳率(≥ 99% 的旧 URL 一次跳到位)、循环数(0)、上线后 30 天内 GSC 新增 404 数(趋近于 0)。行动清单:先合并 GSC、GA4、数据库三个来源建立旧 URL 全集;能用正则就用正则,不能就上 map;上线前用脚本逐条验证状态码与跳转次数;保留映射表一年以上。改版不可怕,可怕的是没有映射表就上线。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。