分布式爬虫系统设计

一、核心需求

需求 说明
大规模抓取 每天抓取数十亿网页
礼貌性抓取 遵守 robots.txt,不压垮目标站点
去重 同一个 URL 只抓一次
优先级调度 重要页面优先抓取
容错 爬虫节点宕机不影响整体进度

二、系统架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──────────────────────────────────────────┐
│ URL Frontier │
│ (优先级队列 + 布隆过滤器去重) │
└──────────────────┬───────────────────────┘

┌──────────────┼──────────────┐
│ │ │
[爬虫节点1] [爬虫节点2] [爬虫节点N]
│ │ │
├─ DNS解析 │ │
├─ HTTP下载 │ │
├─ Robots检查 │ │
├─ HTML解析 │ │
└─ 链接提取 → URL Frontier

[文档存储]

三、URL Frontier

3.1 设计要点

URL Frontier 是分布式爬虫的大脑,负责管理待爬取 URL 的调度和分发。核心功能:

去重

使用布隆过滤器检测 URL 是否已爬取。百亿级 URL,0.01% 误判率 → 约 24GB 内存。为了支持长期运行,定期(如每周)从持久化存储重建布隆过滤器。

礼貌性控制

同一域名的两次请求间隔 ≥ politeness_delay(通常 1-10 秒)。策略是按域名分配后端队列,爬虫节点从各域名队列中轮流取 URL:

1
2
3
4
5
6
7
域名队列:
example.com[url1, url2, url3, ...]
wikipedia.org[url4, url5, ...]
github.com[url6, ...]

爬虫从 example.com 抓取一个 URL 后,必须等待 politeness_delay 才能再取该域名的 URL。
这期间可以从其他域名队列取 URL,保持爬虫持续工作。

优先级

优先级 URL 特征 示例
首页、热门页面、频繁更新的页面 新闻首页
文章页、产品页 博客文章、商品详情
用户评论页、历史归档 2010 年的归档新闻

3.2 分布式实现

按域名分片避免多个爬虫同时爬同一域名:

1
2
3
4
5
URL Frontier 分片策略:
shard = hash(domain) % N

这样就保证了同一域名的所有 URL 都由同一组爬虫处理,
无需额外的跨节点协调。

四、爬虫节点设计

4.1 抓取流程

1
2
3
4
5
6
7
8
9
10
11
1. 从 URL Frontier 获取一个 URL
2. DNS 解析 → IP 地址
3. 检查 robots.txt(可缓存,TTL 1 小时)
4. HTTP GET 请求(带 User-Agent、Referer 等头部)
5. 检查 HTTP 状态码
├─ 200 → 提取链接,存储文档
├─ 301/302 → 记录重定向,新 URL 加入队列(不计数原 URL)
├─ 429 → 退避重试(指数延迟)
└─ 4xx/5xx → 记录错误,放弃或有限重试
6. 解析 HTML,提取所有 <a href> 链接
7. 新链接过滤后加入 URL Frontier

4.2 HTML 解析

HTML 解析的目的是:

  • 提取链接:所有 <a href="..."> 标签,处理相对路径
  • 提取内容:标题、正文、发布时间(用于搜索引擎索引)
  • 规范化 URL:移除 fragment、排序 query 参数、去重协议(http vs https)

4.3 robots.txt 处理

每个域名在首次抓取时需要下载并解析 robots.txt:

1
2
3
4
5
User-agent: *
Disallow: /private/
Disallow: /admin/
Crawl-delay: 5
Allow: /public/

将此解析为规则集并缓存。每个爬虫节点在抓取 URL 前先检查是否被允许,减少对目标站点的压力。

五、去重策略

5.1 为什么去重如此重要

互联网上的页面存在大量重复链接:同一个页面被多个页面引用、URL 参数变化导致表面不同的 URL、HTTP/HTTPS 双版本等。不去重会导致:

  • 浪费带宽重复抓取同一内容
  • 目标站点压力剧增
  • 存储大量重复数据

5.2 布隆过滤器方案

1
2
3
4
5
6
7
布隆过滤器配置:
n = 100 亿(预期 URL 数)
p = 0.001(误判率 0.1%)
m = -100亿 × ln(0.001) / (ln2)² ≈ 1440 亿 bit ≈ 168 GB
k = m/n × ln2 ≈ 10

这需要 168 GB 内存,过大。可以通过以下方式优化:

优化方案:按域名分片的布隆过滤器

1
2
3
4
5
每个域名的布隆过滤器只需要覆盖该域名下的 URL 数量:
example.com 有 1000 万页面 → 14 MB 布隆过滤器
wikipedia.org 有 600 万页面 → 9 MB 布隆过滤器

每个爬虫节点按域名加载对应的布隆过滤器,整体内存分散到所有节点。

5.3 URL 规范化

在进入布隆过滤器之前,需要对 URL 做规范化(Canonicalization):

1
2
3
4
5
6
7
8
9
原始 URL: http://www.example.com:80/path?b=2&a=1#section
规范化: http://example.com/path?a=1&b=2

处理规则:
- 协议转小写
- Host 转小写,去掉默认端口
- 路径标准化(/.//, /a/../b/ → /b/)
- Query 参数按字母序排序
- 去掉 fragment (#xxx)

六、内容提取与存储

6.1 内容去重

即使 URL 不同,页面内容也可能完全相同(镜像站、CDN 等)。除了 URL 去重,还需要内容去重。使用 SimHashMD5 签名来实现:

1
2
3
4
5
对于每个抓取的页面:
1. 提取正文文本
2. 计算 MD5 哈希
3. 如果哈希已存在 → 此页面是重复内容,跳过
4. 如果哈希不存在 → 存储并记录

SimHash 的额外优势:两个页面即使内容不完全一致,如果 SimHash 的汉明距离很小(< 3),说明它们高度相似,可能是镜像站或转载,可以选择只保留一个版本。

6.2 存储

数据 存储 说明
原始 HTML 对象存储 (S3/HDFS) 压缩后存储
结构化数据 宽表 (HBase/Cassandra) URL、标题、时间、关键词
链接图 图数据库或 K-V 存储 URL → {引用该 URL 的页面列表}
爬虫状态 Redis 当前进度、速率、错误率

七、爬虫陷阱与反爬对抗

7.1 常见陷阱

陷阱类型 表现 应对
无穷日历页 某个博客按日期归档,日期链接无穷无尽 限制路径深度、限制同域名下的链接数量
Session ID 陷阱 每次访问生成新的 session ID 参数导致 URL 无限增多 URL 规范化时去掉 session 参数
动态页面 页面内容根据参数不断变化(搜索页、过滤页) 识别低价值页面类型,降低优先级或直接跳过
重定向循环 A → B → C → A 限制重定向次数(≤ 5 次),记录重定向链

7.2 反爬对抗

现代网站大量使用反爬技术,企业级爬虫需要相应策略:

  • IP 代理池:维护数千个代理 IP,每个域名分配不同的出口 IP
  • User-Agent 轮换:模拟不同浏览器和设备的 User-Agent
  • 渲染引擎:对于 SPA (React/Vue) 页面,使用 Headless Chrome 渲染后再提取内容
  • 行为模拟:鼠标移动、滚动等,降低被检测为爬虫的概率

八、容量估算

假设目标:每天抓取 10 亿网页

  • 平均页面大小 100KB → 每天下载 10亿 × 100KB = 100TB
  • 平均抓取速度 50 页/秒/节点 → 需要 10亿 / (50 × 86400) ≈ 230 个爬虫节点
  • 考虑网络延迟和处理开销,实际需要 300-500 个节点

九、小结

分布式爬虫的核心挑战是规模礼貌性的统一。URL Frontier 的优先级调度和域名分片策略保证了公平高效的抓取,布隆过滤器提供了 O(1) 的去重能力,URL 规范化和 SimHash 去重则进一步减少了存储浪费。Google、CommonCrawl 等大型搜索引擎都在此架构基础上做了各自的优化——比如 Google 使用自研的 Caffeine 索引系统处理实时更新,CommonCrawl 每两个月抓取一次全量快照作为公开数据集。