零、前言 Crawler
类是 Scrapy 中的一个核心组件, 很多其他组件通过实现 from_crawler()
方法与 Crawler
类建立起联系.from_crawler()
方法为爬虫各组件提供了强大的功能扩展支持. 本文将结合源码,对from_crawler()
方法进行梳理总结.
全文主要围绕以下几个问题:
哪些组件可以使用 from_crawler()
方法?
在 from_crawler()
中都可以实现哪些功能?
from_crawler()
方法是怎么被调用的?
一、哪些组件可以使用 from_crawler()
方法 首先,常用的组件有:
DupeFilter
Scheduler
Middlewares
Pipelines
Extensions
Spider
根据场景,不常用的组件有:
downloader.handlers
queues
dns resolver
log formatter
常用组件其实可以参考 scrapy_redis
的实现,很容易就可以看到相关的用法.
或者可以参考 scrapy 本身模块的实现,项目中可参考的模块有:
scrapy.dupefilter
scrapy.core.scheduler
scrapy.downloadermiddlewares
scrapy.spidermiddlewares
scrapy.core.downloader.handlers
scrapy.squeues
scrapy.resolver
二、在 from_crawler()
方法中都可以实现哪些功能 首先,from_crawler()
最核心的一个目的,是创建并返回其所在类的实例 。
比如自己实现 Spider 时,所继承的父类 scrapy.Spider
中:
1 2 3 4 5 6 7 8 9 class Spider (object_ref ): ... @classmethod def from_crawler (cls, crawler, *args, **kwargs ): spider = cls(*args, **kwargs) spider._set_crawler(crawler) return spider ...
这里有个相关的知识点:类方法
其次,由于必须传入crawler
实例作为参数,那么我们可以对 crawler
携带的属性加以利用 。
比如内置的爬虫中间件 scrapy.spidermiddlewares.depth.DepthMiddleware
:
在 from_crawler()
中获取 settings 中的配置项,将其作为实例属性绑定到中间件实例中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class DepthMiddleware : def __init__ (self, maxdepth, stats, verbose_stats=False , prio=1 ): self.maxdepth = maxdepth self.stats = stats self.verbose_stats = verbose_stats self.prio = prio @classmethod def from_crawler (cls, crawler ): settings = crawler.settings maxdepth = settings.getint('DEPTH_LIMIT' ) verbose = settings.getbool('DEPTH_STATS_VERBOSE' ) prio = settings.getint('DEPTH_PRIORITY' ) return cls(maxdepth, crawler.stats, verbose, prio) ...
另外,由于 scrapy 提供了信号的支持, 并且 Crawler
实例中绑定了一个 signals
属性。那么,我们可以 利用信号机制在特定时机执行特定的操作 。
比如 Scrapy 文档 - Signals 一节给出的爬虫示例:
from_crawler()
定义了在爬虫结束时,执行 spider_closed()
方法,输出日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class DmozSpider (Spider ): name = "dmoz" allowed_domains = ["dmoz.org" ] start_urls = [ "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/" , "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/" , ] @classmethod def from_crawler (cls, crawler, *args, **kwargs ): spider = super (DmozSpider, cls).from_crawler(crawler, *args, **kwargs) crawler.signals.connect(spider.spider_closed, signal=signals.spider_closed) return spider def spider_closed (self, spider ): spider.logger.info('Spider closed: %s' , spider.name) ...
更多 Crawler
的 API 见文档 Crawler API
三、from_crawler()
方法是怎样被调用的 在上述示例中,直接在类里面声明了 from_crawler()
方法,它们会直接被 scrapy 内部流程中被调用。
那么具体调用过程是什么样的呢?
上面已经说过,类方法 from_crawler()
的主要功能是创建并返回类的实例,那么只要找到这些组件实例化的位置,就能看到 from_crawler()
被调用的过程。
这些组件被实例化,主要是通过两种方式:
直接调用 from_crawler()
进行实例化
通过 scrapy.util.misc.create_instance()
函数进行实例化
首先说 1. 直接调用 from_crawler()
进行实例化。
典型的示例如 scrapy.crawler.Crawler
中,对爬虫类 Spider 进行实例化的过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Crawler : ... @defer.inlineCallbacks def crawl (self, *args, **kwargs ): if self.crawling: raise RuntimeError("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: self.crawling = False if self.engine is not None : yield self.engine.close() raise def _create_spider (self, *args, **kwargs ): return self.spidercls.from_crawler(self, *args, **kwargs) def _create_engine (self ): return ExecutionEngine(self, lambda _: self.stop()) ...
在 scrapy 中用到最多的是第二种,通过create_instance()
函数进行实例化.
调用的实例如 scheduler 实例化 dupefilter 的过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Scheduler : ... @classmethod def from_crawler (cls, crawler ): settings = crawler.settings dupefilter_cls = load_object(settings['DUPEFILTER_CLASS' ]) dupefilter = create_instance(dupefilter_cls, settings, crawler) pqclass = load_object(settings['SCHEDULER_PRIORITY_QUEUE' ]) if pqclass is PriorityQueue: warnings.warn("SCHEDULER_PRIORITY_QUEUE='queuelib.PriorityQueue'" " is no longer supported because of API changes; " "please use 'scrapy.pqueues.ScrapyPriorityQueue'" , ScrapyDeprecationWarning) from scrapy.pqueues import ScrapyPriorityQueue pqclass = ScrapyPriorityQueue dqclass = load_object(settings['SCHEDULER_DISK_QUEUE' ]) mqclass = load_object(settings['SCHEDULER_MEMORY_QUEUE' ]) logunser = settings.getbool('SCHEDULER_DEBUG' ) return cls(dupefilter, jobdir=job_dir(settings), logunser=logunser, stats=crawler.stats, pqclass=pqclass, dqclass=dqclass, mqclass=mqclass, crawler=crawler) ...
那么我们来看下 create_instance
函数内部的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 def create_instance (objcls, settings, crawler, *args, **kwargs ): """Construct a class instance using its ``from_crawler`` or ``from_settings`` constructors, if available. At least one of ``settings`` and ``crawler`` needs to be different from ``None``. If ``settings `` is ``None``, ``crawler.settings`` will be used. If ``crawler`` is ``None``, only the ``from_settings`` constructor will be tried. ``*args`` and ``**kwargs`` are forwarded to the constructors. Raises ``ValueError`` if both ``settings`` and ``crawler`` are ``None``. .. versionchanged:: 2.2 Raises ``TypeError`` if the resulting instance is ``None`` (e.g. if an extension has not been implemented correctly). """ if settings is None : if crawler is None : raise ValueError("Specify at least one of settings and crawler." ) settings = crawler.settings if crawler and hasattr (objcls, 'from_crawler' ): instance = objcls.from_crawler(crawler, *args, **kwargs) method_name = 'from_crawler' elif hasattr (objcls, 'from_settings' ): instance = objcls.from_settings(settings, *args, **kwargs) method_name = 'from_settings' else : instance = objcls(*args, **kwargs) method_name = '__new__' if instance is None : raise TypeError(f"{objcls.__qualname__} .{method_name} returned None" ) return instance
逻辑非常清晰明了,根据条件判断调用类的 from_crawler()
方法 或 from_settings()
方法,对类进行实例化,并返回。
同时,这里也可以看到另一个常见的类方法—— from_settings()
,有着和 from_crawler()
相似的作用。
但是如果一个组件是通过 create_instance()
被实例化的, 并且组件内又同时实现了 from_crawler()
和 from_settings()
方法,那么将只有 from_crawler()
方法被调用。
至此, 我们可以对 Scrapy 中常用的 from_crawler()
方法有一个基本的认识.
四、参考资料 [1] Scrapy documentation [2] Scrapy 源码