您的位置:首页 > 其它

scrapy启动过程源码分析

2016-06-23 19:44 344 查看
看了上一节分析的
scrapy
架构,我们最想了解的,应该就是5大核心模块是怎么实现的吧。好,从github中找到各大核心模块的源码:

(1) Engine:scrapy/scrapy/core/engine.py

(2) Scheduler:scrapy/scrapy/core/scheduler.py

(3) Downloader:scrapy/scrapy/core/downloader/

(4) Spider:scrapy/scrapy/spider.py

(5) Item pipeline:scrapy/scrapy/pipelines/

这些模块,不是用class实现的,就是用package实现的。看模块代码顶多能了解它们的功能(成员函数),根本看不出scrapy是怎么运行的。那么问题来了,scrapy是怎么被启动并运行的?

一个scrapy程序的运行过程

从(1)中我们可以看到,我们自己写一个scrapy程序,这个程序的运行,是从命令
scrapy crawl hello
开始的。

那这条命令到底做了哪些事呢?

crawl
命令的运行

scrapy程序的源代码结构是非常清晰的,可以“猜”到
crawl
命令的代码在这里:

https://github.com/scrapy/scrapy/blob/master/scrapy/commands/crawl.py


看样子是用command模式写的,命令会执行
run()
中的代码,去掉异常处理部分,其实
crawl
命令最核心的代码就是下面两条:

self.crawler_process.crawl(spname, **opts.spargs)
self.crawler_process.start()


只要能找到
self.crawler_process.start()
的代码,就能搞清楚
crawl
命令的运行细节了。要搞明白这两条代码做了什么,必须知道
crawler_process
是什么。在github中搜一下这个
crawler_process
,发现

cmd.crawler_process = CrawlerProcess(settings)


继续搜
CrawlerProcess
(github的搜索功能用起来真是爽)。终于在
scrapy/scrapy/crawler.py
里发现了我们需要寻找的
CrawlerProcess.start()
函数。

CrawlerProcess.start()
的逻辑

通过上面的分析,scrapy程序的启动,就是执行了
self.crawler_process.start()
,跟进去发现其实是调用了
CrawlerProcess.start()
函数。从代码注释,可看到这个函数做了那么几件事:

(1) 初始化一个Twisted
reactor
对象

(2) 调整这个
reactor
对象的线程池大小

(3) 创建一个DNS缓存,并调整缓存大小

(4) 判断是否有其他爬虫没跑结束的,必须等所有爬虫跑结束才启动该爬虫

(5) 然后运行
reactor.run(installSignalHandlers=False)


然后这个
reactor
就run了,嗯?!这个
reactor
是什么鸟,它run啥内容了呢?

在这个
crawler.py
文件中看了一圈,也没发现往
reactor
中传入什么handler之类的参数。不过倒是发现shutdown/kill一个reactor是很容易的。

代码跟到这里,必须要看一下
Twisted
框架的
reactor
对象运行机制是怎么样的了。

Twisted
reactor
对象

Twisted
是python的异步网络编程框架。作为一个框架,它有自己的编程套路。这个套路,就是传说中的“异步编程模式/事件驱动模式”。 事件驱动的特点是包含一个事件循环(loop),当外部事件发生时用回调机制来触发相应的事件处理代码。

reactor
,就是I/O并发模型中“reactor模式”的实现。从(3)中可知,
reactor
就实现了这个事件环(loop)。

而最重要的是,
reactor
模式,实现了单线程环境中,调度多个事件源产生的事件到它们各自的事件处理例程中去。借助这个reactor模式,用单线程,就实现了事件处理机制(callback)。

总结一下,
reactor
只是一种设计模式,就是一个代码框架而已。真正的代码逻辑,应该在调用
reactor.run()
之前就搞定了的。所以想弄明白
CrawlerProcess.start()
,得看这个函数调用之前,做了哪些事。

能做啥事呢?也就是
CrawlerProcess
类,以及它父类的初始化。

CrawlerProcess类

CrawlerProcess
类的父类是
CrawlerRunner
类。看看它们的初始化工作都做了啥。

CrawlerRunner类

看它的注释,“keeps track of, manages and runs crawlers inside an already setup Twisted
reactor
”。看到了吧,它就是管理
reactor
中的各个爬虫的。

这个类的初始化,主要代码就是下面两行:

self.settings = settings
self.spider_loader = _get_spider_loader(settings)


加载配置,并根据配置加载
spider
。所谓加载
spider
,从
scrapy/scrapy/spiderloader.py
可以看出,就是加载各个
sipder
的属性(name等等)。

CrawlerProcess类

父类初始化后,子类
CrawlerProcess
才执行初始化。而
CrawlerProcess
类的功能,根据注释:run multiple scrapy crawlers in a process simultaneously。就是在单进程中跑多个爬虫(用twisted的reactor实现)。这个类里就实现了scrapy的“异步编程模式/事件驱动模式”。

它之所以叫xxxProcess,作者想说的就是“单进程”的意思吧。

这个类除了实现reactor模式,还添加了
log
shutdown
信号处理(Ctrl+C)功能。

总结

理一下,当我们运行
scrapy crawl hello
命令,就启动了一个scrapy爬虫,它的启动过程是这样的:

(1) 加载用户配置,加载所有
spider
(父类
CrawlerRunner
类初始化)

(2) 初始化
log
,与
shutdown
(Ctrl+C)信号处理机制(子类
CrawlerProcess
类初始化)

(3) 运行
CrawlerProcess.start()
,依次完成下面的逻辑:

(3.1) 初始化一个Twisted
reactor
对象,调整这个
reactor
对象的线程池大小

(3.2) 创建一个DNS缓存,并调整缓存大小

(3.3) 判断是否有其他爬虫没跑结束的,必须等所有爬虫跑结束才启动当前爬虫

(4) 然后运行
reactor.run()
。至此,第一步加载的
spider
,都放在reactor模式中运行了。

每个
spider
有自己的
name
start_urls
等属性。根据(4),scrapy会为每一个URL创建
scrapy.Request
对象,并将
spider
parse()
方法作为
scrapy.Request
对象的
callback
函数。

参考

(1) 简单scrapy程序的运行,https://github.com/ybdesire/WebLearn/tree/master/23_scrapy/hello_scrapy

(2) twisted.internet.reactor,https://twistedmatrix.com/documents/current/api/twisted.internet.reactor.html

(3) https://likebeta.gitbooks.io/twisted-intro-cn/content/zh/p03.html

(4) scrapy的运行,http://doc.scrapy.org/en/latest/intro/tutorial.html#what-just-happened-under-the-hood
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: