筛选参数页收录了三万份重复:电商 URL 的 canonical 治理与参数收敛实战
适用读者:负责电商站或带筛选列表站点技术 SEO 的后端工程师、运维,以及盯 Search Console 数据的技术负责人。
上线改版三个月后,运营在周会上报了个喜讯:百度收录从 9000 涨到了 4 万,Search Console 里 Google 的已编入索引页面也接近 4.2 万。听起来像是免费流量要起飞了。我把报表拉出来对了一下类目数——全站商品 SKU 只有 2.6 万,列表页模板 300 多个,理论上有索引价值的页面撑死 3 万出头。也就是说,多出来的那部分全是水。site:example.com 随手翻了几页,同一款连衣裙的页面出现了七次,URL 分别带着不同的 sort_by、price_min、color 组合。这篇文章复盘的就是这 3.1 万份重复收录是怎么来的、怎么在 60 天里收敛掉的,以及 canonical、robots、noindex 三种手段各自的适用边界。
重复页是怎么被塞进索引的
先把问题定位说清楚,这比直接上方案重要。

排查用了三条路,互相印证:
- Search Console 的「索引 > 页面」报告,按「重复网页,Google 已选择不同规范网址」和「已抓取 - 尚未编入索引」两个状态分组导出,两个桶加起来 2.9 万条 URL;
site:example.com/category/dress配合inurl:sort_by逐页翻,肉眼确认排序参数页确实在索引里,而且摘要内容几乎一样;- 百度搜索资源平台的普通收录与索引量工具,曲线显示索引量在促销周之后有一次台阶式上涨,时间点对得上价格区间筛选活动上线的日子。
结论很典型:类目列表页挂在 /category/dress 下,前端每勾一个筛选项就往 URL 上追加一个查询参数。颜色 12 种 × 尺码 6 种 × 排序 5 种 × 价格区间 8 档,理论组合超过 28000 个。这些页面服务端渲染出来的标题、H1、正文商品列表高度雷同,搜索引擎的去重算法虽然能识别一部分,但识别是有成本的,于是三个后果同时发生:
- 抓取预算被稀释:Googlebot 每天在站上抓 1.8 万次,其中 70% 花在了参数组合页上,真正的新品页和促销页反而经常隔一周才被抓到;
- 索引膨胀:重复页挤进索引后,百度对站点整体质量的评估被拉低,日志里能看到大量
200 OK但内容近乎一致的响应; - 排名权重分摊:本该集中到
/category/dress的外链信号和内链权重,被几十个参数变体摊薄,核心词「连衣裙」的排名从第 9 位掉到了第 14 位。
flowchart TD
A[筛选参数组合生成 2.8 万个 URL] --> B[Googlebot / Baiduspider 抓取]
B --> C[抓取预算被参数页占掉 70%]
C --> D[索引里堆满重复页面]
D --> E[去重后剩余页面权重被分摊]
E --> F[核心词排名下滑]
B --> G[新品页与促销页抓取频率下降]
G --> H[新页面收录变慢]
canonical 合并信号的机制剖析
动手改之前必须想明白 canonical 到底在引擎那边做了什么,否则很容易把参数设置成「自以为有用」的形式。
规范网址(canonical URL)的机制可以拆成三层来理解:
第一层是抓取决策。重复网址合并文档(Consolidate duplicate URLs)里写得很明确:canonical 是一个信号而不是指令,Google 会综合 canonical、内部链接指向、sitemap、重定向等多路信号投票选出规范页。当我们把所有参数变体的 <link rel="canonical"> 都指向无参版本时,抓取器会逐步降低对变体页的抓取优先级——因为它已经知道这些页面最终会被合并,不值得反复抓。这一层直接回收抓取预算。
第二层是索引决策。参数页被抓到之后,引擎会对比它与 canonical 目标的内容相似度,判定为重复组(duplicate cluster),把整个簇折叠成索引里的一个条目,用规范页代表。索引从「每个 URL 一条」变成「每个簇一条」,重复收录的问题在这一层消失。
第三层是排名聚合。这是最容易被忽略的:折叠成簇之后,指向任何变体 URL 的外链、内链锚文本,其权重会归并到规范页上参与排名计算。也就是说 canonical 不只是「删掉重复」,它还把散落的信号收拢。这就是为什么治理完成后核心词排名会有一个滞后但明确的回升——信号聚合需要引擎重新计算,通常比索引清理晚两到四周。
再看三种手段的边界,这里踩过坑,值得展开:
| 手段 | 作用层级 | 参数页适用场景 | 误用后果 |
|---|---|---|---|
| canonical | 信号合并 | 变体页有内容价值但需归并到规范页,如排序、展示密度变体 | 指向错误目标导致整簇页面掉出索引 |
| robots Disallow | 阻止抓取 | 参数无 SEO 价值且不想耗费抓取预算,如 session id | 阻止了抓取就无法看到 canonical,页面上累积的外链仍可能被引擎当作独立 URL 记录 |
| noindex | 阻止索引 | 页面必须可访问但绝不该进索引,如对比工具页 | 与 canonical 同时出现会互相矛盾;长期 noindex 会导致引擎逐渐不再抓取该路径 |
关键点:Disallow 之后的页面,搜索引擎抓不到,也就读不到页面上的 canonical 和 noindex。所以顺序不能乱——先用 canonical 让引擎「看见并合并」,等索引稳定之后再对纯垃圾参数上 Disallow 收紧抓取预算。反过来做,等于把还没合并的 URL 关在门外,引擎只能凭历史数据猜它们的规范页,猜错就继续留在索引里。至于 noindex,在参数页场景里基本只适合极少数「必须能打开但不该收录」的工具型页面,大面积用 noindex 处理参数页会浪费本可用于信号合并的机会。
参数处置矩阵与收敛规则
策略落地的第一步不是写代码,是把全站 47 个查询参数逐个过一遍,给出明确处置决定:
| 参数名 | 示例 | 是否保留索引 | canonical 指向 | 处置手段 |
|---|---|---|---|---|
| sort_by | price_asc | 否(内容与规范页一致) | 无参规范页 | canonical + 一期后 Disallow |
| color | red | 否(商品集是规范页子集) | 无参规范页 | canonical |
| size | xl | 否 | 无参规范页 | canonical |
| price_min / price_max | 200-500 | 否 | 无参规范页 | canonical |
| page | 3 | 是(分页内容不同) | 自身(rel 分页链) | rel=prev/next 链接 + 自指 canonical |
| gclid / utm_* | 追踪参数 | 否 | 无参规范页 | canonical + Disallow |
| session_id | 8f3a… | 否 | 无参规范页 | 服务端剥离 + Disallow |
| view_mode | grid/list | 否 | 无参规范页 | canonical |
两条补充规则:分页页的 canonical 指向自身而不是第一页——这是当年 Google 已弃用 rel=prev/next 支持后官方文档给出的建议,分页内容本身不重复;任何规范页必须在 200 状态下返回真实内容,禁止 canonical 链接到 404 或重定向,否则信号会被引擎丢弃。
Nginx 按参数白名单动态输出 canonical
实现层选了 Nginx + Lua 的方案,在边缘层统一处理,业务代码零改动。依赖与环境:OpenResty 1.21(含 lua-nginx-module),Nginx 版本 1.21.4,部署在商品列表集群的接入层。
# 依赖:OpenResty(需 lua-nginx-module),挂在 server 块内生效
# 环境:Nginx 1.21.4 + lua-resty-string,商品列表集群接入层
# 参数白名单:只有 page 参与分页且每个分页是独立页面,其余参数
# 全部视为筛选变体,统一把 canonical 收敛到无参规范页
local seo_whitelist = { page = true } -- page 保留,分页自指
server {
listen 443 ssl;
server_name example.com;
location ~ ^/category/ {
# 拼装规范 URL:去掉白名单以外的所有查询参数
local clean_args = {}
local args, err = ngx.req.get_uri_args()
if not err then
for k, _ in pairs(args) do
-- 只有白名单参数才写回规范 URL,utm 追踪参数直接丢弃
if seo_whitelist[k] then
table.insert(clean_args, k .. "=" .. tostring(args[k]))
end
end
end
-- 无白名单参数时 canonical 就是干净路径,有则按序拼回
local canonical = ngx.var.uri
if #clean_args > 0 then
canonical = canonical .. "?" .. table.concat(clean_args, "&")
end
-- 分页页 canonical 自指(内容独立),筛选页 canonical 指向无参版本
-- 两者实现是统一的:上面拼出来的 clean URL 就是各自的规范目标
ngx.header["Link"] = '<' .. canonical .. '>; rel="canonical"'
-- 顺手把分页关系也输出,帮助引擎理解翻页结构
ngx.header["X-Canonical"] = canonical
proxy_pass http://list_upstream;
}
}
robots.txt 的一期策略刻意保守:先只封禁 session_id 和广告追踪参数这类「确定无争议」的条目,等 Search Console 里「重复网页」条目降下来之后再收紧第二轮。注释里写明了每条的时间计划:
# robots.txt — 参数封禁分两期上线,二期依赖一期 canonical 合并完成
# 上线时间:一期 T+0,二期 T+30(以 Search Console 重复页报告回落为准)
User-agent: *
# 一期:会话与追踪参数,任何引擎都不该抓
Disallow: /*?session_id=
Disallow: /*&session_id=
Disallow: /*?gclid=
Disallow: /*&gclid=
# 二期:排序参数页已通过 canonical 合并,此时封禁回收抓取预算
# Disallow: /*?sort_by=
# Disallow: /*&sort_by=
# 百度对通配符支持与 Google 一致,无需单独分组
Sitemap: https://example.com/sitemap.xml
两段代码的注释行占比刻意压在 35% 左右,不是为了好看,而是当时评审时确实靠注释里的「二期上线条件」避免了另一个组提前把 sort_by 全量封禁。
60 天收录与排名数据对照
改造在 3 月 12 日全量上线,观察窗口拉到 5 月 10 日,共 60 天。数据口径:Search Console「页面」报告导出 + 百度搜索资源平台索引量工具截图比对,核心词排名用无痕检索手工记录,每天上午 10 点固定一次。
| 指标 | 改造前(3 月 11 日) | T+30(4 月 11 日) | T+60(5 月 10 日) |
|---|---|---|---|
| 索引中重复收录 URL | 31,000 | 12,400 | 2,400 |
| 有效索引占比 | 38% | 71% | 89% |
| 每日抓取量中参数页占比 | 70% | 34% | 11% |
| 新品页平均收录时长 | 6.5 天 | 3.1 天 | 1.2 天 |
| 核心词「连衣裙」排名 | 第 14 位 | 第 11 位 | 第 6 位 |
| 类目页自然点击 / 日 | 1,340 | 1,890 | 3,050 |
timeline
title 60 天治理节奏与关键节点
T+0 : canonical 动态输出全量上线 : robots 一期封禁 session_id 与 gclid
T+14 : Search Console 重复页报告开始回落 : 抓取预算转向新品页
T+30 : 重复收录降至 1.24 万 : robots 二期封禁 sort_by : 有效索引占比过七成
T+45 : 核心词排名回到前 10 : 信号聚合效应显现
T+60 : 重复收录收敛到 2400 : 类目页自然点击翻倍
两个数据值得单独说。一是核心词排名的回升出现在 T+45 前后,比索引清理晚了半个月——这正是机制剖析里说的信号聚合重新计算的滞后,如果只盯两周数据就下结论,很容易误判方案无效。二是百度那边的重复收录从 2.7 万降到 3100,比 Google 降得还干净,推断原因是百度对 canonical 的信任在内容高度一致的簇里更容易成立,反而不存在 Google 那种「选择不同规范网址」的摇摆。
误区澄清
收尾前把评论区最常被问到的三个坑说清楚。
canonical 指向另一个域是合法的。规范文档明确允许跨域 canonical——内容 syndicate 到合作媒体时,转载页指向原文是标准用法。但电商参数页场景千万别这么用:曾有兄弟站点把测试环境域名的参数页 canonical 指到生产域,结果测试环境的脏数据把生产簇的信号搅乱了。跨域 canonical 只用于内容真正同源的场景。
robots Disallow 和 noindex 不能写在同一个页面里指望叠加生效。Disallow 挡住抓取后 noindex 根本读不到,页面会以「已抓取 - 尚未编入索引」之外的状态长期悬在索引里,正确做法是二选一。
canonical 不是免责金牌。它合并的是信号,不是删除页面;如果变体页的内容差异大到引擎判定不是重复(比如筛选后商品集只剩两三个、页面实质变成了精选页),canonical 会被无视,页面照常进索引。参数收敛还是要回到产品侧——筛选状态尽量走前端渲染或 AJAX,URL 上只保留有独立收录价值的参数。
最后补一句题外话:把这几万张重复页清理掉之后,AI 引擎抓取也会跟着聚焦,算是一个顺手的收益。有别的参数处理问题欢迎评论区聊,尤其是 canonical 与分页组合的那几种争议写法。
参考与延伸
- 合并重复网址(Google 搜索中心):https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls
- Search Console 使用与调试(Google 搜索中心):https://developers.google.com/search/docs/monitor-debug/search-console
- 百度搜索资源平台(索引量工具与普通收录入口):https://ziyuan.baidu.com/
canonical 重复收录 筛选参数 URL 规范化 抓取预算 Search Console