美文网首页爬虫
聚焦Python分布式爬虫必学框架 Scrapy 打造搜索引擎

聚焦Python分布式爬虫必学框架 Scrapy 打造搜索引擎

作者: 江湖十年 | 来源:发表于2018-07-07 14:07 被阅读69次

    分布式爬虫要点

    image.png

    爬虫 A、B、C 分别放在三台服务器上,还需要一个 “状态管理器” 来对 URL 进行集中管理、去重等操作,它可以单独部署在一个服务器上面,也可以部署在 A、B、C 任何一台服务器上面

    image.png

    回顾一下 Scrapy 架构图

    Scrapy 架构图

    在一台机器上运行的爬虫中,SPIDER yield 出来的任何一条 REQUEST 都需要进入 SCHEDULER,然后 ENGINE 从 SCHEDULER 取 REQUEST 发送给 DOWNLOADER;还有关于 Scrapy 的去重,是放在内存中的一个 set 里面的。

    如果想将 Scrapy 改造成分布式,就会有两个问题必须要解决
    ①request 队列集中管理
    ② 去重集中管理

    image.png

    如何解决:
    ①在 Scrapy 中,SCHEDULER 实际上是放在一个 QUEUE(队列)中的,而这个 QUEUE 实际上就是放在内存中的,如果是分布式爬虫,其他服务器是拿不到当前服务器内存中的内容的,所以 Scrapy 就没法支持分布式,要想办法将这地方的 QUEUE 管理做成一种集中式的管理
    ②去重也是要做集中管理的,Scrapy 当中有一个去重的扩展,去重原理就是通过内存中的 set 来实现的,所以也没法做成分布式

    所以关注的重点就是,将 “去重的 set” 和 “REQUEST 队列 SCHEDULER” 这两个放到第三方的组件来做,这个第三方组件就是分布式爬虫的主角 Redis

    Redis 是基于内存的数据库,事实上也可以用 关系型数据库来做分布式,但是效率会很低

    redis基础知识

    Redis 菜鸟教程:http://www.runoob.com/redis/redis-tutorial.html
    Redis 中文文档:http://www.redis.cn/commands.html

    scrapy-redis编写分布式爬虫代码

    GitHub 地址:https://github.com/rmax/scrapy-redis

    需要安装库

    pip install redis
    

    Scrapy-Redis 的使用

    settings.py 中需要配置

    
    # 调度器(必须)
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    
    # 去重(必须)
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    
    # 用于将 ITEM 序列化并发送到 Redis(可选)
    ITEM_PIPELINES = {
        'scrapy_redis.pipelines.RedisPipeline': 300
    }
    
    

    所有的编写的 Spider 不再继承 scrapy.Spider,而要继承 RedisSpider

    
    from scrapy_redis.spiders import RedisSpider
    
    class MySpider(RedisSpider):
        name = 'myspider'
    
        def parse(self, response):
            # do stuff
            pass
    
    

    启动 Spider 命令不再是 scrapy crawl myspider,而是改成 scrapy runspider myspider.py ,注意要写 文件名 而不再是 爬虫名

    scrapy runspider myspider.py
    

    现在启动爬虫后,所有的 REQUEST 就不再是本地 SCHEDULER 来完成的,而是在 settings.py 中新增的 Scrapy-Redis 的 SCHEDULER SCHEDULER = "scrapy_redis.scheduler.Scheduler"

    启动 Spider 以后,必须要向队列里面放入一个初始化 URL,现在所有 URL 都是放在 Redis 当中的,所以初始化的时候,必须要向 Redis 中 push 一个起始 URL,这样才能够进行爬取

    redis-cli lpush myspider:start_urls http://google.com
    

    新建 Scrapy 项目 ScrapyRedisTest

     scrapy startproject ScrapyRedisTest
    
    image.png

    将 Scrapy-Redis 源码 clone 下来

    git clone https://github.com/rmax/scrapy-redis.git
    
    image.png

    将 clone 下来的 Scrapy-Redis 源码 复制到 新建的 ScrapyRedisTest 项目中

    image.png

    将 jobbole spider 改造成 基于 Scrapy-Redis 的分布式爬虫

    image.png image.png image.png image.png image.png image.png

    关于 RedisMixin 类中获取下一个 URL 的方法 next_requests

    Scrapy 中获取 next_requests 的时候是通过 Scrapy 的 SCHEDULER 来完成的,SCHEDULER 是维持一个 QUEUE,是放在内存当中的;现在 Scrapy-Redis 将这个 QUEUE 放到了 Redis 当中,使用 list 或 set 等来完成

    可以将原来的 jobbole spider 中 parse 方法逻辑完全拷贝过来

    # ScrapyRedisTest/spiders/jobbole.py
    
    from urllib.parse import urljoin
    
    import scrapy
    from scrapy_redis.spiders import RedisSpider
    
    
    class JobboleSpider(RedisSpider):
        name = 'jobbole'
        allowed_domains = ['jobbole.com']
        redis_key = 'jobbole:start_urls'
    
        def parse(self, response):
            """
                1. 提取文章列表页中所有文章详情页链接,并交给 parse_detail 方法进行解析
                2. 提取下一页链接,并交给 Scrapy 进行下载
            Args:
                response: 响应信息
            Yields:
                1. 文章详情页链接,交给 parse_detail 解析
                2. 下一页链接,交给 Scrapy 下载
            """
            post_nodes = response.xpath('//div[@id="archive"]')
            for post_node in post_nodes:
                post_url = post_node.xpath('.//div[@class="post-meta"]//a[@class="archive-title"]/@href').extract_first('')
                front_img_url = post_node.xpath('.//div[@class="post-thumb"]//img/@src').extract_first('')
                yield scrapy.Request(url=urljoin(response.url, post_url), callback=self.parse_detail,
                                     meta={'front_img_url': front_img_url})
            next_url = response.xpath('//a[@class="next page-numbers"]/@href').extract_first()
            if next_url:
                yield scrapy.Request(url=next_url, callback=self.parse)
    
        def parse_detail(self, response):
            pass
    
    

    因为这里只做演示,所以删除了很多逻辑,只有一个简单的 parse 方法

    接下来就是 settings.py 中的配置

    # ScrapyRedisTest/settings.py
    
    ROBOTSTXT_OBEY = False
    
    # Scrapy-Redis 相关配置
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    
    ITEM_PIPELINES = {
        'scrapy_redis.pipelines.RedisPipeline': 300
    }
    
    

    编写启动爬虫主文件 main.py

    image.png

    运行爬虫

    image.png

    向 Redis 里面 push 一个起始 URL

    image.png

    发现 spider 开始进行爬取

    image.png

    为了方便查看 Scrapy-Redis 爬虫的流程,可以在 jobbole.py 和 Scrapy-Redis 源码 scheduler.py 中分别打一个断点,进行调试检测

    image.png image.png

    DeBug 运行 spider,第一次停到断点这里实际上没有起始的 URL,此时向 Redis 中 push 第一个 URL,然后 f9 跳过这个请求,程序又会卡在这个断点

    image.png image.png

    此时查看 Redis,新增了一个 jobbole:requests 键,值为 ZSET 类型,为什么使用可排序的 ZSET 而不是 SET 或者 LIST 呢?因为默认情况下 jobbole:requests 是有优先级的,我们在程序中是可以设定优先级的,为了满足这个功能,必须使用可排序的 ZSET

    实际上,存到 Redis 里面的这个 request 是一个 Python 类,将这个类放到了 Redis 中,但是 Redis 中是不可能放 Python 类的,实际上这里存入 Redis 中是经过 Pickle 序列化的,将类序列化成字符串,存入 Redis,后续拿出来使用的时候可以反序列化成类

    image.png

    多按几次 f9,再次查看 Redis,多了一个 jobbole:dupefilter 键,这个键的值类型为 SET,因为是用于去重的,所以就没必要使用 ZSET

    image.png image.png

    scrapy-redis源码剖析-dupefilter.py、picklecompat.py

    image.png
    • dupefilter.py

    dupefilter.py 是用来去重的文件,Scrapy-Redis 的 dupefilter.py 源码几乎同 Scrapy 的 dupefilter.py 源码一模一样

    image.png image.png image.png

    Scrapy-Redis 在初始化的时候就生成了一个 server,这个 server 就连接到了 Redis,调用了 get_redis_from_settings 方法,这个方法是在 connection.py 中的

    image.png image.png image.png image.png
    • picklecompat.py

    picklecompat.py 实际上就是调用了 Python 的 pickle 库

    image.png

    scrapy-redis源码剖析- pipelines.py、 queue.py

    image.png
    • pipelines.py

    pipelines.py 文件中会将 ITEM 放到 Redis 当中

    image.png

    pipelines.py 文件中 RedisPipeline 类的初始化方法同样会初始化一个 server,这个 server 同样会调用 connection.py 中的 get_redis_from_settings 方法

    image.png image.png image.png image.png

    事实上,将数据存入 Redis 的过程的类 RedisPipeline 不是必须要配置到 settings.py 中的,实际上也可以在爬虫爬取完成之后直接存储到本地数据库中,设置保存到 Redis 当中的好处是,可以完成数据共享,将数据放到 Redis 当中就可以多起几个进程,甚至还可以写一个脚本(不是爬虫),直接从 Redis 中读取数据并将其存储到关系型数据库当中

    • queue.py

    queue.py 是供给 schedluer 调度来使用的,共有 3 个 Queue

    image.png

    FifoQueue 意思是:first in first out 的简写,先进先出,也就是有序队列

    image.png

    LifoQueue 意思是:last in first out 的简写,后进先出,类似于

    image.png

    PriorityQueue:默认使用的就是这个 Queue

    image.png image.png

    相关文章

      网友评论

        本文标题:聚焦Python分布式爬虫必学框架 Scrapy 打造搜索引擎

        本文链接:https://www.haomeiwen.com/subject/jdomyftx.html