在使用 scrapy
时,运行爬虫仅须要经过 scrapy crawl 爬虫名
就可启动咱们写好的爬虫,那么 scrapy
是如何经过名称找到爬虫类的呢?经过分析源码可窥见一二,同时也可从中找出获取指定模块下的全部类的方法。python
scrapy
源码分析在 scrapy.spiderloader.SpiderLoader
中,能够发现一个名为 _load_all_spiders
的方法,经过名称不难看出,该方法用于读取全部的爬虫。再看源码(为便于理解,省掉其中部份内容):app
def _load_all_spiders(self): for name in self.spider_modules: try: for module in walk_modules(name): self._load_spiders(module) except ImportError: ... self._check_name_duplicates()
这里,self.spider_modules
即为 scrapy
项目中配置文件中的 SPIDER_MODULES
配置项。默认状况下该配置项指向项目爬虫中的 spiders
文件夹。scrapy
这里经过对配置项的遍历,找到每个爬虫模块,再调用 walk_modules
函数获取模块下的全部子模块(包含其自己),而后找到每个模块中的 spider
类(也就是 self._load_spiders
方法作的事情)。接下来追溯到 walk_modules
查看其代码:ide
def walk_modules(path): """读取模块及其下面的全部子模块并返回 For example: walk_modules('scrapy.utils') """ mods = [] mod = import_module(path) mods.append(mod) if hasattr(mod, '__path__'): for _, subpath, ispkg in iter_modules(mod.__path__): fullpath = path + '.' + subpath if ispkg: mods += walk_modules(fullpath) else: submod = import_module(fullpath) mods.append(submod) return mods
这里对于注释作了简单的翻译并省掉一部分可有可无的内容。能够看到该方法调用了 iter_modules
找出模块的全部子模块(若是有),iter_modules
属于内置模块 pkgutil
中的方法,该方法返回指定模块路径下的全部子模块信息(不包含其自己)。经过获取的子模块信息进行完整的模块路径拼接,若是子模块为包的话则依次递归调用,不然导入该模块并放入结果中等待返回。函数
最后,再来看看 self._load_spiders
方法具体作了哪些事情:源码分析
def _load_spiders(self, module): for spcls in iter_spider_classes(module): self._found[spcls.name].append((module.__name__, spcls.__name__)) self._spiders[spcls.name] = spcls
这里主要逻辑被封装在 iter_spider_classes
函数中,追溯能够看到其源码:翻译
def iter_spider_classes(module): """返回一个迭代器,包含指定模块下全部定义的爬虫类 """ from scrapy.spiders import Spider for obj in vars(module).values(): if inspect.isclass(obj) and \ issubclass(obj, Spider) and \ obj.__module__ == module.__name__ and \ getattr(obj, 'name', None): yield obj
一样,对注释作了简单的处理。这里调用了 inspect.isclass
函数用来判断对象是否为类。而后再判断是否为 scrapy.Spider
的子类。由此咱们就知道了如何去获取指定模块下(包含其子模块)的全部定义的类了。code
经过对 scrapy
源码的分析,咱们能够定义一个方法用来返回指定模块下的全部指定类:对象
from inspect import isclass from scrapy.utils.misc import walk_modules def iter_cls_from_module(path, base_cls=None): """迭代返回指定模块下的全部定义的类,若是指定 base_cls,则仅返回其子类""" for mod in walk_modules(path): for obj in vars(mod).values(): if isclass(obj): if base_cls is not None: if issubclass(obj, base_cls): yield obj else: yield obj
这里为了方便就再也不重复造轮子了,使用 scrapy
提供的 walk_modules
方法便可。递归