以下以及后续所有的源码都是基于scrapy1.0.2,并且会涉及到一些python基础和模块。
scrapy startproject spiderpoject 生成一个工程,整个目录结构如下图。
item.py用来定义数据模型,每个item是继承scrapy.Item,查看scrapy/item.py
@six.add_metaclass(ItemMeta) class Item(DictItem): pass
简单很定义,继承DictItem,给Item添加元类ItemMeta仅此而已
ItemMeta这个类有3个基础的点
1. 继承ABCMeta(from abc import ABCMeta),咋一看abc这个模块名称很随意,其实是Abstract Base Class的缩写,将ItemMeta定义成一个抽象类,因为python的原始机制中是不能实现抽象类和接口的。
2. __new__方法的实现,__new__是python的实际构造函数,隐式调用__init__函数。
3. 元类,最直白的表述,python里一切皆为对象,包括类,而元类就是类的类。
class ItemMeta(ABCMeta): def __new__(mcs, class_name, bases, attrs): new_bases = tuple(base._class for base in bases if hasattr(base, '_class')) _class = super(ItemMeta, mcs).__new__(mcs, 'x_' + class_name, new_bases, attrs) fields = getattr(_class, 'fields', {}) new_attrs = {} for n in dir(_class): v = getattr(_class, n) if isinstance(v, Field): fields[n] = v elif n in attrs: new_attrs[n] = attrs[n] new_attrs['fields'] = fields new_attrs['_class'] = _class return super(ItemMeta, mcs).__new__(mcs, class_name, bases, new_attrs)
定义BookItem,看看ItemMeta到底发生了什么
class BookItem(Item):
name = Field()
首先调用Item的元类,new_bases是一个空元祖,_class是一个动态x_Item类,最后返回的是一个重构attrs的元类
接着调用BookItem的元类,将定义的name属性,放到fields({'fields':{'name':'xx'}})。
DictItem继承MutableMapping和BaseItem,
1. 继承MutableMapping实现__setitem__ 、 __getitem__ 、 __delitem__ 、 __len__, __iter__,就是实现了python内置dict一样的类。
2. BaseItem是继承scrapy.utils.trackref.object_ref,是一个不能动态添加属性的弱引用对象(模块weakref和__slots__关键字)
3. 定义fields属性,上面提到过,定义在BookItem里的属性会写到fileds中
4. 实现的__getattr__和__setatt__方法,确在里方法里raise AttributeError,这是为了呼应__slots__
class DictItem(MutableMapping, BaseItem): fields = {} def __init__(self, *args, **kwargs): self._values = {} if args or kwargs: # avoid creating dict for most common case for k, v in six.iteritems(dict(*args, **kwargs)): self[k] = v def __getitem__(self, key): return self._values[key] def __setitem__(self, key, value): if key in self.fields: self._values[key] = value else: raise KeyError("%s does not support field: %s" % (self.__class__.__name__, key)) def __delitem__(self, key): del self._values[key] def __getattr__(self, name): if name in self.fields: raise AttributeError("Use item[%r] to get field value" % name) raise AttributeError(name) def __setattr__(self, name, value): if not name.startswith('_'): raise AttributeError("Use item[%r] = %r to set field value" % (name, value)) super(DictItem, self).__setattr__(name, value) def __len__(self): return len(self._values) def __iter__(self): return iter(self._values) __hash__ = BaseItem.__hash__ def keys(self): return self._values.keys() def __repr__(self): return pformat(dict(self)) def copy(self): return self.__class__(self)
总体设计很巧妙,包含了python里很多语法和技巧。