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. Python多线程 - threading

    目录 1. GIL 2. API 3. 创建子线程 4. 线程同步 4.1. 有了GIL,是否还需要同步? 4.1.1. 死锁 4.1.2. 竞争条件 4.1.3. GIL去哪儿了 4.2. Lock ...

  2. Anaconda 安装 以及conda使用

    下载 https://www.anaconda.com/distribution/#macos 管理 conda 版本查看 conda --version conda 版本更新 conda updat ...

  3. Android学习笔记点击事件和触摸事件的区别

    当我们点击手机屏幕的时候Android系统不仅会触发单击事件,还会触发触摸事件.在Android中它会先触发触摸事件,如果这个触摸事件没有被消费掉再去触发单击事件 代码示例: MainActivty. ...

  4. Day8-微信小程序实战-交友小程序-首页用户列表渲染及多账号调试及其点赞功能的实现

    在这之前已经把编辑个人的所有信息的功能已经完成了 之后先对首页的列表搞动态的,之前都是写死的静态 1.之前都是把好友写死的,现在就在js里面定义一个数组,用循环来动态的绑定 在onReady中定义,取 ...

  5. Shell脚本 概括

    Shell脚本的管理 shell 脚本是linux命令的集合 介于操作系统内核与用户之间,赋值解释命令行 Shell的作用及常见种类 登录Shell 指用户每次登录系统后自动加载的Shell程序,大多 ...

  6. JavaWeb网上图书商城完整项目--13.项目所需环境的搭建

    1.首先安装mysql 创建项目所需的数据库,直接运行项目提供的goods.sql文库 2.myeclipse创建一个web project ,项目的名称是goods 把视频中提供的项目原型下的提供的 ...

  7. jni 字符串的梳理

    1.实现的功能是java层传递一个字符串到c层2.c层首先将jstring类型转换成char*类型3.c层对字符串进行处理之后,将处理之后的char*类型转换成jstring类型返回给上层的 pack ...

  8. android面试详解

    前台就是和用户交互的进程 可见进程例如一个activity被一个透明的对话框覆盖,该activity就是可见进程 服务:service进程 后台一个activity按了home按键就是从前台退回到后台 ...

  9. mysql 出现You can't specify target table for update in FROM clause错误的解决方法

    mysql出现You can’t specify target table for update in FROM clause 这个错误的意思是不能在同一个sql语句中,先select同一个表的某些值 ...

  10. hello python week one

    python的注释 好的pytho程序员不一定非要追求 代码的简短,代码的可读性也是至关重要的 与用户交互 输入 input 会将输入内容转化为字符串 输出 print 格式化输出 %d %s 占位符 ...