KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
07 · 站点迁移与重构:可回滚的映射工程 — keel 龙骨
本章目标:把站点迁移当成一次有清单、有映射、有验证、有回滚的数据一致性工程。 验收标准:301 映射覆盖 100% 旧 URL、抽样全通过,迁移后有流量曲线判读与回滚预案。
本章目标:把站点迁移当成一次有清单、有映射、有验证、有回滚的数据一致性工程。
验收标准:301 映射覆盖 100% 旧 URL、抽样全通过,迁移后有流量曲线判读与回滚预案。
为什么迁移是高危操作:迁移把"URL 是稳定的"这个前提一次性打破。旧 URL 的权重、外链、收录都要重新对齐。做错一次,可能损失数月积累。因此它必须像数据库迁移一样严肃:先生成映射、再验证、再切流、再观察、可回滚。
一、迁移的类型与风险
| 类型 | 例子 | 风险 |
|---|---|---|
| 域名更换 | A 域 → B 域 | 高,需全量重定向与 GSC 改址 |
| 协议变更 | http → https | 高,混合内容与 canonical |
| 结构重构 | /p?id= → /courses/x/ |
高,需 1:1 映射 |
| 平台迁移 | 换 CMS/框架 | 中高,URL 易被打乱 |
| 目录调整 | /old/ → /new/ |
中,局部重定向 |
唯一安全的做法:旧 URL 全集 → 新 URL 全集的 1:1 映射,一条都不能漏。
二、迁移检查清单
迁移前:
- 导出旧站全部可索引 URL(来自 sitemap + 日志 + 内部链接)
- 确定新站的 URL 规则(大小写、尾斜杠、参数)
- 生成 301 映射表,覆盖率 100%
- 备份旧站(数据 + 配置),确认可回滚
- 保留旧域名所有权,至少一年
迁移中:
- 301 用单跳,不产生重定向链
- 新站 canonical 自引用且与 sitemap 一致
- 更新所有内链指向新 URL
- 更新 sitemap,提交到 GSC/百度/Bing
- 保留 robots.txt 可访问
迁移后:
- 抽样验证 301(含带参数、带尾斜杠的变体)
- 监控抓取量、收录量、排名、流量
- 清理旧站上残留的被索引页面(noindex 或继续 301)
- 观察周期至少 4–8 周
三、301 映射的生成
# 1) 从旧 sitemap 与日志汇总旧 URL 全集
curl -s https://old.xxxxxx.cn/sitemap.xml | grep -oE '<loc>[^<]+' | sed 's/<loc>//' | sort -u > /tmp/old.txt
grep -iE 'googlebot|bingbot' /var/log/nginx/old.access.log | awk '{print $7}' \
| sed 's#^#https://old.xxxxxx.cn#' | sort -u >> /tmp/old.txt
sort -u /tmp/old.txt -o /tmp/old.txt
wc -l /tmp/old.txt
# 2) 生成映射表(此处用规则推导,实际项目可用脚本按规则批量生成)
awk '{print $0"\thttps://www.xxxxxx.cn"$0}' /tmp/old.txt > /tmp/redirect.map
生成的 nginx 配置:
# /etc/nginx/conf.d/redirects.conf
map $request_uri $new_uri {
default /;
~^/p\?id=87$ /learning/courses/tools/lessons/1/;
~^/old/courses/(.*)$ /learning/courses/$1;
~^/learning/courses/tools/lessons/1$ /learning/courses/tools/lessons/1/;
}
server {
server_name old.xxxxxx.cn;
return 301 https://www.xxxxxx.cn$new_uri;
}
规则:
- 单跳直达:
old → new,不要old → 中间 → new。 - 保留参数语义:有搜索价值的参数要么保留要么归并,不要一律丢。
- 顺序无关:nginx
map用正则匹配,注意优先级。
四、映射的验证
验证必须量化,不能靠人工点几个:
# 抽样 100 条旧 URL,检查是否 301 到预期的新 URL
fail=0; total=0
while read -r old; do
total=$((total+1))
code=$(curl -s -o /dev/null -w '%{http_code}' "$old")
loc=$(curl -s -o /dev/null -w '%{redirect_url}' "$old")
if [ "$code" != "301" ]; then
echo "❌ $old → $code"; fail=$((fail+1))
fi
done < <(shuf -n 100 /tmp/old.txt)
echo "抽样 $total 条,失败 $fail 条"
覆盖率验证(最重要):
# 旧 URL 总数 vs 已配置 301 的数量
echo "旧 URL 总数: $(wc -l < /tmp/old.txt)"
echo "已映射规则数: $(grep -c '~^' /etc/nginx/conf.d/redirects.conf)"
# 两者应大致相等;差距大说明有遗漏
| 验证项 | 通过标准 |
|---|---|
| 覆盖率 | 100%(无遗漏旧 URL) |
| 状态码 | 全部 301(不是 302/200) |
| 目标 URL | 全部是 200 的终址 |
| 跳数 | 全部单跳 |
| 参数变体 | 带参/尾斜杠变体也 301 |
五、迁移后的流量曲线判读
典型迁移曲线与判读:
| 阶段 | 现象 | 是否正常 |
|---|---|---|
| D0–D3 | 流量下滑、抓取激增(重抓新址) | 正常 |
| D3–D14 | 逐步回升、新 URL 开始收录 | 正常 |
| D14–D42 | 接近迁移前水平 | 正常 |
| 长期不回升 | 收录不回、排名丢失 | 异常,排查 |
| 断崖式长期下跌 | 多日无回升 | 异常,考虑回滚 |
异常排查顺序:
# 1) 新址是否被大量抓取
sqlite3 /tmp/seo.db "SELECT substr(ts,1,10), count(*) FROM crawl
WHERE uri LIKE '/learning/%' GROUP BY 1 ORDER BY 1 DESC LIMIT 7;"
# 2) 301 是否大量残留(说明内链/sitemap 还在指旧址)
grep -i 'googlebot' /var/log/nginx/learning.access.log | awk '$9==301 {print $7}' | sort | uniq -c | sort -rn | head
# 3) 新址状态码是否健康
grep -i 'googlebot' /var/log/nginx/learning.access.log | awk '{print $9}' | sort | uniq -c | sort -rn
六、回滚决策
什么情况回滚:迁移后超过既定观察期(如 6 周)且核心词排名与流量仍未恢复,且已排除技术故障。回滚本身也是一次迁移,需要同样严谨。
| 回滚触发条件 | 动作 |
|---|---|
| 6 周无回升 + 技术无故障 | 评估回滚 |
| 映射遗漏 >5% 且无法快速补 | 回滚修映射 |
| 新架构无法在合理时间内修好 | 回滚 |
| 短期波动(<2 周) | 不回滚,继续观察 |
回滚的纪律:保留旧站可用状态,回滚 = 恢复旧站 + 撤销 301 + 提交旧 sitemap。不要在原状不明时仓促回滚,先确认不是可修的技术问题。
七、故障速查
| 现象 | 原因 | 处理 |
|---|---|---|
| 大量 404 出现在新址 | 映射遗漏 | 补 301 |
| 权重没传过来 | 用了 302 或跳数过多 | 改 301 单跳 |
| 排名不恢复 | 内链仍指旧址 | 全站更新内链 |
| 收录数骤降 | sitemap 未提交/错误 | 重新提交新 sitemap |
| 部分页流量正常部分归零 | 局部映射缺失 | 定位缺失目录 |
八、本章验收
- 旧 URL 全集已导出,映射覆盖率 100%
- 全部 301 单跳,目标为 200 终址
- 抽样 100 条验证通过
- 内链与 sitemap 全部指向新址
- 有 4–8 周监控计划与回滚触发条件
- 旧域名保留、旧站可恢复
# 本章核心两连
wc -l < /tmp/old.txt
grep -i 'googlebot' /var/log/nginx/learning.access.log | awk '$9==301' | wc -l
迁移解决"结构怎么变"。下一章回到内容本身:如何让内容成体系、建立主题权威。