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))