爬虫 Scrapy 学习系列之六:Items

前言

这是 Scrapy 系列学习文章之一,本章主要介绍 Items 相关的内容;

本文为作者的原创作品,转载需注明出处;

简介

Scrapy 的核心目的就是从非结构化的网页中提取出结构化的数据;默认的,Scrapy 爬虫以 dicts 的形式返回格式化的数据;但是,这里有一个问题,就是 dicts 并不能很好的表示这种结构化数据的结构,而且经常容易出错,转换也麻烦;

因此,Item 诞生了,它提供了这样一个简单的容器来收集爬取到的数据,并提供非常简便的 API 来声明它的 fields;

多种多样的 Scrapy 的组件使用 Items 所提供的额外信息,比如,exporters 通过 fields 的相关申明而判断哪些 columns 是需要导出的,serializaton 可以使用 fields metadata 来进行定制化,trackref 跟踪 Item 实例以便帮助找到 Memory leaks 发生的地方(更多的查看 Debugging memory leaks with trackref 部分内容)

声明 Items

通过一个简单的 class 和多个 Field 对象来声明 Items 对象;看一个 Product Item 的例子,

1
2
3
4
5
6
7
import scrapy

class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
last_updated = scrapy.Field(serializer=str)

需要注意的是,Product 继承自 scrapy.Item 父类;

Item Fields

Filed 对象是用来指明每一个 field 的 metadata 信息的;比如,last_updated通过 serializer 来声明它的 metadata;

使用 Items

下面以上述的 Product Item 作为例子,来看看 Items 是如何工作的;

创建 Items

1
2
3
>>> product = Product(name='Desktop PC', price=1000)
>>> print product
Product(name='Desktop PC', price=1000)

注意,上述通过关键字参数所新建的 product item,其对应的参数是实例变量;

得到 field values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
>>> product['name']
Desktop PC
>>> product.get('name')
Desktop PC

>>> product['price']
1000

>>> product['last_updated']
Traceback (most recent call last):
...
KeyError: 'last_updated'

>>> product.get('last_updated', 'not set')
not set

>>> product['lala'] # getting unknown field
Traceback (most recent call last):
...
KeyError: 'lala'

>>> product.get('lala', 'unknown field')
'unknown field'

>>> 'name' in product # is name field populated?
True

>>> 'last_updated' in product # is last_updated populated?
False

>>> 'last_updated' in product.fields # is last_updated a declared field?
True

>>> 'lala' in product.fields # is lala a declared field?
False

这里需要注意的是,可以通过 product.fields 返回所有在类中申明的 fields,注意这里不是 populated fields

为 field value 赋值

1
2
3
4
5
6
7
8
>>> product['last_updated'] = 'today'
>>> product['last_updated']
today

>>> product['lala'] = 'test' # setting unknown field
Traceback (most recent call last):
...
KeyError: 'Product does not support field: lala'

访问所有 populated(已填充的) fields

访问所有 populated values,就类似于使用 dict API;

1
2
3
4
5
>>> product.keys()
['price', 'name']

>>> product.items()
[('price', 1000), ('name', 'Desktop PC')]

其它常用方法

拷贝 Items

1
2
3
4
5
6
7
>>> product2 = Product(product)
>>> print product2
Product(name='Desktop PC', price=1000)

>>> product3 = product2.copy()
>>> print product3
Product(name='Desktop PC', price=1000)

可见,有两种方式,一种是把原有的 product item 作为构造函数的参数,另外一种是直接调用 item 的.copy()方法;

从 Items 中创建 dicts

1
2
>>> dict(product) # create a dict from all populated values
{'price': 1000, 'name': 'Desktop PC'}

从 dicts 中创建 Items

1
2
3
4
5
6
7
>>> Product({'name': 'Laptop PC', 'price': 1500})
Product(price=1500, name='Laptop PC')

>>> Product({'name': 'Laptop PC', 'lala': 1500}) # warning: unknown field in dict
Traceback (most recent call last):
...
KeyError: 'Product does not support field: lala'

扩展 Items

可以通过继承 Item 的方式来扩展出更多的 field 以及 metadata;

1
2
3
class DiscountedProduct(Product):
discount_percent = scrapy.Field(serializer=str)
discount_expiration_date = scrapy.Field()

另外,可以直接从原有的 field metadata 中来扩展出新的 field metadata,可以追加更多的 values

1
2
class SpecificProduct(Product):
name = scrapy.Field(Product.fields['name'], serializer=my_serializer)

这种方式除了会覆盖掉原有的 serializer metadata 意外,其余的与之前的 field metadata 保持不变;当然可以追加新的 metadata;

Item

1
class scrapy.item.Item([arg])

通过一个可选的参数 arg 来返回一个新的 Item;

Items 复制了标准的 dict API,包括它的构造器;唯一需要 Item 额外提供的属性是:

fields

一个 dictionary 包含了当前 Item 所有的申明的 Items;keys 就是 field name,values 就是 Filed 对象;

Field

1
class scrapy.item.Field([arg])

Field 其实只是一个 dict class 的 alias 并且并不提供任何额外的功能和属性;