前言
在使用 scrapy
时,运行爬虫仅需要通过 scrapy crawl 爬虫名
就可启动我们写好的爬虫,那么 scrapy
是如何通过名称找到爬虫类的呢?通过分析源码可窥见一二,同时也可从中找出获取指定模块下的所有类的方法。
scrapy
源码分析
在 scrapy.spiderloader.SpiderLoader
中,可以发现一个名为 _load_all_spiders
的方法,通过名称不难看出,该方法用于读取所有的爬虫。再看源码(为便于理解,省掉其中部分内容):
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
文件夹。
这里通过对配置项的遍历,找到每一个爬虫模块,再调用 walk_modules
函数获取模块下的所有子模块(包含其本身),然后找到每一个模块中的 spider
类(也就是 self._load_spiders
方法做的事情)。接下来追溯到 walk_modules
查看其代码:
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
的子类。由此我们就知道了如何去获取指定模块下(包含其子模块)的所有定义的类了。
简单实现
通过对 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
方法即可。
网友评论