scrapy启动过程源码分析

看了上一节分析的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模式,还添加了logshutdown信号处理(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有自己的namestart_urls等属性。根据(4),scrapy会为每一个URL创建scrapy.Request对象,并将spiderparse()方法作为scrapy.Request对象的callback函数。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值