Item Loaders

物料装载器提供了一种方便的机制来填充报废的物料 . 即使可以使用其自己的类似于字典的API填充Item,但通过自动执行一些常见任务(例如在分配原始数据之前解析原始提取的数据),Item Loader仍提供了更为方便的API,用于从刮除过程填充它们.

换句话说, 项目提供了抓取数据的容器 ,而项目加载器则提供了填充该容器的机制.

项加载器旨在提供一种灵活,高效且容易的机制,以扩展或覆盖不同的字段解析规则(无论是蜘蛛还是源格式(HTML,XML等)),而不会造成维护的噩梦.

Using Item Loaders to populate items

要使用Item Loader,必须首先实例化它. 您可以使用类似dict的对象(例如Item或dict)实例化它,也可以不使用它,在这种情况下,将使用ItemLoader.default_item_class属性中指定的Item类在Item Loader构造函数中自动实例化Item.

然后,通常使用Selectors开始将值收集到Item Loader中. 您可以在同一项目字段中添加多个值; Item Loader将知道如何稍后使用适当的处理功能将这些值"联接".

Note

收集的数据在内部存储为列表,从而可以将多个值添加到同一字段. 如果在创建加载程序时传递了item参数,则每个项目的值如果已经是可迭代的,则将按原样存储;如果是单个值,则将其包装在列表中.

这是一个典型的项目装载机使用的蜘蛛 ,用产品项目在申报项目章节

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位置提取的:

  1. //div[@class="product_name"]
  2. //div[@class="product_title"]

换句话说,通过使用add_xpath()方法从两个XPath位置提取数据来收集数据. 这是稍后将分配给name字段的数据.

之后,对pricestock字段使用类似的调用(后者使用CSS选择器的add_css()方法),最后,使用不同的方法(即add_value()直接用文字值( today )填充last_update字段.

最后,当收集ItemLoader.load_item()所有数据后,将ItemLoader.load_item()方法,该方法实际上返回使用先前通过add_xpath()add_css()add_value()调用提取并收集的数据填充的项目.

Input and Output processors

一个项目加载器为每个(项目)字段包含一个输入处理器和一个输出处理器. 输入处理器一旦接收到提取的数据,便会对其进行处理(通过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)

所以发生的是:

  1. Data from xpath1 is extracted, and passed through the 输入处理器 of the name field. The result of the input processor is collected and kept in the Item Loader (but not yet assigned to the item).
  2. 提取来自xpath2数据,并通过(1)中使用的相同输入处理器 . 输入处理器的结果将附加到(1)中收集的数据(如果有).
  3. 除了从css CSS选择器中提取数据并通过(1)和(2)中使用的相同输入处理器外,这种情况与前面的情况相似. 输入处理器的结果将附加到在(1)和(2)中收集的数据(如果有).
  4. 除了要直接分配要收集的值,而不是从XPath表达式或CSS选择器中提取值之外,这种情况也与前面的情况类似. 但是,该值仍通过输入处理器传递. 在这种情况下,由于该值不可迭代,因此在将其传递给输入处理器之前,它将转换为单个元素的可迭代,因为输入处理器始终会接收可迭代.
  5. 在步骤(1),(2),(3)和(4)中收集的数据通过name字段的输出处理器传递. 输出处理器的结果是分配给项目中name字段的值.

值得注意的是,处理器只是可调用的对象,将与要解析的数据一起调用,并返回已解析的值. 因此,您可以使用任何功能作为输入或输出处理器. 唯一的要求是他们必须接受一个(并且只有一个)位置参数,这将是可迭代的.

Note

输入和输出处理器都必须接收一个iterable作为其第一个参数. 这些函数的输出可以是任何东西. 输入处理器的结果将被附加到内部列表(在Loader中),该列表包含(针对该字段)收集的值. 输出处理器的结果是最终将分配给该项目的值.

如果要使用普通函数作为处理器,请确保其将self作为第一个参数:

def lowercase_processor(self, values):
    for v in values:
        yield v.lower()

class MyItemLoader(ItemLoader):
    name_in = lowercase_processor

这是因为只要将函数分配为类变量,它就会成为方法,并且在调用时将实例作为第一个参数传递. 有关更多详细信息,请参见关于stackoverflow的答案 .

您需要记住的另一件事是,输入处理器返回的值是在内部(在列表中)收集的,然后传递给输出处理器以填充字段.

最后但并非最不重要的一点是,Scrapy内置了一些常用的处理器 ,以方便使用.

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

Declaring Input and Output Processors

如上一节所述,可以在Item Loader定义中声明输入和输出处理器,并且以这种方式声明输入处理器是很常见的. 但是,还有一个地方可以指定要使用的输入和输出处理器:" 项目字段"元数据中. 这是一个例子:

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'}

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

  1. Item Loader特定于字段的属性: field_infield_out (最高优先级)
  2. 字段元数据( input_processoroutput_processor键)
  3. Item Loader的默认设置: ItemLoader.default_input_processor()ItemLoader.default_output_processor() (最低优先级)

另请参阅: 重用和扩展项目加载器 .

Item Loader Context

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

例如,假设您有一个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上下文实例化它们的输入/输出处理器. MapCompose是其中之一:

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

ItemLoader objects

class scrapy.loader.ItemLoader([item, selector, response, ]**kwargs)

返回一个新的项目加载器以填充给定的项目. 如果没有给出任何项目,则使用default_item_class的类自动实例化一个项目.

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

Parameters:

项目,选择器,响应和其余关键字参数都分配给Loader上下文(可通过context属性访问).

ItemLoader实例具有以下方法:

get_value(value, *processors, **kwargs)

通过给定的processors和关键字参数处理给定的value .

可用的关键字参数:

Parameters: restr 或已 编译的regex )–正则表达式,用于使用extract_regex()方法从给定值中提取数据,应用于处理器之前

Examples:

>>> from scrapy.loader.processors import TakeFirst
>>> loader.get_value(u'name: foo', TakeFirst(), unicode.upper, re='name: (.+)')
'FOO`
add_value(field_name, value, *processors, **kwargs)

处理,然后为给定字段添加给定value .

该值首先通过给processorskwargs通过get_value()传递,然后通过字段输入处理器传递,并且其结果附加到为该字段收集的数据中. 如果该字段已经包含收集的数据,则添加新数据.

给定的field_name可以为None ,在这种情况下,可以添加多个字段的值. 并且处理后的值应该是将field_name映射到值的字典.

Examples:

loader.add_value('name', u'Color TV')
loader.add_value('colours', [u'white', u'blue'])
loader.add_value('length', u'100')
loader.add_value('name', u'name: foo', TakeFirst(), re='name: (.+)')
loader.add_value(None, {'name': u'foo', 'sex': u'male'})
replace_value(field_name, value, *processors, **kwargs)

add_value()类似,但用新值替换收集的数据,而不是添加新值.

get_xpath(xpath, *processors, **kwargs)

ItemLoader.get_value()类似,但接收XPath而不是值,该值用于从与此ItemLoader关联的选择器中提取Unicode字符串列表.

Parameters:
  • xpath (str) – the XPath to extract data from
  • re (str or 编译正则表达式) – a regular expression to use for extracting data from the selected XPath region

Examples:

# HTML snippet: <p class="product-name">Color TV</p>
loader.get_xpath('//p[@class="product-name"]')
# HTML snippet: <p id="price">the price is $1200</p>
loader.get_xpath('//p[@id="price"]', TakeFirst(), re='the price is (.*)')
add_xpath(field_name, xpath, *processors, **kwargs)

ItemLoader.add_value()类似,但接收XPath而不是值,该值用于从与此ItemLoader关联的选择器中提取Unicode字符串列表.

See get_xpath() for kwargs.

Parameters:xpath (str) – the XPath to extract data from

Examples:

# HTML snippet: <p class="product-name">Color TV</p>
loader.add_xpath('name', '//p[@class="product-name"]')
# HTML snippet: <p id="price">the price is $1200</p>
loader.add_xpath('price', '//p[@id="price"]', re='the price is (.*)')
replace_xpath(field_name, xpath, *processors, **kwargs)

add_xpath()类似,但是替换收集的数据而不是添加数据.

get_css(css, *processors, **kwargs)

ItemLoader.get_value()类似,但接收CSS选择器而不是值,该选择器用于从与此ItemLoader关联的选择器中提取Unicode字符串列表.

Parameters:
  • cssstr )–从中提取数据的CSS选择器
  • restr 或已 编译的regex )–用于从所选CSS区域提取数据的正则表达式

Examples:

# HTML snippet: <p class="product-name">Color TV</p>
loader.get_css('p.product-name')
# HTML snippet: <p id="price">the price is $1200</p>
loader.get_css('p#price', TakeFirst(), re='the price is (.*)')
add_css(field_name, css, *processors, **kwargs)

ItemLoader.add_value()类似,但接收CSS选择器而不是值,该选择器用于从与此ItemLoader关联的选择器中提取Unicode字符串列表.

See get_css() for kwargs.

Parameters: cssstr )–从中提取数据的CSS选择器

Examples:

# HTML snippet: <p class="product-name">Color TV</p>
loader.add_css('name', 'p.product-name')
# HTML snippet: <p id="price">the price is $1200</p>
loader.add_css('price', 'p#price', re='the price is (.*)')
replace_css(field_name, css, *processors, **kwargs)

add_css()类似,但替换收集的数据而不是添加数据.

load_item()

Populate the item with the data collected so far, and return it. The data collected is first passed through the output processors to get the final value to assign to each item field.

nested_xpath(xpath)

使用xpath选择器创建一个嵌套的加载器. 提供的选择器相对于与此ItemLoader关联的选择器应用. 嵌套的加载器与父ItemLoader共享该Item ,因此对add_xpath()add_value()replace_value()等的调用add_xpath()预期进行.

nested_css(css)

使用CSS选择器创建嵌套的加载器. 提供的选择器相对于与此ItemLoader关联的选择器应用. 嵌套的加载器与父ItemLoader共享该Item ,因此对add_xpath()add_value()replace_value()等的调用add_xpath()预期进行.

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_class

Item类(或工厂),用于在构造函数中未指定时实例化项目.

default_input_processor

用于未指定一个字段的默认输入处理器.

default_output_processor

用于那些未指定字段的默认输出处理器.

default_selector_class

如果构造函数中仅给出响应,则用于构造此ItemLoaderselector的类. 如果在构造函数中指定了选择器,则忽略此属性. 有时在子类中覆盖此属性.

selector

从中提取数据的Selector对象. 它可以是构造函数中指定的选择器,也可以是使用default_selector_class从构造函数中指定的响应创建的选择器. 此属性应为只读.

Nested Loaders

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

Example:

<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>

如果没有嵌套的加载器,则需要为要提取的每个值指定完整的xpath(或css).

Example:

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()

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

Example:

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选择器一起使用. 作为一般准则,当嵌套加载器使您的代码更简单但又不会过多嵌套时,请使用嵌套加载器,否则解析器可能变得难以阅读.

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集合的任何特定组织-取决于您和您项目的需要.

Available built-in processors

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

这是所有内置处理器的列表:

class scrapy.loader.processors.Identity

最简单的处理器,不执行任何操作. 它返回原始值不变. 它不接收任何构造函数参数,也不接受Loader上下文.

Example:

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

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

Example:

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

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

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

Examples:

>>> 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来更改此行为.

Example:

>>> 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']

与Compose处理器一样,函数可以接收Loader上下文,并且构造函数关键字参数用作默认上下文值. 见Compose处理器的更多信息.

class scrapy.loader.processors.SelectJmes(json_path)

Queries the value using the json path provided to the constructor and returns the output. Requires jmespath (https://github.com/jmespath/jmespath.py) to run. This processor takes only one input at a time.

Example:

>>> 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']