SpiderLoader用来加载Spider类的。每个Spider类都有一个name属性,它就是Spider的名称。SpiderLoader可以根据Spider的名称,来获取其对应的Spider类。
[root@iZj6chejzrsqpclb7miryaZ aaa]# cat test_spider_loader.py
from scrapy.spiderloader import SpiderLoader
class Settings:
def getlist(self, key):
return ["aaa.spiders"]
def getbool(self, key):
return True
loader = SpiderLoader(Settings())
for spider_name in loader.list():
print "spider name is:", spider_name
spider_class = loader.load(spider_name)
print "spider class is:", spider_class
[root@iZj6chejzrsqpclb7miryaZ aaa]# tree .
.
├── aaa
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ ├── settings.pyc
│ └── spiders
│ ├── baidu_spider.py
│ ├── baidu_spider.pyc
│ ├── __init__.py
│ ├── __init__.pyc
│ ├── sina_spider.py
│ └── sina_spider.pyc
├── scrapy.cfg
└── test_spider_loader.py
2 directories, 15 files
[root@iZj6chejzrsqpclb7miryaZ aaa]# python test_spider_loader.py
spider name is: sina_spider
spider class is: <class 'aaa.spiders.sina_spider.SinaSpiderSpider'>
spider name is: baidu_spider
spider class is: <class 'aaa.spiders.baidu_spider.BaiduSpiderSpider'>
1,如何递归地扫描包:
from pkgutil import iter_modules
if __name__ == "__main__":
for importer, name, ispkg in iter_modules(["/root/aaa/"]):
print importer, name, ispkg
print importer.find_module(name).load_module(name)
pkgutil的iter_modules()函数用来扫描给定的目录列表中的每一个目录,返回它们下面的模块或子包。
其中:
可以看到,iter_modules()不会递归地扫描子包,因此对上面的程序进行改进:
import os
from pkgutil import iter_modules
def walk_modules(path, base_package=""):
for _, name, ispkg in iter_modules([path]):
fullname = name
if base_package:
fullname = base_package + "." + name
print "\033[31m%s\033[0m" % fullname
if ispkg:
walk_modules(os.path.join(path, name), fullname)
if __name__ == "__main__":
walk_modules("/root/aaa/")
上面的walk_modules()函数就会递归地遍历子包了。下面再改进一下:
import pprint
import os
from pkgutil import iter_modules
def walk_modules(path, base_package=""):
mods = []
for _, name, ispkg in iter_modules([path]):
fullname = name
if base_package:
fullname = base_package + "." + name
mods.append(fullname)
if ispkg:
mods.extend(walk_modules(os.path.join(path, name), fullname))
return mods
if __name__ == "__main__":
pprint.pprint(walk_modules("/root/aaa/"))
上面是我们自己实现的:递归地扫描包 的方法。下面看一下scrapy的实现:
scrapy.utils.misc.walk_modules(path)
def walk_modules(path):
mods = []
# 1,先导入“基包”
mod = import_module(path)
mods.append(mod)
# 2,每个包都有一个__path__属性,其定义了包内的模块搜索路径(它是一个列表,并且包目录一定在其中)
if hasattr(mod, '__path__'):
# 3,遍历每个搜索路径,得到其下的模块和子包
for _, subpath, ispkg in iter_modules(mod.__path__):
fullpath = path + '.' + subpath
if ispkg:
# 4.1,如果是子包,那么递归地进行处理
mods += walk_modules(fullpath)
else:
# 4.2,如果是模块,那么导入它
submod = import_module(fullpath)
mods.append(submod)
return mods
2:什么样的类是爬虫类:
scrapy.utils.spider.iter_spider_class(module):
# 爬虫类必须满足下面的条件
# 1,必须是一个新式类或经典类
# 2,必须是Spider的子类
# 3,从其他模块导入进来的Spider子类,不会被当作爬虫类
# 4,必须有name属性,且非空
def iter_spider_classes(module):
from scrapy.spiders import Spider
for obj in six.itervalues(vars(module)):
if inspect.isclass(obj) and \
issubclass(obj, Spider) and \
obj.__module__ == module.__name__ and \
getattr(obj, 'name', None):
yield obj
最后,解析SpiderLoader的主要代码:
class SpiderLoader(object):
def __init__(self, settings):
# “基包”列表。SpiderLoader会递归地导入每个“基包”及其包内搜索路径下,所有的模块和子包
self.spider_modules = settings.getlist('SPIDER_MODULES')
# True表示:当导入包或模块时,如果出现异常,只显示警告信息;否则,抛出该异常
self.warn_only = settings.getbool('SPIDER_LOADER_WARN_ONLY')
# 保存爬虫名称和爬虫类之间的映射关系。其中,key是爬虫名称,value是爬虫类
self._spiders = {}
# 爬虫的名称不能重复,该字典就是用来查重的
self._found = defaultdict(list)
# 加载所有的爬虫类
self._load_all_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
def _load_all_spiders(self):
for name in self.spider_modules:
try:
# 递归地导入包name及其包内搜索路径下,所有的模块和子包
for module in walk_modules(name):
# 加载 包或模块中的爬虫类 到 self._spiders字典
self._load_spiders(module)
except ImportError as e:
if self.warn_only:
# 导入包或模块时,如果出现异常,那么只显示警告信息,然后继续处理下一个“基包”
msg = ("\n{tb}Could not load spiders from module '{modname}'. "
"See above traceback for details.".format(
modname=name, tb=traceback.format_exc()))
warnings.warn(msg, RuntimeWarning)
else:
# 抛出异常
raise
...
# 根据爬虫名称获取爬虫类
def load(self, spider_name):
"""
Return the Spider class for the given spider name. If the spider
name is not found, raise a KeyError.
"""
try:
return self._spiders[spider_name]
except KeyError:
raise KeyError("Spider not found: {}".format(spider_name))