本文以一个当当网图书出版社信息举例,说明Scrapy中,ItemLoader、MapCompose、Compose、input_processor与output_processor的一些使用事项。
先给出spider与item的代码实例:
spider:
def parse_item(self, response):
for r in response.css(".bang_list li"):
loader = DangdangItemLoader(DangdangItem(), selector=r)
loader.add_css("publisher", ".publisher_info a::text")
item = loader.load_item()
yield item
item:
class DangdangItemLoader(ItemLoader):
default_output_processor = TakeFirst()
class DangdangItem(scrapy.Item):
publisher = scrapy.Field(
input_processor=Compose(
lambda x: x[-1]
)
)
Scrapy中Item Loader
为每个Item Field
单独提供了一个Input processor
和一个Output processor
,spider中的 add_xpath()
,add_css()
,add_value()
方法接收到的数据都会先进入input_processor,执行以后所得到清洗后的数据将仍然保存在 ItemLoader 实例中;当数据收集完成以后,ItemLoader 通过 load_item()
方法来进行填充并返回已填充的 Item 实例。
我曾未清晰理解上述概念,而遇到了MapCompose不起作用,且程序不进入input_processor方法内部的问题。直到看了源码后才明白:
class ItemLoader(object):
default_input_processor = Identity()
default_output_processor = Identity()
default_selector_class = Selector
...
def get_input_processor(self, field_name):
proc = getattr(self, '%s_in' % field_name, None)
if not proc:
proc = self._get_item_field_attr(field_name, 'input_processor', \
self.default_input_processor)
return proc
def get_output_processor(self, field_name):
proc = getattr(self, '%s_out' % field_name, None)
if not proc:
proc = self._get_item_field_attr(field_name, 'output_processor', \
self.default_output_processor)
return proc
input_processor与output_processor两个函数都必须在item_loader实例中!这里是一个错误案例:Why my input_processor in Scrapy not work?
我们一般定义一个ItemLoader
的形式:
loader = DangdangItemLoader(item=DangdangItem(), response=response)
这仅是我们需要对response进行数据处理的情况。在我开头给出的例子中,实例化item_loader的第二个参数是一个Selector
,这是因为要处理的数据已不再来自于response,而来自于scrapy.selector.unified.Selector
对象。
在我开头给出的例子中,Item类有一个input_processor:
publisher = scrapy.Field(
input_processor=Compose(
lambda x: x[-1]
)
)
这里的input_processor是当函数parse_item
通过add_css
方法获取到原始数据:
后开始执行的。如果这里再声明一个output_processor,则output_processor中的内容会当函数parse_item
执行到item_loader
方法时再执行。
在我给出的例子中,ItemLoader类有一个output_processor:
default_output_processor = TakeFirst()
因为是output_processor,所以是当函数parse_item
执行到item_loader
方法时,才会执行TakeFirst()
。TakeFirst()的作用就不多解释了,就是将item中的全部列表变为值。
在这个例子中,原始的publisher数据是:
publisher = scrapy.Field(
input_processor=Compose(
lambda x: x[-1]
)
)
Compose
方法会对原始publisher数据整体去执行下面的lambda,即保留最后一个元素"上海人民出版社"。
而如果是MapCompose
,则会对原始publisher数据的每一个元素执行一遍下面的lambda,即仍会返回一个list:[‘尼’,'d','i','宏','社']
。如果再经过上述的load_item
中的TakeFirst(),最终的结果会是'尼'
。