前言
在使用 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
方法即可。