该文章分析了技术文档站爬虫不抓取文章的问题,指出孤岛页面、内链断开和点击深度过大的问题,并提出了具体的解决方法,包括恢复语义化链接、分页规范调整以及引入跳跃式跨度分页和层级面包屑等,以提高页面可抓取性。
上个月给一个技术文档站补了六百多篇排错笔记,Sitemap 生成好并提交给百度搜索资源平台和 Google Search Console 之后,我原本等着看收录曲线往上窜。结果连着盯了三周,控制台里的“已发现 - 目前未编入索引”堆积成山,日均抓取频次纹丝不动。
刚开始我怀疑是新页面内容太单薄,或者被算法打上了低质标签。直到翻开 Nginx 的访问日志,按爬虫 IP 和抓取 URL 筛了一圈,才发现蜘蛛每天几乎只在首页、分类第一页和最近发布的七八篇文章之间打转,那些更深层的文章一次都没被访问过。
我拿脚本跑了一遍全站链接拓扑,抓出两个硬伤:大量文章是没有任何站内反向链接的孤岛页面(Orphan Pages),同时分页层级过深,点击深度(Click Depth)直接超标。搜索引擎对普通站点有严格的抓取预算,顺着死胡同找不到路,光靠 Sitemap 喊话根本不顶用。这里把排查过程和具体改法理一遍。
迷信 Sitemap 的误区:孤岛页面是怎么被抛弃的
很多人觉得把 URL 全塞进 sitemap.xml,爬虫就会老老实实逐个抓取。现实完全不是这样。
Sitemap 对搜索引擎来说只是一个抓取候选清单,并不能证明该页面在站点内部的实际权重。爬虫的调度算法天然依赖 HTML 里的超链接进行权重计算和爬行调度。如果一篇文章在整站的 HTML 树状结构里没有任何站内链接入口,爬虫会认为站长自己都不重视这个页面。
这种页面就被称为孤岛页面(Orphan Pages)。在我的站点上,孤岛页面的产生通常有几个来源:
- 文章发布时只进了数据库,但首页的推荐位有限,很快被新内容挤掉。
- 关联标签没有生成单独的聚合索引页,或者标签页本身也是空的。
- 文章正文里没有被其他老文章引用过,缺乏上下文锚文本支持。
爬虫从 Sitemap 读到这个 URL 时,往往先把它扔进待抓取队列的末尾。如果站点日常权重一般,或者爬虫抓取配额有限,这些队列里的深层页面可能几个月都不会被调度一次。
前端翻页陷阱:JavaScript 瀑布流与丢失的 a 标签
排查过程中我发现的第二个隐蔽问题,出在列表页的前端翻页实现上。
之前为了追求移动端交互体验,前端列表做成了瀑布流,底部放了一个“加载更多”的按钮。代码逻辑是用 JavaScript 监听点击事件或者滚动监听,然后异步请求后端 API,把新的 HTML 片段塞进 DOM。
用户在浏览器里滚动得很顺滑,一口气能翻出二三十页。但是百度蜘蛛和大多数通用爬虫并不会模拟用户的向下滚动或按钮点击行为。
打开页面源码(Ctrl+U 或者查看原始 HTTP 响应体),列表底部只有这么一段代码:
<div class="load-more-btn" id="btnLoadMore" onclick="fetchNextPage()">
点击加载更多
</div>
在爬虫看来,整个分类列表永远只有第一页展示的 10 篇文章。后面所有分页对应的成百上千个 URL,在静态 HTML 里根本不存在可以追踪的超链接。
改法很直接:必须恢复语义化的标准 HTML 链接。即使前端要做无刷新加载,底层的静态输出也必须带有明确的 href 属性:
<div class="pagination">
<a href="/category/linux/page/2" class="next-page-link">下一页</a>
</div>
配合渐进增强脚本,人类浏览器拦截点击事件走异步渲染,而爬虫抓取时顺着 href 属性就能继续爬到第二页、第三页。
另外需要特别注意分页规范:切忌在第 2 页及以后的页面上把 <link rel="canonical"> 粗暴地指向第 1 页。每一页分页都代表独立的内容切片,分页页面的 canonical 必须指向其自身 URL,否则爬虫会认为后面的分页是第一页的重复内容,从而主动放弃抓取。
线性分页与点击深度:爬虫为什么走到第 4 层就停了
修复了 a 标签之后,我又遇到了点击深度(Click Depth)的问题。
点击深度指的是从网站首页出发,顺着链接最少需要点击几次才能到达目标内容页。大部分开源博客或 CMS 的默认分页模板是线性结构:只有“首页”、“上一页”、“下一页”、“尾页”。
假设某个技术分类下有 400 篇文章,每页 10 篇,共 40 页。如果一篇文章排在第 35 页,蜘蛛如果想通过列表顺藤摸瓜爬到它,必须按顺序爬取:
首页 -> 分类第1页 -> 第2页 -> 第3页 ... -> 第35页 -> 文章详情页
这意味着目标文章的点击深度高达 36。
搜索引擎分配给中小站点的单次抓取预算通常很保守。大多数通用爬虫在点击深度达到 4 到 5 层之后,抓取概率就会呈指数级下跌。把内链埋在深不可测的线性链条后面,等于把文章推向被忽略的边缘。
我调整了模板的分页渲染逻辑,引入了跳跃式跨度分页(Pagination Leap)和全站层级面包屑:
- 分页导航不再只是前后两页,而是渲染带跨度的快速直达节点,比如
1, 2, 3, 4, 5 ... 10, 20, 30, 40。这样即使是第 40 页的内容,爬虫从第一页跳过去最多只要两次点击。 - 页面顶部补齐面包屑导航,并在 HTML 源码中声明 BreadcrumbList 结构化数据,确保从详情页到分类、到根目录的双向通路是畅通的。
改造后,全站所有长尾文章从首页出发的最大点击深度,被压制在了 3 步以内。
侧边栏的马太效应与内链网状治理
解决了列表分页,剩下的核心就是解决正文之间的内链传递。
很多网站的侧边栏常年挂着“热门文章排行榜”。这种做法在用户体验上没毛病,但在 SEO 内链分布上会导致严重的马太效应:访问量最高的 10 篇文章,从全站每一页都拿到了反向链接;而刚发布的或者相对冷门的长尾文章,拿到的内链数量是 0。
为了打破这种头部垄断,我重构了文章详情页的推荐板块逻辑:
第一,增加相邻上下文跳转。每篇文章底部强制输出同分类下的前一篇和后一篇。由于文章是按发布时间连续排序的,这相当于在时间线上构建了一条双向链表,爬虫顺着任意一篇读进去,都能沿着前后节点横向遍历。
第二,基于标签的拓扑聚类推荐。我把推荐列表改成优先读取当前文章关联的技术标签,筛选出带有相同标签的 4 到 6 篇相关文章,不再挂全站无脑热门。
如果一篇文章讲的是 Nginx 日志切割,推荐列表出现的是 Nginx 配置调优、Logrotate 使用细节,而不是毫无关联的 Python 入门。这种相关性拉长了访客停留时间,同时也让搜索引擎能读懂清晰的上下文。
动手审计:用一段 Python 脚本排查站内孤岛页面
要确认整改效果,不能凭感觉猜。我写了一段小脚本,用来抓取整站能够顺着链接走通的所有内部 URL,并与实际的 Sitemap 文件做差集比对。
脚本原理很简单:从首页开始爬取所有同域名下的链接,存入已访问集合;爬取完成后读取本地导出的 sitemap.txt,找出那些存在于 Sitemap 中、但站内链接完全无法触达的页面。
import urllib.request
import re
from urllib.parse import urljoin, urlparse
BASE_URL = "https://www.llbbs.cn"
visited = set()
to_visit = {BASE_URL}
# 抓取站内可达链接集合
while to_visit and len(visited) < 1000:
current = to_visit.pop()
if current in visited:
continue
visited.add(current)
try:
req = urllib.request.Request(current, headers={"User-Agent": "InternalLinkAuditor/1.0"})
with urllib.request.urlopen(req, timeout=5) as resp:
if "text/html" not in resp.headers.get("Content-Type", ""):
continue
html = resp.read().decode("utf-8", errors="ignore")
except Exception:
continue
# 提取内部 a 标签链接
for match in re.findall(r'<a\s+(?:[^>]*?\s+)?href=["\']([^"\']+)["\']', html, re.I):
link = urljoin(current, match.split("#")[0])
parsed = urlparse(link)
if parsed.netloc == urlparse(BASE_URL).netloc and link.endswith(".html"):
if link not in visited:
to_visit.add(link)
print(f"站内链接可达页面总数: {len(visited)}")
# 读取 Sitemap 提取的待验证链接列表
with open("sitemap_urls.txt", "r", encoding="utf-8") as f:
sitemap_set = set(line.strip() for line in f if line.strip())
# 计算差集:在 Sitemap 中却无法从站内链接爬到的孤岛页面
orphan_pages = sitemap_set - visited
print(f"发现孤岛页面数量: {len(orphan_pages)}")
for url in list(orphan_pages)[:10]:
print(f" 孤岛 URL: {url}")
整改前跑这个脚本,扫出了 180 多个孤岛 URL,绝大部分是分页第 5 页之后的历史排错文章。把跳跃分页和标签聚合补上之后再跑,孤岛数量直接归零。
改动上线后的日志观察
调整上线后大概十天左右,在 Nginx 日志里监控 BaiduSpider 和 Googlebot 的抓取行径,变化很直观:
之前爬虫每天访问一两百次,80% 的请求集中在根路径和 CSS/JS 资源。跳跃分页生效后,日志里密集出现了对 /page/10、/page/20 以及深层内容页的抓取记录。
过了一周多,Google Search Console 里的“未编入索引”曲线上升势头被遏制,转入“已编入索引”的数量开始稳步爬升。
做技术站的内链不是搞花哨的黑帽手段,而是老老实实把整站的 HTML 树状结构搭建规范。把跳跃分页、面包屑和上下文推荐理顺,确保所有内容页的点击深度控制在 3 步以内,爬虫自然会顺着链接把深层文章逐一收录。
文章标题:新发了几百篇文章爬虫死活不抓?排查网站孤岛页面 (Orphan Pages)、点击深度过大与内链断层的几个排坑记录
文章链接:https://www.llbbs.cn/jishujaocheng/233.html
本站文章均为原创,未经授权请勿用于任何商业用途
评论一下?