需求和目标
爬尽可能多的网页内容。大概有10000亿个网页。需要爬他们至少每周一次。
1T /1m (一周假设有1M 秒)
那么大概有1M 的网页每秒要爬。
假设一个网页平均10K,就是10P的数据。
非功能性需求:
- 可扩展性: 如果要爬的网页量增多,可以水平扩展,增加并行度
- 健壮性: 网络里充满了陷阱,比如糟糕的HTML,不响应的服务器,节点崩溃,恶意链接
- 礼貌: 爬虫不应该在短时间内对网站提出太多的请求
- 可演化性: 为了支持性的特性,系统是灵活的,只需要最小的变化
服务
首先我们需要有很多爬虫模块。
- 一组种子 URL( 作为爬虫开始的起点,这里的选取策略是开放式的)
- URL队列(这里面存的是待爬去的URL,会交给分布式WORKER去实际下载URL里的内容)
- WORKER 里面 会包含几个模块(一个是HTML下载,一个是DNS解析,content parser, content dedup,url extractor)
- 上述步骤都做完了,会把解析后的内容存到CONTENT STORAGE 里。
- 然后再把这个HTTP PAGE里的链接到URL取出来,加到任务数据库(task service)中。(这里面可以也包括几个子模块,如url filter, 去handle error links, blacklisted url; url去重)
爬虫模块负责从TASK SERVICE里取任务。然后加到MQ (URL FRONTIER)
同时爬到之后又把URL 给TASK SERVICE 存新任务。
那么TASK SERVICE 就是用来做任务管理的。
最后还需要一个存储服务,用来存爬到的信息。
我们使用RDBMS 来存TASKS,BIG TABLE来存网页内容(因为数据量过大,肯定需要HDFS)
首先说下单机爬虫,爬虫从一些根WEB SEEDS抓到网页内容,然后交给2个模块,存储内容模块,负责提取有用信息,放进BIGTABLE储存。EXTRACTOR模块,负责获取网站里的URL,然后做一个判定是否已经抓过,然后放进TASK SERVICE的URL QUEUE里。
随后我们引入多线程来提取抓取速度,因为访问网页的时候是NETWORK IO BLOCK。
为了防止多个线程的线程安全问题。我们可以使用线程安全的阻塞队列。如果没有阻塞队列,我们需要用锁来防止多线程竞争。
但是越多的线程不意味越好的性能。
因为1.线程多,那么切换开销也大。
2.超过CPU CORE的数量,多的线程也是在一个CPU的时间片里反复切换。
3.线程需要耗费资源,有限制
4.网络带宽也有限制。
DFS VS BFS
DFS 因会深度过深,所以在这个场景里BFS更好。
但BFS也会带来2个新问题,一个是LINK有环,另一个是大多数BFS出来的LINK都是属于同一个HOSTNAME,那么就会不太礼貌(短时间朝一个HOST发送大量请求),同时BFS 用的FIFO的队列,没办法考虑URL的优先级的设置问题。
要解决上面的问题,我们就要仔细设计一下存储TASK 的MQ(URL FRONTIER)了。
我们可以对多个HOSTNAME,建立单独的QUEUE,然后然一个WORKER负责一个QUEUE,并且在里面处理的时候,控制速率,可以解决这个问题,如上图。
不过更好的做法是,怕取一个任务后,可以更新这个HOST,下次可以爬取的时间,然后如果WORKER拿到之后,发现时间还没到,可以重新放回队列里。这样可以让一个QUEUE,拥有更多的HOSTNAME。
在处理优先级的时候,我们也可以根据不同的优先级开多个队列。然后QUEUE SELECTOR的时候根据优先级不同配置不同的概率,去选择哪个队列里取数据
然后我们可以对DNS的获取 做一层CACHE,来起到性能优化的效果。(因为从URL到IP,去DNS拿 需要10ms ~200ms的开销)(DNS CACHE 会自己有吗?)
局部性优化,知道网站HOST在哪,然后用更近的WORKER 去DOWNLOAD,可以加快下载速度
设置TIMEOUT,来防止某些URL 长时间没有响应
存储
WEB 内容就是直接存下来到HDFS上即可,放在BIG TABLE上 是方便QUERY。
下面主要设计TASK TABLE
一个TASK,需要目标URL,TASK 状态,优先级,有效时间。
TASK 状态,分为在爬,爬好了,还没开始爬。
有效时间,代表到这个时间前,爬好了都无需再爬。
优化
首先为了提高性能,我们需要构建分布式的爬虫。那么一个TASK DB,需要变成分布式。
我们可以用HOST NAME 来做SHARDING KEY。这样一个网站下的不同网页就会分配到同一个服务器了。
如何HANDLE 一个网页连接失败了?
很有可能是这个网站不存在了。为了不反复让费资源。我们可以用指数级拓展法。
比如现在是一周爬一次,挂了之后,改为2周爬一次。还挂改为4周爬一次。以此类推。
一旦有一次好了,就再除以2.
如何避免死循环?
已经爬过的我们设定了下次再爬的时间。这样可以避免死循环。
如果是不同的URL,而是过多的资源被一个HOST占用。我们可以限定配额,当这个HOST下的网页超过配额了。就先去爬别的,等别的爬的多了,那么再爬一点这个网页。让他始终低于一个比例。
如何避免多个不同的网页内容一样?
我们可以对网页内容计算一个CHECKSUM,如果这个CHECKSUM之前出现过。就不再存储。
CHECKSUM 64位就够了。
那么1TRILLION个网页,大概需要8 T 的空间。
如何提高访问页面的性能?
建立一个DNS CACHE 服务器,这样可以不用每次都到DNS 服务器上去把HOSTNAME 转换为IP。
可不可以使用布隆过滤器来去重URL
布隆过滤器的特点是有可能有,没有一定没有。这个特性会造成一些本来应该爬的URL,会被当成已经存在的,而不去爬。所以不太合适。
如何容错
一旦一个CRAWLER挂了就再起一个
一旦一个TASK SERVICE 挂了,我们需要定期把QUEUE的状态写进磁盘,当做一个存档点。这样下次可以恢复。
一旦一个STORAGE SERVICE挂了。我们直接用BIG TABLE 的容错机制来保护。外面应该有负载均衡器,会保障CRAWLER可以到健康的SERVICE上继续存储。
如何设置每个URL的优先级
我们可以手动设置优先级,把重要的一些URL的特征提取出来,或者建立几个优先级的名单。
当然也可以用算法来设置优先级。
1.首先我们需要一组根页面,使得每个页面分配一定的信用。
2.随后我们爬去这些页面,找到他有的LINK。同时把这个页面的信用清0
3.信用要交10%的税给LAMBDA页面。随后平均的分给下面的LINK。
4.信用高的页面会优先处理。
5.按照上述步骤不断去做。
因为都要交税给LAMBDA页面,所以LAMBDA页面信用涨的很快,很快就会要被处理。这个时候我们把这些信用平均分给所有DB未爬的页面上。
这样的好处就使得,因为有税的存在,那些只有内部链接的页面,就会不断信用流失。而正常的页面因为有外部链接,则不会发生这个问题。