ITPub博客

首页 > 数据库 > 数据库开发技术 > scrapy限制同时运行爬虫数量

scrapy限制同时运行爬虫数量

原创 数据库开发技术 作者:kanadeblisst 时间:2020-07-31 15:03:10 0 删除 编辑

假设需求

现在大概有一千多个scrapy的爬虫文件,只有一台机器,怎么执行爬虫。自定义命令运行所有爬虫很简单,但是这在硬件资源上的消耗会很大,机器的带宽和内存会直接拉满,这会导致很多爬虫无法正常运行,所以我们需要限制每同时运行的爬虫数量。比如我一次只启动20个爬虫,当这20个爬虫的某一个运行完成了在接着启动下一个爬虫。

实现方法

只需要创建一个队列来保存爬虫的运行状态,队列里存放待抓取的爬虫(通常会创建三个,分别是pending(待抓取)、running(正抓取)、finished(已抓取))。然后取指定数量的爬虫运行,当其中一个运行完,在去任务队列里取,直到队列空了。

实现

队列使用Redis的集合即可,我们创建pending、running、finished三个集合,然后将所有的爬虫的name字段存放在pending集合中(当然爬虫文件名也可以,只是启动爬虫的方式稍微不一样),接着我们就可以写个脚本来运行爬虫了。方式有两种:

一、crawl命令

伪代码:

pending.add(所有爬虫)
while pending:
 if  len(running) < 指定数量:
  spider = pending.pop()
  开启进程启动爬虫
 else:
  time.sleep(指定时间)

接着再编写一个extension来同步爬虫状态到Redis里,看到以下的代码可能会有疑问,为什么使用extension而不是用中间件,在中间件里同样可以操作信号量。因为中间件的主要作用并不在信号量,像这种需求根据官方的推荐写在extension里会更好,不过这只是一种规范问题,并不是强制的。

class SpiderCountLimit:

    def __init__(self, count):
        self.spider_count = count
        self.r = redis.Redis(decode_responses=True)

    @classmethod
    def from_crawler(cls, crawler):
        count = crawler.settings.get('SPIDER_COUNT', 20)
        ext = cls(count)
        crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed)
        crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened)
        return ext

    def spider_closed(self, spider, reason):
        self.r.srem('running', spider.name) # 爬虫关闭时,删除running中的爬虫
        self.r.sadd('finished', spider.name) # 加入到已完成队列

    def spider_opened(self, spider):
        self.r.sadd('running', spider.name) # 爬虫开启时,添加爬虫到running中

二、Crawler API

伪代码:不懂怎么用的Crawler API可以看另一篇文章:scrapy 自定义命令

while pending:
 if  len(running) < 指定数量:
  多进程执行:
   crawler_process.crawl(pending.pop())
   crawler_process.start()
 time.sleep(指定时间)

因为crawler_process.start()这个语句是阻塞的,所以需要多进程来执行。本来想可不可以通过以下代码来启动爬虫呢?在实测中发现有报错,错误大概是reactor已经启动。

class SpiderCountLimit:

    def __init__(self, count):
        self.spider_count = count
        self.r = redis.Redis(decode_responses=True)

    @classmethod
    def from_crawler(cls, crawler):
        count = crawler.settings.get('SPIDER_COUNT', 20)
        ext = cls(count)
        crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed)
        crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened)
        return ext

    def spider_closed(self, spider, reason):
        self.r.srem('running', spider.name)
        self.r.sadd('finished', spider.name)

        spider = self.r.spop('pending')
        process = CrawlerProcess()
        process.crawl(spider)
        process.start() 
        
    def spider_opened(self, spider):
        self.r.sadd('running', spider.name)

其实这个代码本身就有点问题,上面提到process.start() 是阻塞的,也就是说spider_closed这个方法一直没有结束,这也可能会带来一些无法预见的问题。

后面又想用进程池来管理进程,实测发现同样存在问题。最开始加入进程池的任务正常执行了,后面的爬虫任务虽然被添加到池中但是没有真正运行就终止了,就好像走了个过场一样,也并没有报错,不过这也可能是我用的姿势不对,可以自己尝试一下。


原文作者:Python成长路;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2019-05-15

  • 博文量
    117
  • 访问量
    67426