本文翻译自Design a web crawler,主要描述了一个简单搜索系统的架构设计。
需求用例
基本需求
- 抓取一系列的URLs的内容,生成倒排索引、标题和摘要。这里假设标题和摘要是静态的,不需要随着搜索词而动态改变。
- 用户输入一个查询词,输出搜索到文章列表,每篇文章带有标题和摘要。
- 系统具有高可用性
扩展需求
- 搜索分析
- 个性化搜索结果
- Page rank
假设限制
状态假设
- 具有热点问题,有些搜索词很热门
- 仅支持匿名用户
- 搜索耗时要非常短
- 爬虫系统不能陷入死循环
- 需要抓取10亿网页
- 网页内容需要定期的更新
- 一般网页平均更新周期为一个月一次,热门网页需要更频繁
- 估算每个月大概需要抓取40亿网页
- 每个页面平均大小为500KB
- 每个月1000亿次查询
容量计算
- 每个月需要存储2PB(500KB * 40亿)网页内容,3年需要存储72PB
- 每秒1600次写请求
- 每秒40000次查询
架构设计
爬虫系统
假设我们有一个初始的待抓取的URL列表links_to_crawl,其中的URL根据受欢迎程度排序。另外我们使用表crawled_links存储已抓取到的页面URL和其内容signature,这里可以使用kv-nosql数据库。对于待抓取列表links_to_crawl,我们可以存放到redis的zset中。接下来我们描述抓取的整个流程:
- 从redis的zset中取优先级最高的URL,并抓取内容计算signature
- 去crawled_links的数据库中查询相似的signature
- 如果查找到有相似的signature,说明之前已经有抓取到相似的页面,这时候什么都不做。
- 否则,
- 添加一个任务到Reverse Index Service队列中用来生成倒排索引
- 添加一个任务到Document Service队列中用来提取标题和摘要
- 生成页面signature
- 将该URL从links_to_crawl中删除
- 将该URL和其内容signature加入到crawed_links数据库中
- 去crawled_links的数据库中查询相似的signature
重复页面
为了保证抓取不会陷入死循环,我们使用signature的机制来防止重复抓取。signature的计算可以根据页面内容计算,通常的方法有Jaccard相似度或者Cos相似度。当然在计算之前,需要对页面内容进行分词、提取关键字等将页面转换为向量。
更新频率
一般来说,普通的页面每隔一周要重新抓取一遍以获得最新的内容,热门的网页需要更频繁一些。为了确认每个页面的更新频率,可以使用数据挖掘和统计分析的方式获得页面的更新频率,另外可以使用Robots.txt中建议的更新频率。
检索系统
- 客户端发送http请求到Web服务器
- Web服务器转发请求到Query Api服务,Query Api服务会做以下事情
- 解析Query
- 删除其中的标点符号
- 修正拼写错误
- 归一化大小写
- Reverse Index倒排索引服务返回相关的文档
- Document Service服务返回相关文档的标题和摘要
扩展架构
以下列了一些可能的优化措施:
- 针对热门查询,可以使用内存缓存数据库,比如Redis、Memcache,减少系统的耗时,承接更大的流量。
- Reverse Index服务和Document Index服务采用分布式系统
- 域名解析可能会是瓶颈,需要自建DNS解析器并定期更新
- 抓取系统使用连接池或者UDP来提升抓取性能
- 保证带宽是足够用的