Item Loaders 提供了一种便捷的方式填充抓取到的:Items。虽然 Items 可以使用自带的类字典形式 API 填充,但是 Items Loaders 提供了更便捷的 API,可以分析原始数据并对 Item 进行赋值。

从另一方面来说,Items 提供保存抓取数据的容器,而 Item Loaders 提供的是填充容器的机制。

Item Loaders提供的是一种灵活,高效的机制,可以更方便的被 spidersource format (HTML,XML,etc)扩展,并 override 更易于维护的、不同的内容分析规则。

1.使用itemLoaders填充item

要使用 Item Loader, 你必须先将它实例化。你可以使用类似字典的对象(例如: Item or dict)来进行实例化,或者不使用对象也可以,当不用对象进行实例化的时候,Item 会自动使用 ItemLoader.default._item_class 属性中指定的 Item 类在 Item Loader constructor 中实例化。

然后,你开始收集数值到 Item Loader 时,通常使用 Selectors。你可以在同一个 item field 里面添加多个数值;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()

快速查看这些代码之后,我们可以看到发现 name 字段被从页面中两个不同的 XPath 位置提取:

换言之,数据通过用 add_xpath()的方法,把从两个不同的 XPath 位置提取的数据收集起来. 这是将在以后分配给 name 字段中的数据。

之后,类似的请求被用于 pricestock 字段 (后者使用 CSS selectoradd_css() 方法), 最后使用不同的方法 add_value()last_update 填充文本值(today)。

最终,当所有数据被收集起来之后, 调用 ItemLoader.load_item() 方法,实际上填充并且返回了之前通过调用add_xpath()add_css()and add_value()所提取和收集到的数据的 Item

2.输入和输出处理器

Input and Output processors

Item Loader 在每个(Item)字段中都包含了一个输入处理器一个输出处理器。 输入处理器收到数据时立刻提取数据 (通过 add_xpath()add_css()或者 add_value()方法)之后输入处理器的结果被收集起来并且保存在 ItemLoader 内. 收集到所有的数据后, 调用 ItemLoader.load_item()方法来填充,并得到填充后的 Item 对象。这是当输出处理器被和之前收集到的数据(和用输入处理器处理的)被调用.输出处理器的结果是被分配到 Item 的最终值。

让我们看一个例子来说明如何输入和输出处理器被一个特定的字段调用(同样适用于其他 field):

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)

发生了这些事情:

需要注意的是,输入和输出处理器都是可调用对象,调用时传入需要被分析的数据, 处理后返回分析得到的值。因此你可以使用任意函数作为输入、输出处理器。唯一需注意的是它们必须接收一个(并且只是一个)迭代器性质的 positional 参数

3.声明itemLoaders

通过使用类定义语法,像item一样声明item loaders。这是一个例子:

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_processorItemLoader.default_output_processor属性声明默认的输入/输出处理器。

4.声明输入输出处理器

Declaring Input and Output Processors

如上一节所述,可以在Item Loader定义中声明输入和输出处理器,并且以这种方式声明输入处理器非常普遍。但是,还有一个地方可以指定要使用的输入和输出处理器:item field元数据中。这是一个例子:

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(),
    )
>>> from scrapy.loader import ItemLoader
>>> il = ItemLoader(item=Product())
>>> il.add_value('name', [u'Welcome to my', u'<strong>website</strong>'])
>>> il.add_value('price', [u'&euro;', u'<span>1000</span>'])
>>> il.load_item()
{'name': u'Welcome to my website', 'price': u'1000'}

输入和输出处理器的优先顺序如下:

5.Item Loader Context

Item Loader上下文是在Item Loader中所有输入和输出处理器之间共享的任意key/values的字典。可以在声明,实例化或使用Item Loader时传递。它们用于修改input/output处理器的行为。

例如,假设您有一个parse_length函数,该函数接收文本值并从中提取一个长度:

def parse_length(text, loader_context):
    unit = loader_context.get('unit', 'm')
    # ... length parsing code goes here ...
    return parsed_length

通过接受loader_context参数,该函数显式地告诉Item Loader它可以接收Item Loader上下文,Item Loader在调用它时传递当前活动的上下文,因此处理器函数(在这种情况下为parse_length)可以使用它们。

有几种方法可以修改Item Loader上下文值:

1.通过修改当前活动的Item Loader上下文(context属性):

loader = ItemLoader(product)
loader.context['unit'] = 'cm'

2.在Item Loader实例化上(Item Loader构造函数的关键字参数存储在Item Loader上下文中):

loader = ItemLoader(product, unit='cm')

3.在Item Loader声明上,对于那些支持使用Item Loader上下文实例化它们的input/output处理器。MapCompose是其中之一:

class ProductLoader(ItemLoader):
    length_out = MapCompose(parse_length, unit='cm')

6.ItemLoader objects

classscrapy.loader.ItemLoader([item, selector, response, ]**kwargs

返回一个新的item loader以填充给定的item。如果没有给出任何item,则使用default_item_class中的类自动实例化一个itme

当使用selectorresponse参数实例化时,ItemLoader类提供了使用selectors从网页提取数据的便捷机制。

Parameters

item, selector, response和其余关键字参数都分配给Loader上下文(可通过context属性访问)。

ItemLoader实例具有以下方法:

方法描述
get_value(value, *processors, **kwargs)通过给定的处理器和关键字参数处理给定的值。
add_value(field_name, value, *processors, **kwargs)为给定字段添加给定值
replace_value(field_name, value, *processors, **kwargs)add_value()类似,但用新值替换收集的数据,而不是添加新值
get_xpath(xpath, *processors, **kwargs)ItemLoader.get_value()类似,但接收XPath而不是,该值用于从与此ItemLoader关联的选择器中提取Unicode字符串列表。
add_xpath(field_name, xpath, *processors, **kwargs)ItemLoader.add_value()类似,但接收XPath而不是值,该值用于从与此ItemLoader关联的选择器中提取Unicode字符串列表。
replace_xpath(field_name, xpath, *processors, **kwargs)与add_xpath()类似,但是替换收集的数据而不是添加数据。
get_css(css, *processors, **kwargs)ItemLoader.get_value()类似,但是接收CSS选择器而不是值,该选择器用于从与此ItemLoader关联的选择器中提取Unicode字符串列表
add_css(field_name, css, *processors, **kwargs)ItemLoader.add_value()类似,但接收CSS选择器而不是值,该选择器用于从与此ItemLoader关联的选择器中提取Unicode字符串列表。
replace_css(field_name, css, *processors, **kwargs)add_css()类似,但替换收集的数据而不是添加数据。
load_item()使用到目前为止收集的数据填充项目,然后将其返回。收集的数据首先通过输出处理器传递,以获取最终值以分配给每个项目字段。
nested_xpath(xpath)使用xpath选择器创建一个嵌套的加载器。提供的选择器相对于与此ItemLoader关联的选择器应用。嵌套的加载器与父ItemLoader共享该Item,因此对add_xpath()add_value()replace_value()等的调用将按预期进行。
get_collected_values(field_name)返回给定字段的收集值
get_output_value(field_name)对于给定的字段,返回使用输出处理器解析的收集值。此方法完全不会填充或修改项目。
get_input_processor(field_name)返回给定字段的输入处理器
get_output_processor(field_name)返回给定字段的输出处理器

ItemLoader实例具有以下属性:

属性描述
item此Item Loader解析的Item对象
context此项目加载器的当前活动上下文
default_item_classItem类(或工厂),用于在构造函数中未指定时实例化项目
default_input_processor用于那些未指定一个字段的默认输入处理器
default_output_processor用于未指定一个字段的默认输出处理器
default_selector_class如果构造函数中仅给出响应,则用于构造此ItemLoader的选择器的类。如果在构造函数中指定了选择器,则忽略此属性。有时在子类中覆盖此属性
selector从中提取数据的Selector对象。它既可以是构造函数中指定的选择器,也可以是使用default_selector_class根据构造函数中指定的响应创建的选择器。此属性应为只读

7.嵌套加载器

Nested Loaders

从文档的子部分解析相关值时,创建嵌套的加载器可能很有用。假设您要从页面的页脚中提取详细信息,如下所示:

<footer>
    <a class="social" href="https://facebook.com/whatever">Like Us</a>
    <a class="social" href="https://twitter.com/whatever">Follow Us</a>
    <a class="email" href="mailto:[email protected]">Email Us</a>
</footer>

如果没有nested loaders,则需要为要提取的每个值指定完整的xpath(或css)

loader = ItemLoader(item=Item())
# load stuff not in the footer
loader.add_xpath('social', '//footer/a[@class = "social"]/@href')
loader.add_xpath('email', '//footer/a[@class = "email"]/@href')
loader.load_item()

相反,您可以使用页脚选择器创建一个nested loaders,并添加相对于页脚的值。功能相同,但避免重复页脚选择器。

loader = ItemLoader(item=Item())
# load stuff not in the footer
footer_loader = loader.nested_xpath('//footer')
footer_loader.add_xpath('social', 'a[@class = "social"]/@href')
footer_loader.add_xpath('email', 'a[@class = "email"]/@href')
# no need to call footer_loader.load_item()
loader.load_item()

您可以任意嵌套装载程序,它们可以与xpath或css选择器一起使用。作为一般准则,当嵌套加载器使您的代码更简单但又不会过多嵌套时,请使用嵌套加载器,否则解析器可能变得难以阅读。

8.覆盖和扩展Item Loaders

Reusing and extending Item Loaders

随着您的项目变得越来越大并获得越来越多的蜘蛛,维护成为一个基本问题,尤其是当您必须为每个蜘蛛处理许多不同的解析规则,有很多异常并且还想重用公共处理器时。

Item Loader旨在减轻解析规则的维护负担,而又不失去灵活性,同时,提供了一种方便的机制来扩展和覆盖它们。因此,Item Loader支持传统的Python类继承来处理特定蜘蛛(或蜘蛛组)的差异。

例如,假设某个特定的网站将其产品名称括在三个破折号中(例如— Plasma TV —),而您最终不想将这些破折号刮到最终产品名称中。

通过重新使用和扩展默认的Product Item Loader(ProductLoader),您可以按照以下方法删除这些破折号:

from scrapy.loader.processors import MapCompose
from myproject.ItemLoaders import ProductLoader

def strip_dashes(x):
    return x.strip('-')

class SiteSpecificLoader(ProductLoader):
    name_in = MapCompose(strip_dashes, ProductLoader.name_in)

扩展项目加载器可能非常有用的另一种情况是,当您具有多种源格式时,例如XML和HTML。在XML版本中,您可能要删除CDATA出现的地方。这是操作方法示例:

from scrapy.loader.processors import MapCompose
from myproject.ItemLoaders import ProductLoader
from myproject.utils.xml import remove_cdata

class XmlProductLoader(ProductLoader):
    name_in = MapCompose(remove_cdata, ProductLoader.name_in)

这就是通常扩展输入处理器的方式。

对于输出处理器,更常见的是在字段元数据中声明它们,因为它们通常仅取决于字段,而不取决于每个特定的站点解析规则(就像输入处理器一样)

还有许多其他可能的方式来扩展,继承和覆盖项目加载程序,并且不同的项目加载程序层次结构可能更适合不同的项目。Scrapy仅提供机制。它不会强加您的Loaders集合的任何特定组织-取决于您和您项目的需求

9.内置处理器

Available built-in processors

即使您可以使用任何可调用函数作为输入和输出处理器,Scrapy仍提供了一些常用的处理器,如下所述。其中一些函数,例如MapCompose(通常用作输入处理器)组成了按顺序执行的多个函数的输出,以生成最终的解析值

class scrapy.loader.processors.Identity

最简单的处理器,什么都不做。它返回原始值不变。它不接收任何构造函数参数,也不接受Loader上下文。

>>> from scrapy.loader.processors import Identity
>>> proc = Identity()
>>> proc(['one', 'two', 'three'])
['one', 'two', 'three']

class scrapy.loader.processors.TakeFirst

从接收到的值中返回第一个非空/非空值,因此它通常用作单值字段的输出处理器。它不接收任何构造函数参数,也不接受Loader上下文。

>>> from scrapy.loader.processors import TakeFirst
>>> proc = TakeFirst()
>>> proc(['', 'one', 'two', 'three'])
'one'

class scrapy.loader.processors.Join(separator=u’’)

返回与构造函数中给定的分隔符相连的值,默认为u''。它不接受Loader上下文。

使用默认分隔符时,此处理器等效于以下功能:u''.join

>>> from scrapy.loader.processors import Join
>>> proc = Join()
>>> proc(['one', 'two', 'three'])
'one two three'
>>> proc = Join('<br>')
>>> proc(['one', 'two', 'three'])
'one<br>two<br>three'

class scrapy.loader.processors.Compose(*functions, **default_loader_context)

根据给定功能的组成构造的处理器。这意味着该处理器的每个输入值将传递给第一个函数,该函数的结果将传递给第二个函数,依此类推,直到最后一个函数返回该处理器的输出值为止。

默认情况下,在None值上停止进程。可以通过传递关键字参数stop_on_none = False来更改此行为。

>>> from scrapy.loader.processors import Compose
>>> proc = Compose(lambda v: v[0], str.upper)
>>> proc(['hello', 'world'])
'HELLO'

每个函数可以选择接收loader_context参数。对于那些这样做的处理器,此处理器将通过该参数传递当前活动的Loader上下文。

构造函数中传递的关键字参数用作传递给每个函数调用的默认Loader上下文值。但是,传递给函数的最终Loader上下文值将被可通过ItemLoader.context()属性访问的当前活动Loader上下文覆盖。

class scrapy.loader.processors.MapCompose(*functions, **default_loader_context)

由给定功能的组成构造的处理器,类似于Compose处理器。与该处理器的区别在于内部结果在函数之间传递的方式如下:

迭代此处理器的输入值,并将第一个功能应用于每个元素。这些函数调用的结果(每个元素一个)被串联以构造一个新的Iterable,然后将其应用于第二个函数,依此类推,直到最后一个函数应用于所收集的值列表中的每个值,这样远。最后一个函数的输出值连接在一起以产生此处理器的输出

每个特定的函数都可以返回一个值或一个值列表,该值或值列表将由应用到其他输入值的同一函数返回的值列表展平。这些函数还可以返回None,在这种情况下,该函数的输出将被忽略,以进行链上的进一步处理。

该处理器提供了一种简便的方法来组合仅使用单个值(而不是可迭代值)的函数。由于这个原因,MapCompose处理器通常用作输入处理器,因为数据通常是使用选择器的extract()方法提取的,该方法返回一个Unicode字符串列表

>>> def filter_world(x):
...     return None if x == 'world' else x
...
>>> from scrapy.loader.processors import MapCompose
>>> proc = MapCompose(filter_world, str.upper)
>>> proc(['hello', 'world', 'this', 'is', 'scrapy'])
['HELLO, 'THIS', 'IS', 'SCRAPY']

class scrapy.loader.processors.SelectJmes(json_path)

使用提供给构造函数的json路径查询值并返回输出。需要jmespathhttps://github.com/jmespath/jmespath.py才能运行。该处理器一次仅接受一个输入。

>>> from scrapy.loader.processors import SelectJmes, Compose, MapCompose
>>> proc = SelectJmes("foo") #for direct use on lists and dictionaries
>>> proc({'foo': 'bar'})
'bar'
>>> proc({'foo': {'bar': 'baz'}})
{'bar': 'baz'}

使用Json:

>>> import json
>>> proc_single_json_str = Compose(json.loads, SelectJmes("foo"))
>>> proc_single_json_str('{"foo": "bar"}')
'bar'
>>> proc_json_list = Compose(json.loads, MapCompose(SelectJmes('foo')))
>>> proc_json_list('[{"foo":"bar"}, {"baz":"tar"}]')
['bar']

Scrapy 1.7

Scrapy 安装并创建运行爬虫项目(1) Scrapy 命令行工具(2) Scrapy 选择器(3) Scrapy Item(4) Scrapy Item Loaders(5) Scrapy Item Pipeline(6) Scrapy Feed exports(7) Scrapy 架构概览(8) Scrapy 手写一个爬虫(9) Scrapy 知乎模拟登陆 (10) Scrapy源码分析 架构概览(1) Scrapy源码分析 运行入口(2) Scrapy源码分析 核心组件初始化(3) Scrapy源码分析 核心抓取流程(4)