爬虫的主要任务就是·从非结构化的数据中获得结构化的数据。
Item提供了盛装抓取到的数据的容器。而Item Loader提供了填充该容器的机制。
项目加载器(Item Loaders)提供了一种灵活、高效和容易的机制,通过爬虫或源格式(HTML、XML等)扩展和覆盖不同的字段解析规则。这种方法有利于后期的维护。
输入和输出处理器
项目加载器对于每个(项目)字段包含一个输入处理器和一个输出处理器。输入处理器只要他的接收所处理的数据(通过add_xpath(), add_css()或者add_value()方法)和输入的结果被收集并保持Item Loader内部。收集所有数据后,ItemLoader.load_item()调用该方法来填充和获取Item对象。在这一步中先调用输出处理器来处理之前收集到的数据,然后再存入Item中,输出处理器的结果是分配给项目的最终值。
声明Item Loader加载器
项目加载器是通过类定义语法声明为Items。下面代码在items.py
import re
import scrapy
from scrapy.loader import ItemLoader
from scrapy.loader.processors import MapCompose, TakeFirst
def changeTitle(value):
value = '标题:' + value
return value
def getNewTime(value):
newTime = value.split('·')[0]
newTime = newTime.strip()
return newTime
def getNum(value):
pattern = re.compile(r'\d+', re.S)
res = re.findall(pattern, value)
if res:
return res[0]
else:
return 0
class ArticleItemLoader(ItemLoader):
# 设置输出处理器
# TakeFirst获取所有数据当中的第一条数据
default_output_processor = TakeFirst()
# 默认的输出处理器,其返回值是一个列表
# default_output_processor = ItemLoader.default_output_processor
# 默认的输入处理器
# default_input_processor = ItemLoader.default_input_processor
class JobboleItem(scrapy.Item):
img = scrapy.Field()
title = scrapy.Field(input_processor=MapCompose(changeTitle))
time = scrapy.Field(input_processor=MapCompose(getNewTime))
detail_url = scrapy.Field()
zan = scrapy.Field()
book_mark = scrapy.Field(input_processor=MapCompose(getNum))
comment = scrapy.Field(input_processor=MapCompose(getNum))
也可以在定义Item的时候声明输入输出处理器,例如
class JobboleItem(scrapy.Item):
title = scrapy.Field(
input_processor=MapCompose(changeTitle),
output_processor=TakeFirst(),
)
time = scrapy.Field(
input_processor=MapCompose(getNewTime),
output_processor=Join(),
)
Field 字段有两个参数:
- 第一个是输入处理器(input_processor) ,当这个item,title这个字段的值传过来时,可以在传进来的值上面做一些预处理。
- 第二个是输出处理器(output_processor) , 当这个item,title这个字段被预处理完之后,输出前最后的一步处理。
内置的处理器
scrapy.loader.processors.Identity
最简单的处理器,什么都不做。它返回原始值不变。它不接收任何构造函数参数,也不接受Loader上下文。
例如:
>>> from scrapy.loader.processors import Identity
>>> proc = Identity()
>>> proc(['one', 'two', 'three'])
['one', 'two', 'three']
scrapy.loader.processors.TakeFirst
从接收到的值中返回第一个非空(non-null/non-empty)值,因此通常用作单值字段的输出处理器。它不接收任何构造参数,也不接收Loader上下文。
例如:
>>> from scrapy.loader.processors import TakeFirst
>>> proc = TakeFirst()
>>> proc(['', 'one', 'two', 'three'])
'one'
scrapy.loader.processors.Join(separator=u' ')
返回与构造函数中给定的分隔符连接的值,默认为空格。它不接受加载器上下文(Loader contexts)
当使用默认分隔符时,此处理器相当于以下功能: u' '.join
例如:
>>> proc = Join()
>>> proc(['one', 'two', 'three'])
u'one two three'
>>> proc = Join('<br>')
>>> proc(['one', 'two', 'three'])
u'one<br>two<br>three'
填充数据
ItemLoader提供了三种填充数据的方式。
def parse(self, response):
# 创建ItemLoader实例化对象,需要两个参数。
# 第一个参数为item的实例化对象
# 网页的源码,即response
item_loader = ArticleItemLoader(item=JobboleItem(), response=response)
# 通过xpath选择器,提取信息
item_loader.add_xpath('title', '//div[@class="entry-header"]/h1/text()')
# 直接给字段赋值
item_loader.add_value('url', response.url)
# item_loader.add_value('img', response.meta['img'])
# 通过css选择器,提取信息
item_loader.add_css('time', 'div[class=entry-meta] p::attr(text())')
item_loader.add_value('detail_url', response.url)
item_loader.add_xpath('zan', '//div[@class="post-adds"]/span/h10/text()')
item_loader.add_xpath('book_mark', '//span[contains(@class,"bookmark-btn")]/text()')
item_loader.add_xpath('comment', '//a[@href="#article-comment"]/span/text')
# 将itemloader加载器中保存的值收集
item = item_loader.load_item()
yield item
- 第一个参数:指定字段名,如title。
- 第二个参数:指定对应的提取规则,或者传值。
- 前面调用add_xpath等只是将提取的数据收集起来。最终,当所有数据被收集起来之后,还需要调用 ItemLoader.load_item() 方法, 实际上填充并且返回了之前通过调用 add_xpath(),add_css(),and add_value() 所提取和收集到的数据。
- 默认情况下,这些字段填入的全部是list类型。就算是传值,传递了一个url,但是结果依然是一个list。
- 我们可以对每个字段进行配置,匹配映射,非常的清晰,大大方便了可配置性和可维护性。
-
总结一下,每个字段的数据的处理过程是:
- 第一步, 通过 add_xpath(), add_css() 或者 add_value() 方法),提取到数据。
- 第二步,将提取到的数据,传递到输入处理器(input_processor)中进行处理,处理结果被收集起来,并且保存在ItemLoader内(但尚未分配给该Item)。
- 第三步,最后调用输出处理器(output_processor)来处理之前收集到的数据(这是最后一步对数据的处理)。然后再存入到Item中,输出处理器的结果是被分配到Item的最终值。
- 第四步,收集到所有的数据后, 调用ItemLoader.load_item() 方法来填充,并得到填充后的 Item 对象。
-
需要注意的是:input_processor和output_processor都是可调用对象,调用时传入需要被分析的数据, 处理后返回分析得到的值。因此你可以使用任意函数作为输入、输出处理器。唯一需注意的是它们必须接收一个(并且只是一个)迭代器性质的参数。
Compose
- 用给定的多个函数的组合,来构造的处理器。list对象(注意不是指list中的元素),依次被传递到第一个函数,然后输出,再传递到第二个函数,一个接着一个,直到最后一个函数返回整个处理器的输出。
- 默认情况下,当遇到None值(list中有None值)的时候停止处理。可以通过传递参数stop_on_none = False改变这种行为。
# 单独直接使用
from scrapy.loader.processors import Compose
# stop_on_none=True, 指定在遇到None时,不用中断,还继续处理
# lambda v: v[0], 指定取第一个元素
# str.upper , 大写
proc = Compose(lambda v: v[0], str.upper, stop_on_none=True)
# 接收对象是一个可迭代的对象,如list
result = proc(['one', 'two', None, 'three'])
# 结果:result = ONE
print(f"result = {result}")
- 每个函数可以选择接收一个loader_context参数。
MapCompose
- 与Compose处理器类似,区别在于各个函数结果在内部传递的方式(会涉及到list对象解包的步骤):
- 输入值是被迭代的处理的,List对象中的每一个元素被单独传入,第一个函数进行处理,然后处理的结果被连接起来形成一个新的迭代器,并被传入第二个函数,以此类推,直到最后一个函数。最后一个函数的输出被连接起来形成处理器的输出。
- 每个函数能返回一个值或者一个值列表,也能返回None(会被下一个函数所忽略)
- 这个处理器提供了很方便的方式来组合多个处理单值的函数。因此它常用于输入处理器,因为传递过来的是一个List对象。
# 单独直接使用
from scrapy.loader.processors import MapCompose
def add_firstStr(value):
return value + "_firstAdd"
def add_secondStr(value):
return value + "_secondAdd"
# stop_on_none=True, 指定在遇到None时,不用中断,还继续处理
# 依次处理每个list元素
proc = MapCompose(add_firstStr, add_secondStr, str.upper, stop_on_none=True)
# 接收对象是一个可迭代的对象,如list
result = proc(['one', 'two', 'three'])
# 结果:result = ['ONE_FIRSTADD_SECONDADD', 'TWO_FIRSTADD_SECONDADD', 'THREE_FIRSTADD_SECONDADD']
print(f"result = {result}")
- 与Compose处理器类似,它也能接受Loader context。