CrawlerProcess主进程

它控制了twisted的reactor,也就是整个事件循环。它负责配置reactor并启动事件循环,最后在所有爬取结束后停止reactor。
另外还控制了一些信号操作,使用户可以手动终止爬取任务。

此类在scrapy/crawler.py中定义,此模块有三个类:Crawler、CrawlerRunner和CrawlerProcess。
Crawler代表了一种爬取任务,里面使用一种spider,CrawlerProcess可以控制多个Crawler同时进行多种爬取任务。
CrawlerRunner是CrawlerProcess的父类,CrawlerProcess通过实现start方法来启动一个Twisted的reactor(另有shutdown信号处理、顶层logging功能)。

CrawlerProcess初始化

首先在命令行启动调用crawl()和start()运行之前,就已经建立了CrawlerProcess对象。

scrapy/crawler.py#CrawlerProcess:

class CrawlerProcess(CrawlerRunner):
def __init__(self, settings=None, install_root_handler=True):
super(CrawlerProcess, self).__init__(settings)
install_shutdown_handlers(self._signal_shutdown)
configure_logging(self.settings, install_root_handler)
log_scrapy_info(self.settings)

初始化动作有:
1.使用settings初始化父类CrawlerRunner,只是定义了一些空变量。
2.注册shutdown信号。
3.配置顶层logging。

CrawlerProcess.crawl()创建Crawler对象

在运行前调用了crawl()方法。

scrapy/crawler.py#CrawlerRunner:

    def crawl(self, crawler_or_spidercls, *args, **kwargs):
crawler = self.create_crawler(crawler_or_spidercls)
return self._crawl(crawler, *args, **kwargs) def _crawl(self, crawler, *args, **kwargs):
self.crawlers.add(crawler)
d = crawler.crawl(*args, **kwargs)
self._active.add(d)
def _done(result):
self.crawlers.discard(crawler)
self._active.discard(d)
return result
return d.addBoth(_done) def create_crawler(self, crawler_or_spidercls):
if isinstance(crawler_or_spidercls, Crawler):
return crawler_or_spidercls
return self._create_crawler(crawler_or_spidercls) def _create_crawler(self, spidercls):
if isinstance(spidercls, six.string_types):
spidercls = self.spider_loader.load(spidercls)
return Crawler(spidercls, self.settings)

这里需要分清crawler对象和spider对象:
spider是程序员编写的爬虫代码模块,一般是存放在项目里spiders文件夹内,并给每个爬虫模块赋予独立的名称,命令行启动时通过不同的名称启动不同的spider;
crawler是爬取任务,每次在命令行启动,都会新建一个新的crawler爬取任务,可以为同一个spider新建多个crawler,表现在命令里就是同样的命令可以重复执行多次,同一个spider对应的多个crawler共同占有同样的私有配置、同一个任务队列。

crawl()方法执行的动作有:
1.如果crawler_or_spidercls(命令行输入的spider名称)是一个Spider的子类(已经运行)则创建一个新的Crawler,如果crawler_or_spidercls是一个字符串(未运行),则根据名称来查找对应的spider并创建一个Crawler实例并执行Crawler的初始化。
2.返回一个Deferred对象给CrawlerProcess,把Deferred对象加入_active集合,然后就可以在必要时结束Crawler,并通过向Deferred中添加_done callback来跟踪一个Crawler的结束。

Crawler对象初始化

scrapy/crawler.py#Crawler:

class Crawler(object):

    def __init__(self, spidercls, settings=None):
if isinstance(settings, dict) or settings is None:
settings = Settings(settings) self.spidercls = spidercls
self.settings = settings.copy()
self.spidercls.update_settings(self.settings) d = dict(overridden_settings(self.settings))
logger.info("Overridden settings: %(settings)r", {'settings': d}) self.signals = SignalManager(self)
self.stats = load_object(self.settings['STATS_CLASS'])(self) handler = LogCounterHandler(self, level=self.settings.get('LOG_LEVEL'))
logging.root.addHandler(handler)
if get_scrapy_root_handler() is not None:
# scrapy root handler already installed: update it with new settings
install_scrapy_root_handler(self.settings)
# lambda is assigned to Crawler attribute because this way it is not
# garbage collected after leaving __init__ scope
self.__remove_handler = lambda: logging.root.removeHandler(handler)
self.signals.connect(self.__remove_handler, signals.engine_stopped) lf_cls = load_object(self.settings['LOG_FORMATTER'])
self.logformatter = lf_cls.from_crawler(self)
self.extensions = ExtensionManager.from_crawler(self) self.settings.freeze()
self.crawling = False
self.spider = None
self.engine = None

动作:
1.导入spider的custom_settings;
2.添加SignalManager(利用开源的python库pydispatch作消息的发送和路由, scrapy使用它发送关键的消息事件给关心者,如爬取开始,爬取结束等消息。通过send_catch_log_deferred来发送消息,通过connect方法来注册消息的处理函数);
3.添加ExtensionManager。

Crawler.crawl()创建spider对象

scrapy/crawler.py#Crawler:

    @defer.inlineCallbacks
def crawl(self, *args, **kwargs):
assert not self.crawling, "Crawling already taking place"
self.crawling = True
try:
self.spider = self._create_spider(*args, **kwargs)
self.engine = self._create_engine()
start_requests = iter(self.spider.start_requests())
yield self.engine.open_spider(self.spider, start_requests)
yield defer.maybeDeferred(self.engine.start)
except Exception:
if six.PY2:
exc_info = sys.exc_info()
self.crawling = False
if self.engine is not None:
yield self.engine.close()
if six.PY2:
six.reraise(*exc_info)
raise

调用Crawler的crawl方法(这里使用了Twisted的defer.inlineCallbacks装饰器,表明此函数非阻塞,异步执行)开启一个爬取任务,通过调用spider的from_crawler方法来创建一个spider对象,这样,许多spider类都可以使用crawler的关键方法和数据,属于依赖注入。
spider的代码由程序员自己编写,不同的爬虫类除了调用父类的from_crawler外,可以重定义这个方法来实现个性化实现。
典型写法为:

    @classmethod
def from_crawler(cls, crawler):
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s

Crawler.crawl()创建ExecutionEngine执行引擎以及对应的调度器、下载器、刮取器
继续向下执行:
1.self.engine = self._create_engine(),创建一个ExecutionEngine执行引擎;
ExecutionEngine的初始化代码为:
scrapy/core/engine.py#ExecutionEngine:

class ExecutionEngine(object):

    def __init__(self, crawler, spider_closed_callback):
self.crawler = crawler
self.settings = crawler.settings
self.signals = crawler.signals
self.logformatter = crawler.logformatter
self.slot = None
self.spider = None
self.running = False
self.paused = False
self.scheduler_cls = load_object(self.settings['SCHEDULER'])
downloader_cls = load_object(self.settings['DOWNLOADER'])
self.downloader = downloader_cls(crawler)
self.scraper = Scraper(crawler)
self._spider_closed_callback = spider_closed_callback

操作有:使用crawler的信号管理器,用来发送注册消息;根据配置加载调度类模块,默认是scrapy.core.scheduler.Scheduler;根据配置加载下载类模块,并创建一个对象,默认是scrapy.core.downloader.Downloade;创建一个Scraper刮取器,主要是用来处理下载后的结果并存储提取的数据。
2.start_requests = iter(self.spider.start_requests()),获取spider中的start_requests;
3.yield self.engine.open_spider(self.spider, start_requests),调用执行引擎打开spider;
4.yield defer.maybeDeferred(self.engine.start),启动执行引擎。此时仍然并未真正开始爬取,仍然是CrawlerProcess.start()之前的预处理步骤。

ExecutionEngine的详细内容将在下一篇讲解。

CrawlerProcess.start()

正式运行。

scrapy/crawler.py#CrawlerProcess:

    def start(self, stop_after_crawl=True):
if stop_after_crawl:
d = self.join()
# Don't start the reactor if the deferreds are already fired
if d.called:
return
d.addBoth(self._stop_reactor) reactor.installResolver(self._get_dns_resolver())
tp = reactor.getThreadPool()
tp.adjustPoolsize(maxthreads=self.settings.getint('REACTOR_THREADPOOL_MAXSIZE'))
reactor.addSystemEventTrigger('before', 'shutdown', self.stop)
reactor.run(installSignalHandlers=False) # blocking call

1.此函数首先调用join函数来对前面所有Crawler的crawl方法返回的Deferred对象添加一个_stop_reactor方法,当所有Crawler对象都结束时用来关闭reactor。
2.reactor.installResolver安装一个dns缓存。
3.根据配置调整reactor的线程池。
4.添加结束事件的监听。
5.启动reactor事件循环,标志着所有爬虫正式运行,如果没有手动结束,就只会在所有爬虫全部爬取完成后才会自动结束。

————————————————
版权声明:本文为CSDN博主「csdn_yym」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/csdn_yym/java/article/details/85423656

scrapy 源码解析 (二):启动流程源码分析(二) CrawlerProcess主进程的更多相关文章

  1. Spring IOC容器启动流程源码解析(四)——初始化单实例bean阶段

    目录 1. 引言 2. 初始化bean的入口 3 尝试从当前容器及其父容器的缓存中获取bean 3.1 获取真正的beanName 3.2 尝试从当前容器的缓存中获取bean 3.3 从父容器中查找b ...

  2. Android Activity启动流程源码全解析(1)

    前言 Activity是Android四大组件的老大,我们对它的生命周期方法调用顺序都烂熟于心了,可是这些生命周期方法到底是怎么调用的呢?在启动它的时候会用到startActivty这个方法,但是这个 ...

  3. Android Activity启动流程源码全解析(2)

    接上之前的分析 ++Android Activity启动流程源码全解析(1)++ 1.正在运行的Activity调用startPausingLocked 一个一个分析,先来看看startPausing ...

  4. 【图解源码】Zookeeper3.7源码分析,包含服务启动流程源码、网络通信源码、RequestProcessor处理请求源码

    Zookeeper3.7源码剖析 能力目标 能基于Maven导入最新版Zookeeper源码 能说出Zookeeper单机启动流程 理解Zookeeper默认通信中4个线程的作用 掌握Zookeepe ...

  5. Spark(五十一):Spark On YARN(Yarn-Cluster模式)启动流程源码分析(二)

    上篇<Spark(四十九):Spark On YARN启动流程源码分析(一)>我们讲到启动SparkContext初始化,ApplicationMaster启动资源中,讲解的内容明显不完整 ...

  6. Spark(四十九):Spark On YARN启动流程源码分析(一)

    引导: 该篇章主要讲解执行spark-submit.sh提交到将任务提交给Yarn阶段代码分析. spark-submit的入口函数 一般提交一个spark作业的方式采用spark-submit来提交 ...

  7. Spring IOC 容器预启动流程源码探析

    Spring IOC 容器预启动流程源码探析 在应用程序中,一般是通过创建ClassPathXmlApplicationContext或AnnotationConfigApplicationConte ...

  8. scrapy 源码解析 (三):启动流程源码分析(三) ExecutionEngine执行引擎

    ExecutionEngine执行引擎 上一篇分析了CrawlerProcess和Crawler对象的建立过程,在最终调用CrawlerProcess.start()之前,会首先建立Execution ...

  9. Spring Boot的自动配置原理及启动流程源码分析

    概述 Spring Boot 应用目前应该是 Java 中用得最多的框架了吧.其中 Spring Boot 最具特点之一就是自动配置,基于Spring Boot 的自动配置,我们可以很快集成某个模块, ...

随机推荐

  1. redis 深入理解redis 主从复制原理

    redis 主从复制 master 节点提供数据,也就是写.slave 节点负责读. 不是说master 分支不能读数据,也能只是我们希望将读写进行分离. slave 是不能写数据的,只能处理读请求 ...

  2. <VCC笔记>VCC简介与安装

    最近在学校跟着老师参与了一个代码验证的工作,需要使用Microsoft Research(微软学术)开发的VCC工具,是开源的,托管在Codeplex上.这东西英语资料极其少,中文资料基本没有.我只能 ...

  3. HttpClient 常用方法封装

    简介 在平时写代码中,经常需要对接口进行访问,对于 http 协议 rest 风格的接口请求,大多使用 HttpClient 工具进行编写,想着方便就寻思着把一些常用的方法进行封装,便于平时快速的使用 ...

  4. nacos基础--客户端下载

    对于nacos的作用,我在这里不在过多介绍,不知道的同学可以自行先了解,对于nacos,有官网进行介绍,对于一个初学者来说是一件非常方便的事情. 官网地址:https://nacos.io 但是在下载 ...

  5. Water Testing【皮克定理,多边形面积,线段上点的数目】

    Water Testing 传送门:链接  来源:UPC 9656 题目描述 You just bought a large piece of agricultural land, but you n ...

  6. 面试:在面试中关于List(ArrayList、LinkedList)集合会怎么问呢?你该如何回答呢?

    前言 在一开始基础面的时候,很多面试官可能会问List集合一些基础知识,比如: ArrayList默认大小是多少,是如何扩容的? ArrayList和LinkedList的底层数据结构是什么? Arr ...

  7. Linux系统管理——初学者建议

    学习Linux的注意事项(一) Linux严格区分大小写 Linux是严格区分大小写的,这一点和Windows不一样,所以操作时要注意区分大小写的不同,包括文件名和目录名.命令.命令选项.配置文件配置 ...

  8. 恕我直言你可能真的不会java第1篇:lambda表达式会用了么?

    本文配套教学视频:B站观看地址 在本号之前写过的一些文章中,笔者使用了lambda表达式语法,一些读者反映说代码看不懂.本以为java 13都已经出了,java 8中最重要特性lambda表达式大家应 ...

  9. css3动画的实例讲解

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  10. cb16a_c++_顺序容器的选用_排序_二分查找

    /*cb16a_c++_顺序容器的选用_排序_二分查找顺序容器: 1.vector的优点与缺点 vector优点:排序利用下标,快速排序,做二分查找非常快 2.list的优点与缺点 list优点:插入 ...