scrapy 源码解析 (二):启动流程源码分析(二) CrawlerProcess主进程
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主进程的更多相关文章
- Spring IOC容器启动流程源码解析(四)——初始化单实例bean阶段
目录 1. 引言 2. 初始化bean的入口 3 尝试从当前容器及其父容器的缓存中获取bean 3.1 获取真正的beanName 3.2 尝试从当前容器的缓存中获取bean 3.3 从父容器中查找b ...
- Android Activity启动流程源码全解析(1)
前言 Activity是Android四大组件的老大,我们对它的生命周期方法调用顺序都烂熟于心了,可是这些生命周期方法到底是怎么调用的呢?在启动它的时候会用到startActivty这个方法,但是这个 ...
- Android Activity启动流程源码全解析(2)
接上之前的分析 ++Android Activity启动流程源码全解析(1)++ 1.正在运行的Activity调用startPausingLocked 一个一个分析,先来看看startPausing ...
- 【图解源码】Zookeeper3.7源码分析,包含服务启动流程源码、网络通信源码、RequestProcessor处理请求源码
Zookeeper3.7源码剖析 能力目标 能基于Maven导入最新版Zookeeper源码 能说出Zookeeper单机启动流程 理解Zookeeper默认通信中4个线程的作用 掌握Zookeepe ...
- Spark(五十一):Spark On YARN(Yarn-Cluster模式)启动流程源码分析(二)
上篇<Spark(四十九):Spark On YARN启动流程源码分析(一)>我们讲到启动SparkContext初始化,ApplicationMaster启动资源中,讲解的内容明显不完整 ...
- Spark(四十九):Spark On YARN启动流程源码分析(一)
引导: 该篇章主要讲解执行spark-submit.sh提交到将任务提交给Yarn阶段代码分析. spark-submit的入口函数 一般提交一个spark作业的方式采用spark-submit来提交 ...
- Spring IOC 容器预启动流程源码探析
Spring IOC 容器预启动流程源码探析 在应用程序中,一般是通过创建ClassPathXmlApplicationContext或AnnotationConfigApplicationConte ...
- scrapy 源码解析 (三):启动流程源码分析(三) ExecutionEngine执行引擎
ExecutionEngine执行引擎 上一篇分析了CrawlerProcess和Crawler对象的建立过程,在最终调用CrawlerProcess.start()之前,会首先建立Execution ...
- Spring Boot的自动配置原理及启动流程源码分析
概述 Spring Boot 应用目前应该是 Java 中用得最多的框架了吧.其中 Spring Boot 最具特点之一就是自动配置,基于Spring Boot 的自动配置,我们可以很快集成某个模块, ...
随机推荐
- 关于wifi营销的看过来
亲测可用.对于一个开发者来说,终于如获至宝.详情联系qq2455994690.源码可二开.包括微信一键关注上网,手机验证码上网.
- Redis学习笔记(二十) 发布订阅(下)
当一个客户端执行SUBSCRIBE命令订阅某个或某些频道时,这个客户端与被订阅频道之间就建立起了一种订阅关系. Redis将所有频道的订阅关系保存在服务器状态的pubsub_channels字典里面, ...
- ODBC 常见数据源配置整理
目录 1. 简介 1.1 ODBC和JDBC 1.2 ODBC配置工具 1.3 ODBC 数据源连接配置 2. MySQL 数据源配置 2.1 配置步骤 2.2 链接参数配置 3. SQLServer ...
- Git执行"git rebase -i HEAD~xxx"报错:git rebase fatal: Needed a single revision invalid upstream –i
一.问题"git rebase -i HEAD~xxx"是修改git 历史记录的一个很有用的命令. 但是有时在执行该命令时会出现以下错误:$ git rebase –i HEAD~ ...
- MapReduce 论文阅读笔记
Abstract MapReduce : programming model 编程模型 an associated implementation for processing and generati ...
- Java Agent(上)
1.java agent是什么? --->对用户透明,不侵入用户业务代码.让java虚拟机加载agent jar 2.java agent有什么用? --->应用场景例如:调用链追踪项目, ...
- IOC和DI的概念,以及Spring框架的介绍
对于Java开发者来说,Spring肯定是一个避不开的技术.所以准备系统的学下Spring框架. 我给自己设计的学习路线是这样的:首先阅读下Spring的官方文档(注意Spring官网上有很多项目,S ...
- MySQL 8.0 主从同步
一.简介 一台服务器充当主数据库服务器,另一台或多台服务器充当从数据库服务器,主服务器中的数据自动复制到从服务器之中.MySQL主从复制的基础是主服务器对数据库修改记录二进制日志,从服务器通过主服务器 ...
- 洛谷 P2212 【[USACO14MAR]Watering the Fields S】
一道最小生成树模板题,这里用的Kruskal算法,把每两点就加一条边,跑一遍最小生成树即可. #include <bits/stdc++.h> using namespace std; s ...
- ADAS感知设计
ADAS传感器融合 0.传感器标定 首先标定传感器.一般可以精度高的传感标定用精度低一个数量级的传感器,如用激光雷达标定毫米波雷达. 毫米波雷达标定:可以采用激光雷达对毫米波雷达进行标定.选取一个纹理 ...