前阵子给一个二手域名做技术顾问,站长问的第一个问题是「这域名有四年历史,为什么收录还是一塌糊涂」。看了一眼他的 Search Console,指数很干净:垃圾外链只有十几条,人工处罚没有,内容也是自己写的。问题出在历史 URL 的遗留形态——这个站四年前是另一套程序,文章地址全是 /p/123.html 和 /?p=123 两种形式并存,现在又加上了新的 /123.html。同一篇文章在搜索引擎眼里有三个地址,权重被切碎,而且新旧地址互相之间什么也没声明。
这类问题的共同点是:它不会报错,不会掉收录到零,只会让整站排名稳定地低一截。所以极少有人主动去查。本文讲的是这件事的工程做法——不是「怎么让排名变高」的技巧,而是「怎么确认搜索引擎已经把所有信号归到同一个地址上」的核对流程。
一、先弄清搜索引擎合并信号时认哪几条
把同一篇内容挂在多个 URL 上,搜索引擎需要判断「这些是同一个东西」。它依据的东西按可靠程度排序大致是:
第一,永久重定向。301 是最强的信号,它同时告诉搜索引擎「旧地址已经不存在,所有信号转给新地址」。302/307 是临时的,长期挂着 302 会让引擎保留旧地址的索引,而把流量导向新地址——结果是路由能通,但索引和排名都不转移,这是最坏的一种组合。
第二,canonical 标签。它是「声明」而不是「动作」,引擎会参考但会自己判断。如果 canonical 指向的页面本身是 noindex,或者 canonical 链断成 A→B→C,引擎可能直接忽略整条链,把每个地址各自索引。
第三,站内链接的结构。如果站内导航、相关推荐、分页里还在链向旧地址,那等于在告诉引擎「旧地址我还认」,这和 301 是矛盾的。
第四,URL 参数的处理方式。追踪参数、排序参数、分页参数如果每个都能生成可索引页面,会成倍放大重复。
这四条必须一致。实践中出问题最多的不是某一条写错了,而是四条互相矛盾——最典型的是「301 配了,但 sitemap 里还列着旧地址」,或者「canonical 指向新地址,而旧地址的 301 已经删掉了」。
二、第一步永远是清点,不是修改
在动任何配置之前,把站内所有「可能存在的地址形态」列出来。来源有四个:
一是访问日志里过去 90 天出现过的所有路径。awk 一行就能出结果(用落盘再处理的方式,别在管道里塞复杂正则):
zcat -f /var/log/nginx/access.log* \
| awk '{print $7}' | cut -d'?' -f1 \
| sort | uniq -c | sort -rn | head -50看三类东西:同一个 cid 出现多种路径形态、index.php 混进路径、末尾斜杠不一致(/tech 与 /tech/)。
二是 Google Search Console 的「网页」报告。看「未编入索引」里的分项:「已提交的网址标记为 noindex」「备用网页(有适当的规范标记)」「重复网页,Google 选择的规范网页与用户指定的不同」。最后这一条特别重要——它代表 Google 不认你的 canonical,自己另选了一个。
三是 sitemap 里的地址数量与实际文章数对比。类型上不应该出现两种形态,数量上不应该超过文章总数。差异超过 10% 就说明有重定向地址或参数页被收录进去了。
四是拿 site: 查一遍,看搜索结果里出现的 URL 形态。如果同时看到 /p/123.html 和 /123.html,说明旧地址还留在索引里,重定向没生效或者生效时间不够长。
三、格式型重复的优先级最高
「格式型」指的是同一篇文章,因为系统实现的原因有多个可访问地址:/?p=123、/index.php/123.html、/123.html、/123.html/、/123.html?from=rss。
这一层必须在 Nginx 层面收口,因为它的数量跟文章数一样多,没法用页面级 canonical 一个个写。Typecho 的伪静态配置里,常见的形态是 try_files 加一条 rewrite,容易漏掉「带 index.php 前缀的地址」。正确的做法是:
# 带 index.php 前缀的地址一律 301 到干净地址
location ~ ^/index\.php/(.*)$ {
return 301 /$1;
}
# 参数型访问跳转到静态地址(仅对文章详情生效)
if ($arg_p != "") {
return 301 /$arg_p.html;
}两个坑。if 在 location 里很危险,上面这段要放在 server 级且只处理 arg_p 这一种情况,不要在里面再套 rewrite 或者 proxy_pass。另一个坑是带追踪参数的地址不要 301,?utm_source=xxx 这类应该保留 URL 但加 canonical 或直接由 CDN 在缓存键里剔掉;如果 301 到无参地址,会把分享链接里的参数全部丢掉,反而影响渠道统计。
末尾斜杠的不一致要用统一规则处理,不要零散地写。文章页不带斜杠、列表页带斜杠是一种约定,选定后站内所有链接都要遵守,Nginx 里用两条规则分别归一化到约定形态。
四、canonical 与 301 的分工不能搞混
两者解决的问题不同,混用会出事故。
301 用于「这个地址不存在了,请去新地址」。它要求旧地址真的可以被放弃,比如程序升级、URL 规则改变。canonical 用于「两个地址都能访问,但我告诉你哪个是主版本」,比如分页列表的筛选参数、文章被归到两个分类下都能访问。
最常犯的错误是:给一个已经有 301 的页面同时写 canonical。这时候旧地址的响应是 301,搜索引擎根本读不到旧地址页面里的 canonical 标签,写了也没用,还容易在验证时误判「canonical 没生效」。
第二个错误:canonical 写成相对路径。虽然规范允许,但不同模板、不同层级下解析结果会不同,出过「文章页的 canonical 拼成 /」的事故,全站指向首页,那等于自宫。一律写完整绝对地址,并且带上协议和 www 前缀,与实际的访问域名完全一致。验证:
for u in /123.html /tech/ /page/2/; do
echo "== $u"
curl -s "https://www.example.com$u" | grep -oE ']+canonical[^>]*>'
done逐个看输出,确认每条 canonical 都是「当前页面的绝对地址」而不是首页、不是带参数的版本。
第三个错误:canonical 指向的页面返回了非 200。比如某个分类页被设成 noindex 了,文章页的 canonical 却指向它。这种情况在 GSC 里表现为「重复网页,Google 选择的规范网页与用户指定的不同」,看到这个分项有量,就先去测那一批 canonical 目标的 HTTP 状态码。
五、分页是最容易被忽略的重复源
列表页的分页有一个长期争论:第 2 页要不要被索引。我的建议很直接——第 2 页以后保留可索引,但 canonical 指向自身,不要指向第 1 页。
指向第 1 页的写法(常见的错误做法)会让第 2 页上的文章彻底失去被发现的机会,因为这些文章可能只出现在第 2 页。正确的信号是:每页 canonical 指向自己,同时用 rel="prev" / rel="next" 表达序列关系(虽然 Google 已不把这两个当索引信号,但其他引擎和工具还认)。
分页的第二个问题是「带参数的排序」。?order=hot、?sort=views 这些应该统一 canonical 到无参版本,或者在 robots.txt 层收口。注意 robots.txt 的 Disallow 只是禁止抓取,不禁止索引——如果这些 URL 有外链,仍然会出现在搜索结果里,只是没有摘要。想让它彻底消失,必须用 noindex;但 noindex 又要求页面能被抓到,所以不能同时 Disallow。这两条同时写会形成死锁:引擎抓不到页面就读不到 noindex,最终仍然按外链信息索引。
六、站点地图只放「应该被索引」的地址
sitemap 不是「越多越好」,它是「我推荐你优先抓这些」的名单。里面出现 301、404、noindex 的地址,会浪费抓取预算,同时让引擎对这个 sitemap 的信任下降。
核对方法很机械但有效——把 sitemap 里的所有地址批量打一遍状态码:
curl -s https://www.example.com/sitemap.xml \
| grep -oE 'https://www[.]example[.]com/[^<]+' \
| while read -r u; do
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 8 "$u")
[ "$code" = "200" ] || echo "$code $u"
done只打印非 200 的,输出为空才算干净。这条命令建议每月跑一次,因为新加的页面、删掉的文章、改过的 slug 都会悄悄让 sitemap 变得不准确。
另外,sitemap 里的 lastmod 必须真实。很多程序在保存时会刷新所有文章的修改时间,导致每次访问 sitemap 都显示「全站刚更新」——引擎会逐渐忽略这个字段,你真正的更新就失去了优先级。检查方式:连续两天抓 sitemap,对比 lastmod 是否有大量无意义的变化。
七、改完之后怎么确认真的合并了
不能只看「页面上有 301」,要看引擎侧的反映。三步:
第一步,用 GSC 的网址检查工具对旧地址做「测试实际网址」,确认抓取结果是 301 且目标正确。这一步确认的是「配置生效」,不是「引擎已合并」。
第二步,看「网页」报告里那个 URL 归到了哪个分项。旧地址应该出现在「网页已通过其他方式重定向」或者「备用网页(有适当的规范标记)」里,而不是「已编入索引」。如果一个月后还在「已编入索引」,检查是不是站内还有链接指向旧地址。
第三步,看新地址的展示量是否吸收了旧地址的。这个要用「效果」报告里按页面筛,对比改之前和改之后六周的数据。判断标准不是新地址涨了多少,而是「新旧地址合计」是否没有下降——合并过程中短期下滑是正常的,但总量不该掉。
八、三个不要做
不要为了合并信号而把旧地址删成 404。404 不传递信号,只告诉引擎「这里没了」。除非旧地址本来就是垃圾内容、你不想保留它的任何权重,否则一律 301。
不要频繁改 URL 规则。每改一次,引擎就要重新学习一遍,而且每次都会留下一批需要长期维护的重定向。URL 结构应该在建站时就定好,之后宁可在旧规则上打补丁,也不要推倒重来。
不要指望 301 立刻生效。收录的合并周期通常是几周到几个月,取决于抓取频次。抓取频次低的站,等两个月很正常。这段时间里唯一该做的是保持配置不变,别今天 301 明天改回来。
九、收尾清单
一个完整的「信号统一」核对,包含五件事:一,清点出站内所有地址形态,弄清哪种是主形态;二,在 Nginx 层把格式型变体(index.php 前缀、?p= 参数、尾斜杠)统一收口;三,确认每条 canonical 都是绝对地址、指向自身、目标返回 200;四,sitemap 只放 200 的真实页面,lastmod 真实;五,站内所有内部链接指向主形态,不留旧地址。
这套东西做完不会让你的站突然排到第一,但它会把「本来该属于这个页面的权重」全部收拢回来。对历史悠久的站来说,这一步的收益往往比写十篇新文章更大,而且它是一次性投入——之后只要保持规则不变,就是持续生效的。