先给结论:如果同一份内容在服务器上只存在一个小写路径,而历史链接、CMS 或站点地图里混入了大写路径,永久重定向不应逐个大小写变体去补,而应先把请求路径规范化为小写,再做一次统一的永久重定向映射。前提是服务器文件系统区分大小写(Linux 的 ext4、NFS 等通常如此);如果运行在默认不区分大小写的文件系统上,同一 URL 的大写形式可能直接返回 200,此时要做的是先统一对外路径,而不是叠加跳转。
拿你手上一个具体页面来验证。假设真实文件是 /products/blue-widget.html,而外链或旧页面写成了 /Products/Blue-Widget.html。在区分大小写的服务器上,后者会 404;在不区分大小写的服务器或某些 CDN 回源配置下,它可能直接返回 200。这两种结果的后续动作完全不同:
判断依据不是“有没有跳转”,而是同一资源是否被两个以上 URL 同时成功响应。可以分别请求全小写、首字母大写、全大写三种形式,记录状态码和最终 URL。只要有两个变体都返回 200,就说明映射尚未统一。
统一映射的常见做法是分两层:第一层把请求路径转成小写,第二层用一份映射表指向真实文件。这样做的理由是,大小写变体在数量上可能远多于真实文件数,逐条写死容易漏。
以 Nginx 为例,假设所有真实文件都是小写路径,可以先把请求 URI 转小写,再尝试匹配文件,匹配不到才进入永久重定向或 404 处理:
if ($request_uri ~ [A-Z]) { rewrite ^(.*)$ /${lowercase:$1} permanent; }
这段配置的假设是:服务器上不存在仅靠大小写区分、且内容不同的两个文件。如果确实存在 /Docs/readme 和 /docs/readme 两份不同内容,就不能全局转小写,必须改为显式映射表,把每个大写路径单独指向对应目标。
动作与结果的关系在这里很直接:先做一次全局小写规范化,再抓取日志或爬虫报告观察剩余 404。如果剩余 404 明显减少,说明绝大多数差异来自大小写;如果几乎没变,问题可能出在扩展名、目录层级或参数,而不是大小写。
映射上线后,不能只看首页或几个样本。以下信号组合起来,才能判断统一映射是否真的生效:
需要说明的是,抓取量下降或某个路径的请求归零,不能单独证明映射正确。它也可能来自爬虫降低频率、站点整体抓取预算变化,或该路径本来就没有外链。更可靠的证据是同一变体在映射前后状态码的变化,而不是请求数量的增减。
假设你有一份约 300 条的历史 URL 清单,其中约 40 条含大写字母,真实文件全部为小写。可以这样推进:
/OldDir/Page.html 对应 /new/page.html,单独写入显式映射表。这个例子的数字只用于说明分类比例,不代表任何真实站点。它的价值在于:先分类,再决定哪些交给统一规则、哪些必须手工映射,避免把 300 条全写成逐条跳转。
统一映射成立的前提有三条:真实文件路径本身是全小写且稳定的;服务器或应用层能够读取原始请求路径;你有权修改重定向规则或映射表。缺任何一条,都应先解决前提,而不是继续加跳转。
不适用的情况也要明确:如果业务依赖大小写区分不同资源(例如某些代码仓库、区分租户的路径),全局转小写会直接破坏访问。此时只能对已知的历史变体做显式永久重定向,并接受映射表需要持续维护。另外,robots.txt 的抓取限制不等于索引移除,映射上线后旧的大写 URL 仍可能在一段时间内出现在结果中,这属于正常过渡,不能靠屏蔽抓取来替代永久重定向。
最后一步始终是回到你手上的那份路径清单:把仍返回 200 的大写变体和仍返回 404 的路径分别记录,前者补映射,后者查真实文件是否存在。只有这两类都清零,统一映射才算完成。