微信搜索关注「水滴与银弹」公众号,第一时间获取优质技术干货。7年资深后端研发,用简单的方式把技术讲清楚。
在上一篇文章:Scrapy 源码剖析(二)Scrapy 是如何运行起来的?,我们主要剖析了 Scrapy 是如何运行起来的核心逻辑,也就是在真正执行抓取任务之前,Scrapy 都做了哪些工作。
这篇文章,我们就来进一步剖析一下,Scrapy 有哪些核心组件?以及它们主要负责了哪些工作?这些组件为了完成这些功能,内部又是如何实现的。
爬虫类
我们接着上一篇结束的地方开始讲起。上次讲到 Scrapy 运行起来后,执行到最后到了 Crawler
的 crawl
方法,我们来看这个方法:
@defer.inlineCallbacks
def crawl(self, *args, **kwargs):
assert not self.crawling, "Crawling already taking place"
self.crawling = True
try:
# 从spiderloader中找到爬虫类 并实例化爬虫实例
self.spider = self._create_spider(*args, **kwargs)
# 创建引擎
self.engine = self._create_engine()
# 调用爬虫类的start_requests方法 拿到种子URL列表
start_requests = iter(self.spider.start_requests())
# 执行引擎的open_spider 并传入爬虫实例和初始请求
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
实例化时,会创建 SpiderLoader
,它会根据我们定义的配置文件 settings.py
找到存放爬虫的位置,我们写的爬虫代码都在这里。
然后 SpiderLoader
会扫描这些代码文件,并找到父类是 scrapy.Spider
爬虫类,然后根据爬虫类中的 name
属性(在编写爬虫时,这个属性是必填的),生成一个 {spider_name: spider_cls}
的字典,最后根据 scrapy crawl <spider_name>
命令中的 spider_name
找到我们写的爬虫类,然后实例化它,在这里就是调用了_create_spider
方法:
def _create_spider(self, *args, **kwargs):
# 调用类方法from_crawler实例化
return self.spidercls.from_crawler(self, *args, **kwargs)
实例化爬虫比较有意思,它不是通过普通的构造方法进行初始化,而是调用了类方法 from_crawler
进行的初始化,找到 scrapy.Spider
类:
@classmethod
def from_crawler(cls, crawler, *args, **kwargs):
spider = cls(*args, **kwargs)
spider._set_crawler(crawler)
return spider
def _set_crawler(self, crawler):
self.crawler = crawler
# 把settings对象赋给spider实例
self.settings = crawler.settings
crawler.signals.connect(self.close, signals.spider_closed)
在这里我们可以看到,这个类方法其实也是调用了构造方法,进行实例化,同时也拿到了 settings
配置,来看构造方法干了些什么?
class Spider(object_ref):
name = None
custom_settings = None
def __init__(self, name=None, **kwargs):
# name必填
if name is not None:
self.name = name
elif not getattr(self, 'name', None):
raise ValueError("%s must have a name" % type(self).__name__)
self.__dict__.update(kwargs)
# 如果没有设置start_urls 默认是[]
if not hasattr(self, 'start_urls'):
self.start_urls = []
看到这里是不是很熟悉?这里就是我们平时编写爬虫类时,最常用的几个属性:name
、start_urls
、custom_settings
:
name
:在运行爬虫时通过它找到我们编写的爬虫类;start_urls
:抓取入口,也可以叫做种子URL;custom_settings
:爬虫自定义配置,会覆盖配置文件中的配置项;
引擎
分析完爬虫类的初始化后,还是回到 Crawler
的 crawl
方法,紧接着就是创建引擎对象,也就是 _create_engine
方法,看看初始化时都发生了什么?
class ExecutionEngine(object):
"""引擎"""
def __init__(self, crawler, spider_closed_callback):
self.crawler = crawler
# 这里也把settings配置保存到引擎中
self.settings = crawler.settings
# 信号
self.signals = crawler.signals
# 日志格式
self.logformatter = crawler.logformatter
self.slot = None
self.spider = None
self.running = False
self.paused = False
# 从settings中找到Scheduler调度器,找到Scheduler类
self.scheduler_cl