设计一个网页爬虫

定义 User Case 和 约束

注意:没有一个面试官会阐述清楚问题,我们需要定义Use case和约束

Use cases

我们的作用域只是处理以下Use Case:

  1. Service 爬取一批 url
  • 生成包含搜索词的单词到页面的反向索引
  • 给页面生成标题和片段
  • -- 标题和片段是静态的,他们不会基于搜索语句改变
  1. User 输入一个搜索词然后看到相关页面的List, 伴随着爬虫生成的 title 和 snippet
  • 只有描绘出High Level的组件和User Case的交互,不需要去深入
  1. Service有高可用

作用域外:

  1. 搜索分析
  2. 个人化的搜索结果
  3. 页面排名

约束和假设

状态假设:

  1. 流量分布不均匀
  • 一些搜索访问非常频繁,当其他搜索都只是执行一次
  1. 只是支持匿名用户
  2. 生成搜索结果应该是很快的
  3. 这个 Web 爬虫不应该被阻塞在无限循环
  • 如果这个图包含一个周期,我们将被阻塞在无限循环
  1. 10 亿link会被爬
  • 页面需要被规律的爬去确保刷新
  • 平均的刷新率应该是一次一周,热门网站应该更频繁
  • -- 每个月会爬4 亿 link
  • 平均每个网页存储尺寸是:500 kb
  • -- 为了简化,计数和新页面相同
  1. 每个月1000 亿搜索

使用更传统的系统进行练习 - 不要使用现存的系统比如 solr 或者 nutch

计算使用量

和你的面试官说清楚你权衡之后选择的最优的方案

  1. 每个月 2 PB 的存储页面内容
  • 500 KB 每页 * 40 亿 link / month
  • 3年内存储 72 PB页面内容
  1. 1600 写请求 / s
  2. 40000 搜索请求 / s

便利的转换指南

  • 每月有 250 万秒
  • 1 请求 / 秒 = 250 万请求 / 月
  • 40 请求 / 秒 = 一亿请求 / 月
  • 400 请求 / 秒 = 十亿 请求 / 月

创建一个 High Level 设计

设计核心组件

Service 爬取 url 的 list

我们假设我们有一个初始化list links_to_crawl 基于整体站点流行度排序初始化,如果这不是一个合理的假设的话,我们可以搜索这个爬虫伴随着流行度网站。link到外面的内容,比如 Yahoo, DMOZ等

我们使用一个表 crawled_links 去存储处理过的link和他们的页面签名.

我们可以存储 links_to_crawlcrawled_links 在一个 key-value NoSQL Database. 对于排名的link 我们存进 links_to_crawl, 我们可以使用 Redis 伴随着 sorted set去维护一个页面link的排名。我们应该讨论不同 use case之间的最优解。

  • Crawler Service 通过循环处理每个页面的link:
    • 获取排名第一的page link给爬虫
      • 检查在NoSQL数据库中 crawled_links for 一个有相同页面签名的 entry
        • If 我们有一个相同的 page,减少页面link的优先级
          • 这将防止我们进入循环
          • 继续
        • Else 爬取这个 link
          • 添加一个job到 Reverse Index Service的队列去生成一个 reverse index
          • 添加一个 job 到 Document Service 队列去生成一个静态Title和代码片段
          • 生成一个页面签名
          • 从 links_to_crawl 移除 link 进 NoSQL Database
          • 插入页面link和签名到 crawled_link 进 NoSQL Database

PageDataStore 是一个抽象伴随着 Crawler Service,并且使用 NoSQL 数据库

python 复制代码
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, 用来疯涨一个 Page, 他的内容, child urls,和签名

python 复制代码
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中的主类, 聚合 PagePagesDataStore

python 复制代码
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)

处理重复Link

我们需要小心这个Web爬虫不会被阻塞在一个无限循环里面,这种情况发生在graph包含一个Cycle.

我们需要去移除重复的 urls:

  1. 对于稍小的 list 我们可以使用 sort | unique
  2. 当有十亿 link需要爬时,我们可以使用 MapReduce 去输出,然后确定频率到1
python 复制代码
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 Index

决定什么时候去更新爬虫的结果

Pages 需要被常规的爬取用以刷新,爬取结果将有一个 timestamp 字段,用来指示这个pgae上一次被爬取的时间,在默认时间段,S一周所有的page会被刷新,频繁的更新或者更流行的网站会被刷新在更短的周期。

尽管我们不会深入分析细节,我们可以做一些数据修剪用来决定在特定页被更新的时间,而且使用 statistic 来决定重新爬取页面的频率

User Case: 用户输入一个搜索Term并且看到一个相关页面(包括title和片段)的list
  1. Client 发送一个请求到 Web Server
  2. Web Server 转发请求到 Query API server
  3. Query API server 做下面的事
    • 解析 Query
      • 移除 markup
      • 分解 text 进 term
      • 修复 typos
      • 格式化首字母
      • 转换 Query 去使用 bool 操作
    • 使用 Reverse Index Service 去寻找匹配 query 的文档
      • Reverse Index Service 排序匹配的结果,然后返回Top的记录
    • 使用 Document Service 去返回 titles 和 文档片段

我们可以使用 public REST API:

shell 复制代码
$ curl https://search.com/api/v1/search?query=hello+world

Response:

json 复制代码
{
    "title": "foo's title",
    "snippet": "foo's snippet",
    "link": "https://foo.com",
},
{
    "title": "bar's title",
    "snippet": "bar's snippet",
    "link": "https://bar.com",
},
{
    "title": "baz's title",
    "snippet": "baz's snippet",
    "link": "https://baz.com",
},

扩展设计

在限制条件下,识别并解决瓶颈问题。

针对 Crawler Service目前发现这些优化点:

  1. 为了处理 data size 和 request load, Reverse Index Service 和 Document Service 将很有可能使用 Shadring 和 federation.
  2. DNS 查询会是一个 bottleneck, Crawler Service 会保持它自己的 DNS 查询,而且周期性刷新
  3. Crawler Service会提高性能并且减少内存使用(通过保持大量开放连接的方法),可以考虑切换到 UDP
  4. 网络爬虫是带宽密集型的,确保有足够的带宽来维持高吞吐量
相关推荐
爬虫程序猿9 小时前
利用 Java 爬虫获取淘宝商品 SKU 详细信息实战指南
java·开发语言·爬虫
元Y亨H14 小时前
Playwright 下载文件时页面被 Chrome 过滤的问题
爬虫
q5673152320 小时前
手把手教你用Go打造带可视化的网络爬虫
开发语言·爬虫·信息可视化·golang
q567315231 天前
无需Python:Shell脚本如何成为你的自动化爬虫引擎?
开发语言·爬虫·python·自动化·scala
moonsheeper2 天前
用爬虫技术及各种技术使用场景
爬虫
是有头发的程序猿2 天前
电商开发日志:淘宝图片搜索商品列表(二)
数据库·爬虫·python
fyakm2 天前
python和java爬虫优劣对比
java·爬虫·python
华科云商xiao徐2 天前
手把手教你用Go打造带可视化的网络爬虫
前端·爬虫
华科云商xiao徐2 天前
Python爬虫数据清洗实战:从杂乱无章到整洁可用
爬虫·数据挖掘·数据分析
q567315232 天前
从开发到部署深度解析Go与Python爬虫利弊
爬虫·python·golang