美文网首页
分布式爬虫总结和使用

分布式爬虫总结和使用

作者: 沉吟不语 | 来源:发表于2018-12-09 20:14 被阅读16次

    使用scrapy-redis:
    Scrapy_redis在scrapy的基础上实现了更多,更强大的功能,具体体现在:reqeust去重,爬虫持久化,和轻松实现分布式

    安装scrapy-redis:

    pip3 install scrapy-redis

    Scrapy-redis提供了下面四种组件(components):(意味着四个模块都要做相应的修改)

    1.Scheduler
    2.Duplication Filter
    3.Item Pipeline
    4.Base Spider

    scrapy-redis工作流程

    image.png

    Scheduler:

    Scrapy中跟“待爬队列”直接相关的就是调度器Scheduler,它负责对新的request进行入列操作(加入Scrapy queue),取出下一个要爬取的request(从Scrapy queue中取出)等操作。它把待爬队列按照优先级建立了一个字典结构.
    比如:
    {
    优先级0 : 队列0
    优先级1 : 队列1
    优先级2 : 队列2
    }
    然后根据request中的优先级,来决定该入哪个队列,出列时则按优先级较小的优先出列。为了管理这个比较高级的队列字典,Scheduler需要提供一系列的方法。但是原来的Scheduler已经无法使用,所以使用Scrapy-redis的scheduler组件。

    Duplication Filter:

    Scrapy中用集合实现这个request去重功能,Scrapy中把已经发送的request指纹放入到一个集合中,把下一个request的指纹拿到集合中比对,如果该指纹存在于集合中,说明这个request发送过了,如果没有则继续操作。这个核心的判重功能是这样实现的:

    def request_seen(self, request):
            # 把请求转化为指纹  
            fp = self.request_fingerprint(request)
            # 这就是判重的核心操作  ,self.fingerprints就是指纹集合
            if fp in self.fingerprints:
                return True  #直接返回
            self.fingerprints.add(fp) #如果不在,就添加进去指纹集合
            if self.file:
                self.file.write(fp + os.linesep)
    

    在scrapy-redis中去重是由Duplication Filter组件来实现的,它通过redis的set 不重复的特性,巧妙的实现了Duplication Filter去重。scrapy-redis调度器从引擎接受request,将request的指纹存⼊redis的set检查是否重复,并将不重复的request push写⼊redis的 request queue。

    Item Pipeline:

    引擎将(Spider返回的)爬取到的Item给Item Pipeline,scrapy-redis 的Item Pipeline将爬取到的 Item 存⼊redis的 items queue。
    修改过Item Pipeline可以很方便的根据 key 从 items queue 提取item,从⽽实现 items processes集群。

    Base Spider:

    不在使用scrapy原有的Spider类,重写的RedisSpider继承了Spider和RedisMixin这两个类,RedisMixin是用来从redis读取url的类。
    当我们生成一个Spider继承RedisSpider时,调用setup_redis函数,这个函数会去连接redis数据库,然后会设置signals(信号):
    一个是当spider空闲时候的signal,会调用spider_idle函数,这个函数调用schedule_next_request函数,保证spider是一直活着的状态,并且抛出DontCloseSpider异常。
    一个是当抓到一个item时的signal,会调用item_scraped函数,这个函数会调用schedule_next_request函数,获取下一个request。

    项目说明

    使用scrapy-redis的example来修改 先从github上拿到scrapy-redis的示例,然后将里面的example-project目录移到指定的地址:
    clone github scrapy-redis源码文件

    git clone https://github.com/rolando/scrapy-redis.git

    拿官方的项目范例:

    mv scrapy-redis/example-project ~/scrapyredis-project

    我们clone到的 scrapy-redis 源码中有自带一个example-project项目,这个项目包含3个spider,分别是dmoz, myspider_redis,mycrawler_redis。

    一.dmoz (class DmozSpider(CrawlSpider))
    注意:这里只用到Redis的去重和保存功能,并没有实现分布式

    这个爬虫继承的是CrawlSpider,它是用来说明Redis的持续性,当我们第一次运行dmoz爬虫,然后Ctrl + C停掉之后,再运行dmoz爬虫,之前的爬取记录是保留在Redis里的。

    分析起来,其实这就是一个 scrapy-redis 版 CrawlSpider 类,需要设置Rule规则,以及callback不能写parse()方法。 执行方式:

    scrapy crawl dmoz
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    
    
    class DmozSpider(CrawlSpider):
        """Follow categories and extract links."""
        name = 'dmoz'
        allowed_domains = ['dmoz.org']
        start_urls = ['http://www.dmoz.org/']
      
       #定义了一个url的提取规则,将满足条件的交给callback函数处理
        rules = [
            Rule(LinkExtractor(
                restrict_css=('.top-cat', '.sub-cat', '.cat-item')
            ), callback='parse_directory', follow=True),
        ]
    
        def parse_directory(self, response):
            for div in response.css('.title-and-desc'):
              #这里将获取到的内容交给引擎
                yield {
                    'name': div.css('.site-title::text').extract_first(),
                    'description': div.css('.site-descr::text').extract_first().strip(),
                    'link': div.css('a::attr(href)').extract_first(),
                }
    

    二.myspider_redis (class MySpider(RedisSpider))
    这个爬虫继承了RedisSpider, 它能够支持分布式的抓取,采用的是basic spider,需要写parse函数。 其次就是不再有start_urls了,取而代之的是redis_key,scrapy-redis将key从Redis里pop出来,成为请求的url地址。

    from scrapy_redis.spiders import RedisSpider
    
    
    class MySpider(RedisSpider):
        """Spider that reads urls from redis queue (myspider:start_urls)."""
        name = 'myspider_redis'
        #手动设置允许爬取的域
        allowed_domains = ['设置允许爬取的域']
        # 注意redis-key的格式:
        redis_key = 'myspider:start_urls'
    
        # 可选:等效于allowd_domains(),__init__方法按规定格式写,使用时只需要修改super()里的类名参数即可,一般不用
        def __init__(self, *args, **kwargs):
            # Dynamically define the allowed domains list.
            domain = kwargs.pop('domain', '')
            self.allowed_domains = filter(None, domain.split(','))
    
            # 修改这里的类名为当前类名
            super(MySpider, self).__init__(*args, **kwargs)
    
        def parse(self, response):
            return {
                'name': response.css('title::text').extract_first(),
                'url': response.url,
            }
    

    注意: RedisSpider类 不需要写start_urls:

    • scrapy-redis 一般直接写allowd_domains来指定需要爬取的域,也可以从在构造方法init()里动态定义爬虫爬取域范围(一般不用)。
    • 必须指定redis_key,即启动爬虫的命令,参考格式:redis_key = 'myspider:start_urls'
    • 根据指定的格式,start_urls将在 Master端的 redis-cli 里 lpush 到 Redis数据库里,RedisSpider 将在数据库里获取start_urls

    执行方式:
    1.通过runspider方法执行爬虫的py文件(也可以分次执行多条),爬虫(们)将处于等待准备状态:

    scrapy runspider myspider_redis.py

    or

    scrapy crawl myspider_redis

    2.在Master端的redis-cli输入push指令,参考格式(指定起始url):

    lpush myspider:start_urls http://www.dmoz.org/

    3.Slaver端爬虫获取到请求,开始爬取。

    三.mycrawler_redis (class MyCrawler(RedisCrawlSpider))
    这个RedisCrawlSpider类爬虫继承了RedisCrawlSpider,能够支持分布式的抓取。因为采用的是crawlSpider,所以需要遵守Rule规则,以及callback不能写parse()方法。
    同样也不再有start_urls了,取而代之的是redis_key,scrapy-redis将key从Redis里pop出来,成为请求的url地址。

    from scrapy.spiders import Rule
    from scrapy.linkextractors import LinkExtractor
    
    from scrapy_redis.spiders import RedisCrawlSpider
    
    
    class MyCrawler(RedisCrawlSpider):
    
        """Spider that reads urls from redis queue (myspider:start_urls)."""
    
        name = 'mycrawler_redis'
    
        allowed_domains = ['设置允许爬取的域']
        redis_key = 'mycrawler:start_urls'
    
        rules = (
            # follow all links
            Rule(LinkExtractor(), callback='parse_page', follow=True),
        )
    
        # __init__方法必须按规定写,使用时只需要修改super()里的类名参数即可(一般不用)
        def __init__(self, *args, **kwargs):
            # Dynamically define the allowed domains list.
            domain = kwargs.pop('domain', '')
            self.allowed_domains = filter(None, domain.split(','))
    
            # 修改这里的类名为当前类名
            super(MyCrawler, self).__init__(*args, **kwargs)
    
        def parse_page(self, response):
            return {
                'name': response.css('title::text').extract_first(),
                'url': response.url,
            }
    

    注意: 同样的,RedisCrawlSpider类不需要写start_urls:

    执行方式:

    1.scrapy runspider myspider_redis.py
    2.lpush myspider:start_urls http://www.dmoz.org/
    3.Slaver端爬虫获取到请求,开始爬取。

    总结:

    1 如果只是用到Redis的去重和保存功能,就选第一种;
    2 如果要写分布式,则根据情况,选择第二种、第三种;
    3 通常情况下,会选择用第三种方式编写深度聚焦爬虫。

    相关文章

      网友评论

          本文标题:分布式爬虫总结和使用

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