收集需求并确定问题的范围。提出问题以澄清用例和约束。讨论假设。
如果没有面试官来解决澄清问题,我们将定义一些用例和约束。
我们将问题范围限定为仅处理以下用例
超出范围
状态假设
练习使用更传统的系统——不要使用现有的系统,例如solr或nutch。
计算用量
如果您应该进行粗略的使用计算,请与您的面试官澄清。
方便的转换指南:
概述包含所有重要组件的高级设计。

深入了解每个核心组件的细节。
我们假设我们有一个links_to_crawl最初基于整体网站受欢迎程度的排名的初始列表。如果这不是一个合理的假设,我们可以使用链接到外部内容(如Yahoo、DMOZ等)的流行网站为爬虫播种。
我们将使用一个表crawled_links来存储处理后的链接及其页面签名。
我们可以将links_to_crawl和存储crawled_links在键值NoSQL 数据库中。对于 中的排名链接links_to_crawl,我们可以使用带有排序集的Redis来维护页面链接的排名。我们应该讨论选择 SQL 或 NoSQL 之间的用例和权衡。
crawled_links在NoSQL 数据库中检查具有相似页面签名的条目
links_to_crawl中删除链接crawled_links在NoSQL 数据库中插入页面链接和签名与你的面试官澄清你需要写多少代码。
PagesDataStore是使用NoSQL 数据库的爬虫服务中的抽象:
- class PagesDataStore(object):
-
- def __init__(self, db);
- self.db = db
- ...
-
- def add_link_to_crawl(self, url):
- """Add the given link to `links_to_crawl`."""
- ...
-
- def remove_link_to_crawl(self, url):
- """Remove the given link from `links_to_crawl`."""
- ...
-
- def reduce_priority_link_to_crawl(self, url)
- """Reduce the priority of a link in `links_to_crawl` to avoid cycles."""
- ...
-
- def extract_max_priority_page(self):
- """Return the highest priority link in `links_to_crawl`."""
- ...
-
- def insert_crawled_link(self, url, signature):
- """Add the given link to `crawled_links`."""
- ...
-
- def crawled_similar(self, signature):
- """Determine if we've already crawled a page matching the given signature"""
- ...
Page是Crawler Service中的一个抽象,它封装了页面、其内容、子 url 和签名:
- class Page(object):
-
- def __init__(self, url, contents, child_urls, signature):
- self.url = url
- self.contents = contents
- self.child_urls = child_urls
- self.signature = signature
Crawler是Crawler Service中的主要类,由Page和组成PagesDataStore。
- class Crawler(object):
-
- def __init__(self, data_store, reverse_index_queue, doc_index_queue):
- self.data_store = data_store
- self.reverse_index_queue = reverse_index_queue
- self.doc_index_queue = doc_index_queue
-
- def create_signature(self, page):
- """Create signature based on url and contents."""
- ...
-
- def crawl_page(self, page):
- for url in page.child_urls:
- self.data_store.add_link_to_crawl(url)
- page.signature = self.create_signature(page)
- self.data_store.remove_link_to_crawl(page.url)
- self.data_store.insert_crawled_link(page.url, page.signature)
-
- def crawl(self):
- while True:
- page = self.data_store.extract_max_priority_page()
- if page is None:
- break
- if self.data_store.crawled_similar(page.signature):
- self.data_store.reduce_priority_link_to_crawl(page.url)
- else:
- self.crawl_page(page)
我们需要小心网络爬虫不会陷入无限循环,当图形包含循环时会发生这种情况。
与你的面试官澄清你需要写多少代码。
我们要删除重复的网址:
sort | unique- class RemoveDuplicateUrls(MRJob):
-
- def mapper(self, _, line):
- yield line, 1
-
- def reducer(self, key, values):
- total = sum(values)
- if total == 1:
- yield key, total
检测重复内容更加复杂。我们可以根据页面内容生成一个签名,并比较这两个签名的相似性。一些潜在的算法是Jaccard 索引和余弦相似度。
需要定期抓取页面以确保新鲜度。爬网结果可能有一个timestamp字段,指示上次爬网页面的时间。在默认时间段之后,比如一周,所有页面都应该被刷新。经常更新或更受欢迎的网站可以在更短的时间间隔内刷新。
虽然我们不会深入分析分析的细节,但我们可以进行一些数据挖掘来确定特定页面更新之前的平均时间,并使用该统计数据来确定重新抓取页面的频率。
我们也可以选择支持Robots.txt让网站管理员控制抓取频率的文件。