• 设计一个网络爬虫(Python)


    第 1 步:概述用例和约束

    收集需求并确定问题的范围。提出问题以澄清用例和约束。讨论假设。

    如果没有面试官来解决澄清问题,我们将定义一些用例和约束。

    用例

    我们将问题范围限定为仅处理以下用例

    • 服务抓取 url 列表:
      • 生成包含搜索词的页面的反向索引
      • 为页面生成标题和片段
        • 标题和片段是静态的,它们不会根据搜索查询而改变
    • 用户输入搜索词并查看相关页面列表,其中包含爬虫生成的标题和片段
      • 仅为此用例绘制高级组件和交互,无需深入
    • 服务具有高可用性

    超出范围

    • 搜索分析
    • 个性化搜索结果
    • 网页排名

    约束和假设

    状态假设

    • 流量分布不均
      • 有些搜索很受欢迎,而有些搜索只执行一次
    • 仅支持匿名用户
    • 生成搜索结果应该很快
    • 网络爬虫不应陷入无限循环
      • 如果图包含一个循环,我们就会陷入无限循环
    • 10 亿个要抓取的链接
      • 页面需要定期爬取,保证新鲜度
      • 平均刷新率约为每周一次,对于热门网站更频繁
        • 每月抓取 40 亿个链接
      • 每个网页的平均存储大小:500 KB
        • 为简单起见,计数更改与新页面相同
    • 每月 1000 亿次搜索

    练习使用更传统的系统——不要使用现有的系统,例如solrnutch

    计算用量

    如果您应该进行粗略的使用计算,请与您的面试官澄清。

    • 每月存储 2 PB 的页面内容
      • 每页 500 KB * 每月抓取 40 亿个链接
      • 3 年内存储 72 PB 的页面内容
    • 每秒 1,600 个写入请求
    • 每秒 40,000 个搜索请求

    方便的转换指南:

    • 每月 250 万秒
    • 每秒 1 个请求 = 每月 250 万个请求
    • 每秒 40 个请求 = 每月 1 亿个请求
    • 每秒 400 个请求 = 每月 10 亿个请求

    第 2 步:创建高级设计

    概述包含所有重要组件的高级设计。

     

    第三步:设计核心组件

    深入了解每个核心组件的细节。

    用例:服务抓取 url 列表

    我们假设我们有一个links_to_crawl最初基于整体网站受欢迎程度的排名的初始列表。如果这不是一个合理的假设,我们可以使用链接到外部内容(如YahooDMOZ等)的流行网站为爬虫播种。

    我们将使用一个表crawled_links来存储处理后的链接及其页面签名。

    我们可以将links_to_crawl和存储crawled_links在键值NoSQL 数据库中。对于 中的排名链接links_to_crawl,我们可以使用带有排序集的Redis来维护页面链接的排名。我们应该讨论选择 SQL 或 NoSQL 之间的用例和权衡

    • 爬虫服务通过循环执行以下操作来处理每个页面链接:
      • 抓取排名靠前的页面链接
        • crawled_linksNoSQL 数据库中检查具有相似页面签名的条目
          • 如果我们有相似的页面,降低页面链接的优先级
            • 这可以防止我们进入一个循环
            • 继续
          • 否则,抓取链接
            • 将作业添加到反向索引服务队列以生成反向索引
            • 将作业添加到文档服务队列以生成静态标题和片段
            • 生成页面签名
            • 从NoSQL 数据库links_to_crawl中删除链接
            • crawled_linksNoSQL 数据库中插入页面链接和签名

    与你的面试官澄清你需要写多少代码

    PagesDataStore是使用NoSQL 数据库的爬虫服务中的抽象:

    1. class PagesDataStore(object):
    2. def __init__(self, db);
    3. self.db = db
    4. ...
    5. def add_link_to_crawl(self, url):
    6. """Add the given link to `links_to_crawl`."""
    7. ...
    8. def remove_link_to_crawl(self, url):
    9. """Remove the given link from `links_to_crawl`."""
    10. ...
    11. def reduce_priority_link_to_crawl(self, url)
    12. """Reduce the priority of a link in `links_to_crawl` to avoid cycles."""
    13. ...
    14. def extract_max_priority_page(self):
    15. """Return the highest priority link in `links_to_crawl`."""
    16. ...
    17. def insert_crawled_link(self, url, signature):
    18. """Add the given link to `crawled_links`."""
    19. ...
    20. def crawled_similar(self, signature):
    21. """Determine if we've already crawled a page matching the given signature"""
    22. ...

    PageCrawler Service中的一个抽象,它封装了页面、其内容、子 url 和签名:

     
    1. class Page(object):
    2. def __init__(self, url, contents, child_urls, signature):
    3. self.url = url
    4. self.contents = contents
    5. self.child_urls = child_urls
    6. self.signature = signature

    CrawlerCrawler Service中的主要类,由Page和组成PagesDataStore

    1. class Crawler(object):
    2. def __init__(self, data_store, reverse_index_queue, doc_index_queue):
    3. self.data_store = data_store
    4. self.reverse_index_queue = reverse_index_queue
    5. self.doc_index_queue = doc_index_queue
    6. def create_signature(self, page):
    7. """Create signature based on url and contents."""
    8. ...
    9. def crawl_page(self, page):
    10. for url in page.child_urls:
    11. self.data_store.add_link_to_crawl(url)
    12. page.signature = self.create_signature(page)
    13. self.data_store.remove_link_to_crawl(page.url)
    14. self.data_store.insert_crawled_link(page.url, page.signature)
    15. def crawl(self):
    16. while True:
    17. page = self.data_store.extract_max_priority_page()
    18. if page is None:
    19. break
    20. if self.data_store.crawled_similar(page.signature):
    21. self.data_store.reduce_priority_link_to_crawl(page.url)
    22. else:
    23. self.crawl_page(page)

    处理重复项

    我们需要小心网络爬虫不会陷入无限循环,当图形包含循环时会发生这种情况。

    与你的面试官澄清你需要写多少代码

    我们要删除重复的网址:

    • 对于较小的列表,我们可以使用类似sort | unique
    • 如果要抓取 10 亿个链接,我们可以使用MapReduce仅输出频率为 1 的条目
    1. class RemoveDuplicateUrls(MRJob):
    2. def mapper(self, _, line):
    3. yield line, 1
    4. def reducer(self, key, values):
    5. total = sum(values)
    6. if total == 1:
    7. yield key, total

    检测重复内容更加复杂。我们可以根据页面内容生成一个签名,并比较这两个签名的相似性。一些潜在的算法是Jaccard 索引余弦相似度

    确定何时更新爬网结果

    需要定期抓取页面以确保新鲜度。爬网结果可能有一个timestamp字段,指示上次爬网页面的时间。在默认时间段之后,比如一周,所有页面都应该被刷新。经常更新或更受欢迎的网站可以在更短的时间间隔内刷新。

    虽然我们不会深入分析分析的细节,但我们可以进行一些数据挖掘来确定特定页面更新之前的平均时间,并使用该统计数据来确定重新抓取页面的频率。

    我们也可以选择支持Robots.txt让网站管理员控制抓取频率的文件。

    用例:用户输入一个搜索词并查看包含标题和片段的相关页面列表

    • 客户端向Web 服务器发送请求,作为反向代理运行
    • Web 服务器将请求转发到查询 API服务器
    • 查询 API服务器执行以下操作 :
      • 解析查询
        • 删除标记
        • 将文本分解为术语
        • 修正错别字
        • 规范大写
        • 将查询转换为使用布尔运算
      • 使用反向索引服务查找与查询匹配的文档
        • 反向索引服务对匹配结果进行排名并返回排名靠前的结果
      • 使用文档服务返回标题和片段
  • 相关阅读:
    【算法练习Day18】二叉搜索树的最小绝对差&&二叉搜索树中的众数&& 二叉树的最近公共祖先
    2核4G服务器 如何设计编码一款扛得住高并发高吞吐量的商品秒杀系统
    FPGA UDP RGMII 千兆以太网(1)
    android源码学习-android异常处理机制
    React生命周期和响应式原理(Fiber架构)
    物理验证LVS流程和技术点滴(上)
    微信小程序-页面导航-导航传参
    微服务01-基本介绍+注册中心EureKa
    2024上海国际人工智能展(CSITF)“创新驱动发展·科技引领未来”
    Android查看签名信息系列 · 使用逆向分析工具JadxGUI获取签名
  • 原文地址:https://blog.csdn.net/sikh_0529/article/details/126846817