文章讲述了将网站从分页改为无限滚动后,搜索引擎索引量大幅下降的问题。作者分析了爬虫无法抓取动态加载内容、错误使用 canonical 标签等原因,并提出了使用渐进增强、保留静态链接和正确配置 canonical 标签的解决方案。
去年底重构一个内容资讯站时,为了让移动端浏览体验顺畅,我把原本传统的分页栏改成了无限瀑布流。用户滑到底部自动请求下一页接口,桌面端保留一个好看的“点击加载更多”按钮。
上线前交互体验测试顺滑,首屏跳出率还下降了 6%。
结果两个月后看搜索引擎控制台,心凉了半截。百度和谷歌的索引量直接从一万二千多条断崖式跌到两千多条,新发布的文章只要掉出列表第一页,搜索引擎就完全不抓了。服务器日志里一搜,蜘蛛整天只在第一页打转,第二页往后的海量历史文章变成了没人能找到的孤岛。
那两个星期我把前端路由、后端分页接口和蜘蛛抓取日志反复对照,发现自己踩了一堆网上常见教程根本不提的暗坑。
爬虫根本不会帮你滑屏幕,无实体链接就是抓取死穴
很多人以为现在的搜索引擎爬虫都带无头浏览器渲染,能跑 JavaScript,就觉得无限滚动不会影响抓取。这个假设只对了一半。
爬虫确实会用基于 Chrome 的无头环境去渲染页面,但爬虫的资源预算极度抠门。爬虫访问一个 URL 时,通常只等 DOMContentLoaded 和初始异步请求完成,根本不会模拟用户手指向下滑动屏幕,更不会去触发你写在 window 上的 onscroll 监听函数。
当时我的前端组件写得很典型:
window.addEventListener('scroll', () => {
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500) {
if (!loading && hasMore) {
fetchNextPage();
}
}
});
用户滑到底部,JS 发 Ajax 请求 api/posts?page=2,拿到 JSON 后动态拼接 DOM 挂在容器末尾。
在浏览器看来体验顺滑,但对百度蜘蛛(Baiduspider)和谷歌蜘蛛(Googlebot)来说,它拿到 HTML 执行完初始脚本就直接交卷了。由于页面底部没有任何标准的 <a href="..."> 链接指向第 2 页,爬虫根本不知道第 2 页的存在。
单靠脚本事件驱动的分页,在搜索引擎眼里等同于“这个网站只有 15 篇文章”。
修复方法:用渐进增强把静态链接埋回 DOM
要保留瀑布流的交互,又得让爬虫顺畅爬取,最稳妥的手段是服务端渐进式增强(Progressive Enhancement)。
前端列表底部必须保留一个纯 HTML 的分页容器。无论页面交互再怎么炫,HTML 源码里必须老老实实输出带有真实 href 的标签:
<div class="pagination-fallback">
<a href="/category/tech?page=2" class="next-page-link">下一页</a>
</div>
然后在前端 JavaScript 里拦截这个链接的默认点击事件:
const nextBtn = document.querySelector('.next-page-link');
if (nextBtn) {
nextBtn.addEventListener('click', function(e) {
e.preventDefault();
loadMoreArticles(this.getAttribute('href'));
});
}
普通用户进入页面时,脚本可以给这个 fallback 容器加上 display: none 或者用透明占位替换掉,同时用滚动监听或者 IntersectionObserver 去自动触发加载。而当搜索引擎爬虫爬取时,或者用户关掉了脚本时,它顺着标准的 <a> 标签依然能拿到清晰的分页 URL。
千万别把第 2 页以后的 Canonical 无脑指向第 1 页
这是那次排查中我发现最坑的一点,网上不少博客甚至一些开源建站主题的作者都犯过这个常识性错误。
当时为了防止搜索引擎觉得 ?page=2、?page=3 和列表首页内容雷同,有人建议在所有分页头上统一加 canonical 指向根目录:
<link rel="canonical" href="https://example.com/category/tech" />
这个操作完全是自掘坟墓。
Canonical 标签的语义是“当前页面的内容是目标 URL 的副本,请合并权重,不要索引当前页面”。你把第 2 页、第 3 页的 canonical 指回第 1 页,就等于亲口告诉搜索引擎:第 2 页上的这 20 篇新文章都是第 1 页的重复数据,不需要建索引。
百度直接把第 2 页以后的 URL 从索引库里全部注销,谷歌在控制台里直接标注“重复页面:Google 选择的规范网页与用户提供的不同”,后续文章彻底失去了曝光入口。
正确的 Canonical 配法
分页列表的每个分页页面,其规范 URL 必须指向它自己本身。
在 https://example.com/category/tech?page=2 上,canonical 标签必须准确声明自己:
<link rel="canonical" href="https://example.com/category/tech?page=2" />
并且,对于分页 URL 的参数格式要全站统一。千万别在正文内链里写 /tech?page=2,而网站地图里写 /tech/page/2/,这会导致权重在两条不同的 URL 之间互相拉扯。
早就废弃的 rel="next" 与现代分页标题去重
多年前大家习惯在 <head> 里写 <link rel="prev" ...> 和 <link rel="next" ...>。但我查阅谷歌官方文档后发现,早在 2019 年谷歌就宣布不再将 rel="next/prev" 作为分页索引的合并信号了。
现在的爬虫把每一个分页 URL 都视作一个独立的网页进行评估。如果你的几十个分页页面 <title> 和 <h1> 全叫“技术教程 - 雷灵博客”,搜索引擎的质量算法很容易把它们归类为批量生成的相似度过高页面(Soft 404 或低质列表)。
我的调整方式是在服务端模板输出标题时,只要页码大于 1,就强制注入页码后缀:
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
$title = "技术教程";
if ($page > 1) {
$title = "技术教程 (第 {$page} 页) - 雷灵博客";
}
这样做让每个分页的标题标签具有唯一性,同时列表页的 description 也要避免完全套用一段无意义的通用文字。
筛选、排序与分页交织导致的蜘蛛参数爆炸
改了前面两项后,我观察了一周的 Nginx 访问日志,发现爬虫抓取频次上来了,但服务器的 CPU 负载时不时飙高,而且收录依然很慢。
我把日志里带蜘蛛特征的请求拿 awk 统计了一下:
cat access.log | grep -iE 'baiduspider|googlebot' | awk '{print $7}' | grep 'category/tech' | head -n 30
结果抓出来一堆触目惊心的 URL:
/category/tech?sort=views&order=desc&filter=free&page=48
/category/tech?sort=comments&filter=vip&page=35
/category/tech?order=asc&sort=date&page=82
前端为了做多维度筛选,把排序、条件标签和页码自由拼在 Query String 里。爬虫只要从某个带有排序的链接钻进去,就会把每一种排序方式下的前 50 页全都抓一遍。
一个分类原本只有 10 页文章,组合了三种排序和两个过滤标签后,直接裂变出上千条内容高度重合的冗余页面。有限的蜘蛛抓取预算(Crawl Budget)全被耗死在这些无意义的变种分页上了,真正包含新文章的正经分页反而排不上抓取队列。
Nginx 与 robots.txt 的截流策略
对付这种参数爆炸,不需要在代码里做极其复杂的逻辑,用 robots.txt 规范配合 Nginx 响应头就能快速收尾。
首先在 robots.txt 里把带有排序与筛选参数的分页彻底封堵,只留最干净的单一分页参数:
User-agent: *
Disallow: /category/*sort=*
Disallow: /category/*order=*
Disallow: /category/*filter=*
Allow: /category/*?page=*
如果有些页面已经被收录了,不能简单粗暴在 robots.txt 里拦截,因为 robots 拦截后爬虫不会抓取页面,导致已经存在的脏索引无法被刷新。针对这种情况,可以在 Nginx 里判断参数,给包含排序或多重过滤的分页动态注入 X-Robots-Tag: noindex, follow:
location /category/ {
if ($args ~* "(sort|order|filter)=") {
add_header X-Robots-Tag "noindex, follow" always;
}
try_files $uri $uri/ /index.php?$args;
}
这里的关键是保留 follow。告诉爬虫不要索引这个带参数的重复筛选列表,但允许爬虫顺着列表里正文的链接继续往下抓取文章,把抓取权重集中输送给具体文章详情页。
分页深度过长:给前三页以后的老文章开直达天梯
还有一个很隐蔽的权重衰减问题。
如果一个分类有 80 页,即使爬虫顺着第 1 页抓到第 2 页,再抓到第 3 页,当爬到第 30 页时,页面在整个站点的层级深度(Click Depth)已经达到了 30 层。对任何搜索引擎算法来说,距离根目录层级超过 4 层的页面,分到的抓取频次和初始权重都会断崖式下跌。
这也是为什么无限滚动哪怕能抓,排在后头的文章也往往收录极慢。
我后来在架构上补了两套救砖机制:
- 改写分页跳转步长:在底部的静态分页器里,除了输出上一页和下一页,同时显式输出大跨度的跳转链接,比如
[1] [2] [3] ... [10] [20] [50] [末页]。这样能把最底层的深层页面在逻辑上缩短到两次点击以内。 - XML Sitemap 补充独立的分页与内容全量列表:不要只在站点地图里放首页和最新几篇文章。编写定时脚本,把所有包含真实内容的分类分页及历史文章直接写入
sitemap_articles.xml,主动推送到百度搜索资源平台和 Google Search Console。
把实体分页链接、独一无二的自引用 Canonical、标题防重以及参数截流做扎实以后,网站的抓取日志才终于恢复正常。两周后监控后台看到抓取频次稳步爬升,掉出索引的深层页面也陆续重新出现在搜索结果里。
文章标题:网站改了无限滚动和加载更多,深层页面收录却彻底归零?排查分页爬虫黑洞、Canonical 误杀与参数爆炸我踩过的几个坑
文章链接:https://www.llbbs.cn/jishujaocheng/228.html
本站文章均为原创,未经授权请勿用于任何商业用途
评论一下?