同一个页面11种URL写法都返回200,做SEO查重复内容时一条都不会报
本文目录
- 一个页面到底有几个地址?先在实验台上数一遍
- 十一种写法,返回的字节数一个不差
- 被挡住的是哪些
- nginx为什么把这么多写法都认成同一个页面?
- 这三步都不是可选项
- 归一化跑在你的配置之前
- 大小写是唯一一条真分界线吗?
- 可现实里三成多的站不是这样
- 拿本站当对照
- 关掉merge_slashes是在收紧还是在放松?
- 真正变化的是别的东西
- 现网105个站,一个页面平均有几个能返回200的地址?
- 怎么测的
- 结果
- 最松的和最严的
- canonical到底收不收得住这些变体?
- 为什么它这么稳
- 那还有什么可担心的
- 哪些写法是你自己配出来的?
- 那条大小写不敏感的location
- 兜底路由
- 还有一个挡住了
- 该怎么把一个页面的地址收敛回一个?
- 能直接抄的收敛配置
- 三条命令自查
- 什么时候不值得治
- 常见问题解答
- 同一个页面有多个能返回200的地址,会被谷歌当成重复内容惩罚吗?
- 我加了canonical,是不是就不用管URL变体了?
- 路径里的大小写到底要不要统一成小写?
- 把merge_slashes关掉能不能挡住多余斜杠的地址?
- 为什么我在nginx里写规则去跳转带双斜杠的地址,规则从来不生效?
- 兜底路由(try_files最后指向应用)是不是必须改掉?
- 这些多出来的地址会不会稀释页面权重?
- 权威参考资料
摘要:在一台干净的nginx上放一个165字节的文件,然后换着花样去请求它——加一道斜杠、加一个点、把斜杠写成 %2F、在中间塞一段
zz/..。十一种写法全部返回200,返回的字节数一个不差,而nginx内部记录的路径永远是同一个。这不是配置失误,是HTTP服务器按规范必须做的归一化,它发生在你写的每一条location生效之前。真正被挡住的只有大小写。往外看105个真实站点,一个页面平均带着2.66个字节级完全相同的200地址,六成以上的站至少多出一个。好消息是canonical在这批变体上一次都没掉链子;坏消息是有17.1% 的页面压根没有canonical。
先说清楚这篇文章要数的是什么:不是“URL该怎么起名”,也不是“重复内容会不会被罚”,而是一个更基础、却几乎没人真去数过的问题——同一份内容,到底有多少种地址写法能让服务器痛痛快快回一个200?
这个数字很重要,因为搜索引擎眼里的“页面”就是URL。你觉得自己发布了一个页面,服务器可能同时承认了十一个。
一个页面到底有几个地址?先在实验台上数一遍
要数清楚,得排除干扰。生产站上跑着CDN、WAF、伪静态、缓存,任何一层都可能替你把地址改掉,结论就不干净了。所以我在服务器上另起了一个独立的nginx实例,不碰生产:自己的配置文件、自己的日志目录、端口18545,root指向一个只有几个文件的目录。
里面放一个真实存在的文件 /foo/bar.html,165字节。然后我给每个server加了一行:
add_header X-Nginx-Uri "$uri" always;
add_header X-Req-Uri "$request_uri" always;$request_uri 是客户端原样发过来的请求行,$uri 是nginx在内部实际用来找文件、匹配location的那个路径。把两个都打出来,就能看见nginx到底在哪一步把地址改了。
接下来用curl换着花样请求同一个文件。这里有个细节必须交代:curl默认会替你把 ./ 和 ../ 先化简掉,那样测的就是curl而不是nginx了。加上 --path-as-is 才能把原始字符串原样送出去。
十一种写法,返回的字节数一个不差
| 请求行原样 | 状态码 | 返回字节 | nginx内部 $uri |
|---|---|---|---|
/foo/bar.html | 200 | 165 | /foo/bar.html |
//foo/bar.html | 200 | 165 | /foo/bar.html |
/foo//bar.html | 200 | 165 | /foo/bar.html |
///foo///bar.html | 200 | 165 | /foo/bar.html |
/foo/./bar.html | 200 | 165 | /foo/bar.html |
/./foo/bar.html | 200 | 165 | /foo/bar.html |
/foo/xx/../bar.html | 200 | 165 | /foo/bar.html |
/foo/../foo/bar.html | 200 | 165 | /foo/bar.html |
/foo%2Fbar.html | 200 | 165 | /foo/bar.html |
/%66oo/bar.html | 200 | 165 | /foo/bar.html |
/foo/bar%2Ehtml | 200 | 165 | /foo/bar.html |
十一行,状态码全是200,字节数全是165,$uri 那一列从头到尾没变过。
最后一列是关键。nginx不是“分别处理了十一个地址并且碰巧都返回了同样的内容”,而是它压根就没看见这十一个地址——它只看见一个。那十一种写法的差别,在请求进入你的配置之前就已经被抹平了。
顺带一提,第九行的 %2F 是斜杠的百分号编码。有人专门用它来表示“这是一个字面上的斜杠,不是路径分隔符”,但在这里它被解码成了真斜杠,然后当成路径分隔符用掉了。关于百分号编码在URL各个位置的不同含义,可以看这篇URI编解码器怎么用?中文URL编码、UTM参数转码与GSC乱码网址还原。
被挡住的是哪些
同一台机器上,下面这些写法拿到的是404:
| 请求行原样 | 状态码 | nginx内部 $uri | 为什么 |
|---|---|---|---|
/FOO/BAR.HTML | 404 | /FOO/BAR.HTML | 路径大小写没有被归一化 |
/Foo/bar.html | 404 | /Foo/bar.html | 同上,一个字母也不行 |
/foo/bar.html. | 404 | /foo/bar.html. | 尾部的点不会被吃掉 |
/foo/bar.html;a=1 | 404 | /foo/bar.html;a=1 | 分号参数是路径的一部分 |
/foo/bar.html%20 | 404 | /foo/bar.html␣ | 解码出一个真空格,文件名不匹配 |
/foo/bar.html/ | 404 | /foo/bar.html/ | 文件不是目录 |
还有一个例外值得单独记一笔:/foo/bar.html?x=1 返回200。查询串不参与路径匹配,所以你可以在任何一个页面后面挂上任意查询串,服务器照样给你200,内容一字不差。这条对搜索引擎的意义完全不同于路径变体——它是分面导航和追踪参数把URL数量炸开的根源,展开讲是另一篇的量,分面导航的SEO怎么治理?筛选过滤产生的海量URL别拖垮抓取预算里拆得比较细。
nginx为什么把这么多写法都认成同一个页面?
这不是nginx的自作主张,官方文档在location那一节写得很直白:
The matching is performed against a normalized URI, after decoding the text encoded in the "%XX" form, resolving references to relative path components "." and "..", and possible compression of two or more adjacent slashes into a single slash.
一句话三个动作,顺序还是固定的:
- 先解码:把
%XX形式的字符还原成本来的样子。%66变成f,%2F变成/,%2E变成.。 - 再解析点段:把
.和..这两种相对路径引用算掉。 - 最后压缩斜杠:连续两个以上的斜杠合并成一个。
为什么是这个顺序,实验台上能看出来。/foo/bar%2Ehtml 里的 %2E 先被还原成点,然后才轮到点段解析——但这个点是文件名里的点,不构成 . 或 .. 这样的完整路径段,所以留了下来。而 /foo%2Fbar.html 里的 %2F 被还原成斜杠之后,它就是一个货真价实的路径分隔符了,再想让它变回“字面上的斜杠”已经没有机会。
这三步都不是可选项
点段那一步的出处在RFC 3986。规范用了一整节讲 remove_dot_segments 算法,并且在讲URI归一化的时候明确要求:
URI normalizers should remove dot-segments by applying the remove_dot_segments algorithm to the path.
换句话说,任何一个正经实现都得这么做。这一条不是“nginx的脾气”,是所有人的共同底线。
这句话不用只当规范来信,后面那轮普查里正好有现成的对照:Apache官方自己的站 httpd.apache.org(响应头 Server: Apache),在点段、父段、开头双斜杠、路径内双斜杠这四种写法上全部原地返回200,且字节与规范地址完全相同。换个服务器实现,答案一模一样。
归一化跑在你的配置之前
这才是最要命的一点,也是我认为最值得单独拎出来的一句:
你在nginx里写的每一条location、每一个if、每一段rewrite,看到的都已经是归一化之后的路径。原始写法在到达它们之前就没了。
所以那种“我加一条规则把带双斜杠的请求301到规范地址”的想法,默认配置下根本没法实现——等你的规则拿到手时,双斜杠已经不在了。这跟 Nginx配置每次reload都通过,Googlebot每8次抓取却有1次撞在301上里讲的那类“配置通过了但行为不是你想的那样”是同一类问题:nginx -t 只检查语法,不检查你对执行顺序的理解。
想拿到原始写法,只有 $request_uri 这一个口子。这也是为什么本文的实验台要把两个变量都打出来——只看 $uri,你会以为世界很干净。
大小写是唯一一条真分界线吗?
实验台上,大小写是唯一一个把请求挡在门外的因素。这背后也有规范依据,RFC 3986讲得很细:
the scheme and host are case-insensitive and therefore should be normalized to lowercase... The other generic syntax components are assumed to be case-sensitive unless specifically defined otherwise by the scheme.
协议名和主机名不区分大小写,路径、查询串、片段这些“其他部分”默认区分。所以 https://EXAMPLE.com/Page 和 https://example.com/Page 是同一个地址,而 /Page 和 /page 不是。
谷歌的表态和规范完全一致,而且直接点了名:
Like any other HTTP client following IETF STD 66, Google Search's URL handling is case sensitive (for example, Google treats both /APPLE and /apple as distinct URLs with their own content).
这句话里的IETF STD 66就是RFC 3986。谷歌明说自己按这份规范处理URL,把 /APPLE 和 /apple 当两个各有各内容的地址。
可现实里三成多的站不是这样
规范归规范,服务器给不给你面子是另一回事。我拿105个真实站点的深层页面做了大小写探测,把路径整个转成大写再请求一次,结果分成三档:
| 路径全大写之后 | 站数 | 占比 | 意味着什么 |
|---|---|---|---|
| 404 | 67 | 63.8% | 大小写敏感,行为符合规范 |
| 3xx跳回原地址后200 | 23 | 21.9% | 做了归一化,主动收敛,最省心 |
| 原地直接200 | 14 | 13.3% | 两个大小写各是一个可索引地址 |
| 其他(4xx非404) | 1 | 1.0% | 被防护层拦下 |
最后那13.3% 是要出事的那一档。服务器认了大写地址、原地返回200,而谷歌按规范把它当成另一个页面。一个页面于是有了两个可索引地址,内容一模一样。
这一档通常来自四个地方:跑在Windows/IIS上(NTFS默认不区分大小写)、跑在macOS开发机同款文件系统上、前面挂了会做小写归一化的CDN规则、或者应用层用了不区分大小写的路由匹配(下一节会专门拆这个)。
谷歌给的建议也很实在:
If upper and lower case text in a URL is treated the same by your web server, convert all text to the same case so it's easier for Google to determine that URLs reference the same page.
注意它的前提——“如果你的服务器把大小写当成同一回事”。它没让所有人都去做小写归一化,只让那13.3% 去做。剩下63.8% 本来就是404,做了反而多此一举。
拿本站当对照
顺手拿保哥自己这个站测了一遍同一批写法,结果和实验台的出厂默认差得挺远:
| 请求 | 状态码 | Location |
|---|---|---|
/glossary/ | 200 | — |
/glossary | 301 | /glossary/ |
/GLOSSARY/ | 301 | /glossary/ |
//glossary/ | 301 | /glossary/ |
/glossary// | 404 | — |
/./glossary/ | 404 | — |
/x/../glossary/ | 404 | — |
同样是nginx,出厂默认能进十一个门,这边只剩两个:规范地址本身,加上三种会被301送回规范地址的写法。差别全在配置上。
关掉merge_slashes是在收紧还是在放松?
知道斜杠会被合并之后,很多人的第一反应是把它关掉——既然多余斜杠是“脏”的,那就别让它进来。
我在实验台上开了第二个server,唯一的差别是加了一行 merge_slashes off;。测下来是这样:
| 请求行 | 默认(on)状态码 | 默认 $uri | off状态码 | off的 $uri |
|---|---|---|---|---|
//foo/bar.html | 200 | /foo/bar.html | 200 | //foo/bar.html |
/foo//bar.html | 200 | /foo/bar.html | 200 | /foo//bar.html |
/foo/./bar.html | 200 | /foo/bar.html | 200 | /foo/bar.html |
/foo/xx/../bar.html | 200 | /foo/bar.html | 200 | /foo/bar.html |
两个关键结论:
第一,关掉之后照样返回200。因为最后去磁盘上取文件的是操作系统,而Linux把路径里的连续斜杠视同一个。nginx不再帮你合并,内核帮你合并。你以为堵住了门,其实只是把门牌换了个写法。
第二,点段照样被算掉。merge_slashes 只管斜杠,管不了 . 和 ..——那两个是RFC 3986要求的动作,不归这个开关管。
真正变化的是别的东西
关掉之后唯一实质性的变化,是 $uri 里保留了原始的多余斜杠。这件事的后果,nginx文档自己讲了:
Note that compression is essential for the correct matching of prefix string and regular expression locations. Without it, the "//scripts/one.php" request would not match location /scripts/ { ... } and might be processed as a static file.
翻译成人话:你所有按前缀写的location都会被绕过。那些挂在 location /admin/ 上的访问控制、挂在 location /api/ 上的限流、挂在某个前缀上的缓存策略,请求方只要多打一道斜杠就全部躲开了,而文件本身还是能取到。
文档随后给了一句相当罕见的措辞:
However, for security considerations, it is better to avoid turning the compression off.
官方文档很少直接说“你最好别这么干”。这条值得当成硬规则记下来:merge_slashes off 不是在收紧URL,它在放松你的location匹配。唯一的合理用例文档也写了——路径里嵌了base64,而base64的字母表里恰好有斜杠。
顺手说一句,“改一个开关,结果和直觉相反”这种事在nginx里不算稀奇。Nginx反向代理实战指南里 proxy_pass 末尾那道斜杠加不加,能把整个后端路径改掉,也是同一类。
现网105个站,一个页面平均有几个能返回200的地址?
实验台说明了机制,但机制不等于现实。真实站点前面挂着CDN、边缘规则、框架路由,最终行为得实测。
怎么测的
先抓181个域名的首页,从HTML里挑出一个同域的深层链接——要求至少两段路径、不带查询串、不是静态资源,这样才有路径可以变形。能拿到深层地址的有112个站,其中原样请求就能返回200的105个,这105个是分母。
然后对每个深层地址做八种变形,加原样一共九次请求,总计1008次。判据卡得比较严,一个变体要算数,必须同时满足三条:
- 返回 200;
- 没有被重定向走(最终地址还是它自己,被301送回规范地址的不算);
- 响应体的 SHA-1与原样请求完全一致(字节级相同,不是“看起来差不多”)。
第三条最关键。很多站会对变体返回一个“长得像但不是同一个”的页面,那种情况另有说法,不该混进来。
结果
| 额外写法数 | 站数 | 占比 |
|---|---|---|
| 0种(只有规范地址能进) | 40 | 38.1% |
| 1种 | 15 | 14.3% |
| 2种 | 23 | 21.9% |
| 3种 | 13 | 12.4% |
| 4种 | 2 | 1.9% |
| 5种 | 6 | 5.7% |
| 6种 | 6 | 5.7% |
平均每站额外1.66种写法,也就是一个页面平均带着2.66个字节级完全相同的200地址。61.9% 的站至少多出一个。
按变体类型拆开看,哪种写法最容易蒙混过关:
| 变体 | 原地200且字节相同 | 占105站 |
|---|---|---|
| 尾斜杠切换(加上或去掉末尾的 /) | 60 | 57.1% |
| 加一个无意义查询串 | 48 | 45.7% |
开头双斜杠 //a/b | 23 | 21.9% |
点段 /a/./b | 17 | 16.2% |
路径内双斜杠 /a//b | 12 | 11.4% |
父段 /a/zz/../b | 12 | 11.4% |
| 路径全大写 | 2 | 1.9% |
| 追加一段不存在的路径 | 0 | 0.0% |
尾斜杠是头号来源,一多半的站两种写法都收。这个数字后面还会再出现一次——它在另一个维度上的杀伤力比在这里大得多。
最松的和最严的
额外写法数拉满(6种)的六个站:shopify.com、stripe.com、klaviyo.com、backlinko.com、nodejs.org、iana.org。
这份名单挺有意思。前三个是SaaS大厂,第四个是SEO圈子里人人都读过的博客,最后一个是管着全球IP地址和端口号分配的机构。连IANA都没把自己的URL收干净,这事说明它确实不是“谁不专业”的问题,而是默认行为就这样。
另一头,零额外写法的站包括 walmart.com、target.com、nike.com、hubspot.com、salesforce.com、square.com、wix.com。大型电商和企业级SaaS明显更紧——它们的URL通常由一层专门的边缘路由统一发牌,不是让web服务器直接对着文件系统开门。
按Server头分组也能看出这个规律,只是样本量小,只能当参考。测量来自单一出口,跨境访问这批站点,个别站的边缘节点行为可能和当地不同,绝对值别当成普适结论——这一点在限速规则拒掉的第4个请求是robots.txt,全站SEO抓取会停12小时那次两地对照里吃过教训,同一批站从不同网络位置测,状态码有四分之一对不上。
canonical到底收不收得住这些变体?
数完了地址,下一个问题自然是:这些多出来的地址,有没有人管?
标准答案是canonical。我把它当成一个可证伪的假设来测:对每一个“原地200且字节相同”的变体,把它页面上的canonical取出来,解析成绝对地址,看它指回规范页,还是跟着变体一起变了。
结果和我下场之前的预期完全相反:
| 项目 | 数量 | 占比 |
|---|---|---|
| 原样页面带canonical的站 | 87 / 105 | 82.9% |
| 变体上canonical指回规范页(收敛成功) | 130 | 100.0% |
| 变体上canonical跟着变(收敛失败) | 0 | 0.0% |
| 变体页干脆没有canonical | 0 | 0.0% |
130次判决,一次都没掉链子。我原本准备写的是“canonical在现网基本是坏的”,实测把这个预设按在地上摩擦了——凡是有canonical的站,它在这些路径变体上都老老实实指回了同一个规范地址。
为什么它这么稳
回头看第一节那张表就明白了:因为canonical通常是模板写死的绝对地址,或者由框架按路由名生成,而不是拿“当前请求的URL”拼出来的。路径变体在到达应用之前就已经被归一化成同一个 $uri,应用根本不知道自己是被 //a/b 还是 /a/./b 请求的,自然也就写不出跟着变的canonical。
这是个挺漂亮的意外收获:把请求写法抹平的那套归一化机制,同时也顺手保证了canonical不会被写歪。同一件事在这里既是病因又是解药。
那还有什么可担心的
两件事。
一是那17.1%。105个站里有18个原样页面就没有canonical。它们的路径变体没有任何收敛机制,全靠搜索引擎自己去判重。谷歌确实会自己选一个规范地址,那套决策逻辑在 Google选择Canonical URL的9大决策逻辑与排查实操指南里拆过,但“它会自己选”和“它会选你想要的那个”是两码事。
二是收敛成功不等于零成本。canonical是在页面被抓取之后才起作用的——爬虫得先把这个变体地址请求一遍、下载完整个页面、解析出canonical,才知道“哦这个不算数”。省下的是索引里的位置,花掉的是抓取。这笔账怎么算,做SEO算抓取预算,发现服务器把没改过的页面对爬虫重发了几百遍那篇讲得比较透。
所以canonical该有还得有,它是安全网;但它不是免死金牌,真要省抓取,还得靠301把变体挡在下载之前。
哪些写法是你自己配出来的?
前面数的都是“出厂默认送你的”。还有一类地址,是配置里那些看着人畜无害的行明确造出来的。这类通常比默认的严重得多,因为它们不受“路径必须真实存在”这个约束。
那条大小写不敏感的location
把某个路径写成不区分大小写,是个流传很广的写法,通常长这样:
location ~* ^/foo/bar {
try_files /foo/bar.html =404;
}看起来只是“让 /foo/bar 和 /Foo/Bar 都能访问”。实验台上打一遍:
| 请求 | 状态码 | $uri |
|---|---|---|
/foo/bar | 200 | /foo/bar.html |
/FOO/BAR | 200 | /foo/bar.html |
/Foo/Bar | 200 | /foo/bar.html |
/fOo/bAr | 200 | /foo/bar.html |
/foo/barXXXX | 200 | /foo/bar.html |
前四行是预期内的:六个字母,每个都能大小写互换,2的6次方等于64种写法全部返回200。
最后一行才是真问题。~* ^/foo/bar 只锚定了开头,没锚定结尾,所以任何以它开头的路径都命中——/foo/barXXXX、/foo/barbarbar、/foo/bar-anything-you-want,全部200,全部返回同一份内容。
64种大小写组合乘以无限种后缀,这个页面的地址数量正式变成了无穷大。而这一切的代价,是配置里少写了一个 $。
兜底路由
另一个常见来源是 try_files 的最后一档。第三个server用的是这样一条:
location / { try_files $uri $uri/ /app.html; }意思是“文件找不到就交给应用处理”,几乎所有前后端分离的站、所有单页应用、大部分CMS都是这个结构。实测:
| 请求 | 状态码 | 返回字节 |
|---|---|---|
/this-page-does-not-exist | 200 | 115 |
/a/b/c/d/e/f/g/h | 200 | 115 |
/docs/guide/anything | 200 | 115 |
/docs/guide/anything/deeper/still | 200 | 115 |
任意深度、任意内容的路径,全部200,全部同一份字节。这条配置本身就是一台无限URL生成器,前提是有人给它喂地址——而“有人给它喂地址”这件事,恰恰是下一篇要讲的。
好消息是现网这么干的不多。我在105个站上做了同样的探测(在真实路径后面追加一段随机的不存在路径),只有12个站还返回200(11.4%),其中字节级完全相同的只有4个(3.8%):walmart.com、engadget.com、php.net、mariadb.com。剩下8个返回的是内容不同的页面,多半是自定义的错误页或者软404。
剩下91个站规规矩矩返回404。这说明大部分框架的兜底路由后面还接了一层“这个路由到底存不存在”的判断,没有直接把200发出去。HTTP状态码怎么影响SEO?301、302、404和410该怎么选里讲过为什么404在这个位置是对的答案。
还有一个挡住了
顺手测了一条路径遍历:/wp-admin/../../etc/passwd。nginx返回400,连 $uri 都没有输出。
这说明归一化不是无脑做的:.. 只要试图跳出根目录,请求就被直接拒了。停留在根目录之内的冗余段照单全收,跨出边界的立刻叫停。这个分界线画得很清楚,也解释了为什么 /foo/xx/../bar.html 能进而它不能。
该怎么把一个页面的地址收敛回一个?
前面数出来的东西,落到手上就是一张决策表。按“能不能挡在下载之前”排序,越靠前收益越大。
| 写法 | 现网命中率 | 建议动作 | 说明 |
|---|---|---|---|
| 尾斜杠两种都收 | 57.1% | 301到一种 | 收益最大的一条,且下一篇会说明它的另一重代价 |
| 任意查询串都收 | 45.7% | canonical兜底 | 不能一刀切301,正常参数要留 |
| 路径大小写原地200 | 13.3% | 301到小写 | 只有这13.3% 需要做,其余本来就404 |
| 多余斜杠 / 点段 | 11%-22% | canonical足够 | 默认配置下拿不到原始写法,301做不了 |
location ~* 前缀正则 | 配置决定 | 加 $ 锚定结尾 | 不加等于开放无限后缀 |
| 兜底路由吃掉一切 | 3.8% | 路由不存在就返回404 | 在应用层判断,不在nginx层 |
能直接抄的收敛配置
尾斜杠和大小写这两条是301能解决的,写法不复杂:
# 统一去掉末尾斜杠(目录除外),二选一,别两条都写
location ~ ^(.+)/$ {
return 301 $1;
}
# 路径含大写字母就跳到小写版本
# 需要 nginx 带 perl 模块或在应用层做,纯 nginx 可用 map 列举常见入口
map $request_uri $has_upper {
"~[A-Z]" 1;
default 0;
}大小写归一化在纯nginx里比较别扭,因为它没有内置的小写函数。实际项目里更常见的做法是在应用层或者CDN边缘规则里做。.htaccess重定向生成器实测:导入一份旧配置会丢掉多少规则里对照过Apache侧的等价写法。
两条铁律:
- 只留一条规范形态,然后所有变体单跳到它。最忌讳的是A跳B、B又跳C,重定向链既费抓取又容易跳出环。
- 301上线前先确认站内链接已经用的是规范形态,否则每一次内部跳转都在给自己制造跳转。
三条命令自查
不用写脚本,手动敲三次就能知道自己站在哪一档:
# 1. 尾斜杠:两个都 200 就是有问题
curl -s -o /dev/null -w '%{http_code}\n' https://你的域名/某个页面
curl -s -o /dev/null -w '%{http_code}\n' https://你的域名/某个页面/
# 2. 大小写:200 就要治,404 或 301 都合格
curl -s -o /dev/null -w '%{http_code}\n' https://你的域名/某个页面 | tr 'a-z' 'A-Z'
# 3. 兜底路由:必须 404
curl -s -o /dev/null -w '%{http_code}\n' https://你的域名/某个页面/随便一段不存在的注意第一条和第三条要带 -o /dev/null 之外再看一眼字节数,因为“返回200”和“返回同一份200”是两件事。要严格一点就把 %{size_download} 也打出来,两次一样才算命中。
什么时候不值得治
说句实在话:如果你的canonical是模板写死的绝对地址,而且站内链接全部用规范形态,那多余斜杠和点段这两类基本可以不管。
理由是它们需要有人主动把那种畸形地址写出来才会被抓到。真实世界里,这种地址主要来自三个源头:手写外链时打错、某些聚合工具拼接出错、以及爬虫自己在解析相对链接时算出来的。前两个是小概率,第三个才是大头——而那正好是这个话题的另外一半,下一篇会拿真浏览器和两套URL标准对着测一遍。
保哥的经验是:先把尾斜杠和大小写这两条收干净,再确认每个页面都有canonical,剩下的按投入产出排到后面去。URL治理最容易犯的错是花三天写一套完美的重定向规则,去解决一个一年被抓两次的地址;而同一时间里,那个真正在漏抓取的分面参数还在那儿转。
常见问题解答
同一个页面有多个能返回200的地址,会被谷歌当成重复内容惩罚吗?
不会有惩罚这回事。谷歌的处理方式是从这批地址里挑一个当规范地址,其余的合并过去。真正的代价在抓取——每一个变体地址都要被完整下载一遍才能被判定为重复,这些请求全部计入你的抓取预算。所以问题不是“会不会被罚”,是“值不值得让爬虫为同一份内容下载好几遍”。
我加了canonical,是不是就不用管URL变体了?
基本可以,但不是全部。实测130次判决里canonical全部正确指回了规范地址,它比很多人以为的可靠。剩下的两个缺口是:一,你得先确认这个页面真的有canonical,现网有17.1% 的页面没有;二,canonical只能在页面被下载并解析之后生效,省的是索引不是抓取。命中率最高的尾斜杠变体值得用301挡在下载之前。
路径里的大小写到底要不要统一成小写?
看你的服务器怎么回答大写地址。如果返回404(现网63.8% 是这样),说明大小写本来就是敏感的,不存在两个地址同时可访问的问题,不用做。如果原地返回200(13.3%),那必须做,因为谷歌明确把 /APPLE 和 /apple 当两个各有内容的地址。测一条curl就能分辨自己在哪一档。
把merge_slashes关掉能不能挡住多余斜杠的地址?
不能,而且会帮倒忙。实测关掉之后 //foo/bar.html 照样返回200,因为最终去磁盘取文件的是操作系统,Linux本来就把连续斜杠当一个。唯一的实质变化是 $uri 保留了多余斜杠,导致所有按前缀写的location被绕过——访问控制、限流、缓存策略全部失效。nginx官方文档直接建议出于安全考虑不要关它。
为什么我在nginx里写规则去跳转带双斜杠的地址,规则从来不生效?
因为归一化跑在你的配置之前。nginx在做location匹配之前就已经完成了百分号解码、点段解析和斜杠压缩,等你的rewrite或if拿到路径时,双斜杠已经不存在了。要拿原始写法只能用 $request_uri 这个变量,它保留了客户端发过来的原样请求行。
兜底路由(try_files最后指向应用)是不是必须改掉?
不用改nginx那一层,前后端分离和单页应用都得这么配。要改的是应用层:路由匹配不上时返回404,而不是把首页或者某个默认页当200发出去。现网105个站里只有3.8% 在这一点上失守,说明主流框架默认是对的,属于要自查但通常没问题的一项。
这些多出来的地址会不会稀释页面权重?
不会按“稀释”那个模型来。只要它们被合并到同一个规范地址,外链信号也会跟着合并过去。真正会损失的是两种情况:一是页面没有canonical且谷歌选错了规范地址,二是你的站内链接本身就在用不同写法互相指,导致内部权重流向分散在几个地址上。第二种比第一种常见得多,也更容易自查。
权威参考资料
本文标题:《同一个页面11种URL写法都返回200,做SEO查重复内容时一条都不会报》
本文链接:https://zhangwenbao.com/url-path-normalization-case-slash-duplicate-seo.html
版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0