爬虫的主要目标是从非结构化的数据源(通常是web页面)中提取结构化数据。Scrapy spider可以以Python字典的形式返回提取的数据。虽然很方便和熟悉,但Python字典缺乏结构:很容易在字段名中出现拼写错误或返回不一致的数据,特别是在有许多爬行器的大型项目中。
为了定义通用的输出数据格式,Scrapy提供了Item类。Item对象是用于收集爬取到的数据的简单容器。它们提供了一个类似于dict API的API,具有方便的语法来声明它们的可用字段。
Item使用简单的class定义语法以及 Field 对象来声明。例如:
import scrapy
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
tags = scrapy.Field()
last_updated = scrapy.Field(serializer=str)
所有的item都是用scrapy.Field()来定义,日期对象要序列化为字符串
可以在定义Field时为每一个item对象指定序列化的数据类型
item的使用方法与dict非常类似
>>> product = Product(name=‘Desktop PC’, price=1000)
>>> print(product)
Product(name=‘Desktop PC’, price=1000)
>>> product[‘name’]
Desktop PC
>>> product.get(‘name’)
Desktop PC
>>> product[‘last_updated’] = ‘today’
>>> product[‘last_updated’]
today
>>> product.keys()
[‘price’, ‘name’]
>>> product.items()
[(‘price’, 1000), (‘name’, ‘Desktop PC’)]
也可以通过dict(items)的方法转化为字典
使用item来改写book爬虫的代码
# items.py 声明items
class BookItem(scrapy.Item):
name = scrapy.Field()
content = scrapy.Field()
bookname = scrapy.Field()
实例化item对象,将name和bookname存入items中
# book.py
item = BookItem()
item['name'] = name
item['bookname'] = bookname
item['content'] = content
# 最后返回items
yield item
Item Loader提供了一种解析数据并且填充进item的一种机制,注意:解析到的数据在item内部以列表的形式存储,允许向同一个字段添加多个值。如果在创建加载器时传递了item参数,则如果item的值已经是可迭代的,则按原样存储,如果项是单个值,则用列表包装。
Item Loader的使用
1、首先实例化一个item Loader对象,传入item类,和请求响应
2、itemLoader支持add_xpath语法,add_css选择器和add_value的方法向item中填充数据
3、最后将加载存入Item_Loader中的内容并返回。
from scrapy.loader import ItemLoader
from myproject.items import Product
def parse(self, response):
l = ItemLoader(item=Product(), response=response)
l.add_xpath(‘name’, ‘//div[@class=“product_name”]’)
l.add_xpath(‘name’, ‘//div[@class=“product_title”]’)
l.add_xpath(‘price’, ‘//p[@id=“price”]’)
l.add_css(‘stock’, ‘p#stock]’)
l.add_value(‘last_updated’, ‘today’) # you can also use literal values
return l.load_item()
Item_Loader为每个(item)字段包含一个输入处理器和一个输出处理器。一旦接收到提取的数据(通过add_xpath()、add_css()或add_value()方法),输入处理器就会对其进行处理,并将输入处理器的结果收集到ItemLoader中。在收集所有数据之后,将调用ItemLoader.load_item()方法来填充和获取填充的Item对象。这时调用输出处理器处理之前输入处理器处理过的内容,输出处理器的结果是分配给该项的最终值。
下面这个例子来说明输入处理器和输出处理器的工作流程:
l = ItemLoader(Product(), some_selector)
l.add_xpath(‘name’, xpath1) # (1)
l.add_xpath(‘name’, xpath2) # (2)
l.add_css(‘name’, css) # (3)
l.add_value(‘name’, ‘test’) # (4)
return l.load_item() # (5)
注意
输入和输出处理器的第一个参数都必须是iterable。这些函数的输出可以是任何值。输入处理器的结果将被附加到一个内部列表(在加载程序中),其中包含收集到的值(用于该字段)。输出处理器的结果是最终分配给该项的值。
输入处理器返回的值是内部收集的(在列表中),然后传递给输出处理器来填充字段。
1、直接定义一个继承于ItemLoader类的类声明
from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, MapCompose, Join
class ProductLoader(ItemLoader):
default_output_processor = TakeFirst()
name_in = MapCompose(unicode.title)
name_out = Join()
price_in = MapCompose(unicode.strip)
# …
可以看到,使用_in后缀声明输入处理器,而使用_out后缀声明输出处理器。您还可以使用ItemLoader.default_input_processor和ItemLoader.default_output_processor属性声明一个默认的输入/输出处理器。
2、在定义Item的时候,添加输入和输出选择器
正如在前一节中看到的,输入和输出处理器可以在项加载器定义中声明,以这种方式声明输入处理器是很常见的。但是,还有一个地方可以指定要使用的输入和输出处理器:Item字段元数据。下面是一个例子:
import scrapy
from scrapy.loader.processors import Join, MapCompose, TakeFirst
from w3lib.html import remove_tags
def filter_price(value):
if value.isdigit():
return value
class Product(scrapy.Item):
name = scrapy.Field(
input_processor=MapCompose(remove_tags),
output_processor=Join(),
)
price = scrapy.Field(
input_processor=MapCompose(remove_tags, filter_price),
output_processor=TakeFirst(),
)
输入和输出处理器的优先顺序如下:
ItemLoader特定于字段的属性:field_in和field_out(优先级最高)
字段元数据(input_processor和output_processor键)
默认项加载器:ItemLoader.default_input_processor()和ItemLoader.default_output_processor()(最低优先级)
由于在ItemLoader内部数据都是以列表或者可迭代对象的方式来传输,因此在定义item是要定义输出处理器。
class BookItem(scrapy.Item):
name = scrapy.Field(output_processor=TakeFirst())
content = scrapy.Field(output_processor=TakeFirst())
bookname = scrapy.Field(output_processor=TakeFirst())
# 1).使用itemloader的方法对数据进行存储,将item对象和selector/response关联
l = ItemLoader(item=BookItem(), selector=chapter)
detail_url=chapter.xpath('./a/@href').extract_first()
# -). 根据xpath进行提取数据信息并填充到item对象的name属性中
l.add_xpath('name', './a/text()')
# -). 将数据信息(书籍名称)填充到item对象的bookname属性中
l.add_value('bookname', response.url.split('/')[-1].rstrip('.html'))
# 最后以元数据的方式返回加载后的item对象,
yield Request(url=self.base_url + detail_url, callback=self.parse_chapter_detail,
meta={
'item': l.load_item()
})