译者:OSGeo 中国

物品装载机为填充抓取物提供了一种方便的机制。 Items . 即使可以使用它们自己的字典(如API)来填充项,项加载器也提供了一个更方便的API,通过自动化一些常见任务(如在分配原始提取数据之前对其进行解析)来从抓取过程填充项。

换言之, Items 提供 container 当项目加载器为 populating 那个容器。

项目加载器的设计目的是提供一种灵活、高效和简单的机制,通过 Spider 或源格式(HTML、XML等)扩展和重写不同的字段解析规则,而不会成为维护的噩梦。

使用项目加载器填充项目

要使用项目加载器,必须首先实例化它。可以使用类似dict的对象(例如item或dict)或不使用对象来实例化它,在这种情况下,将使用在 ItemLoader.default_item_class 属性。

然后,开始将值收集到项加载器中,通常使用 Selectors . 您可以向同一个项目字段添加多个值;项目加载器稍后将知道如何使用适当的处理函数“联接”这些值。

下面是在 Spider ,使用 Product item Items chapter ::

  1. from scrapy.loader import ItemLoader
  2. from myproject.items import Product
  3. def parse(self, response):
  4. l = ItemLoader(item=Product(), response=response)
  5. l.add_xpath('name', '//div[@class="product_name"]')
  6. l.add_xpath('name', '//div[@class="product_title"]')
  7. l.add_xpath('price', '//p[@id="price"]')
  8. l.add_css('stock', 'p#stock]')
  9. l.add_value('last_updated', 'today') # you can also use literal values
  10. return l.load_item()

通过快速查看该代码,我们可以看到 name 正在从页面中的两个不同的xpath位置提取字段:

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

换句话说,通过使用 add_xpath() 方法。这是将分配给 name 以后再说。

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

最后,当收集所有数据时, ItemLoader.load_item() 方法,它实际返回用以前提取和收集的数据填充的项 add_xpath()add_css()add_value() 电话。

输入和输出处理器

项目加载器为每个(项目)字段包含一个输入处理器和一个输出处理器。输入处理器一旦接收到提取的数据(通过 add_xpath()add_css()add_value() 方法),输入处理器的结果被收集并保存在itemloader中。在收集所有数据之后, ItemLoader.load_item() 方法来填充和获取填充的 Item 对象。这就是使用先前收集的数据(并使用输入处理器处理)调用输出处理器的时候。输出处理器的结果是分配给该项的最终值。

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

  1. l = ItemLoader(Product(), some_selector)
  2. l.add_xpath('name', xpath1) # (1)
  3. l.add_xpath('name', xpath2) # (2)
  4. l.add_css('name', css) # (3)
  5. l.add_value('name', 'test') # (4)
  6. return l.load_item() # (5)

所以发生的是:

  1. 数据来自 xpath1 提取并通过 input processorname 字段。输入处理器的结果被收集并保存在项目加载器中(但尚未分配给项目)。
  2. 数据来自 xpath2 提取并通过 input processor 用于(1)。输入处理器的结果将附加到(1)中收集的数据(如果有)中。
  3. 这种情况与以前的情况类似,只是数据是从 css 并通过相同的 input processor 用于(1)和(2)。输入处理器的结果将附加到(1)和(2)中收集的数据(如果有)中。
  4. 这种情况也与前面的情况类似,只是要收集的值是直接分配的,而不是从xpath表达式或css选择器中提取的。但是,该值仍然通过输入处理器传递。在这种情况下,由于该值不可重设,因此在将其传递给输入处理器之前,它将转换为单个元素的可重设值,因为输入处理器始终接收可重设值。
  5. 步骤(1)、(2)、(3)和(4)中收集的数据通过 output processorname 字段。输出处理器的结果是分配给 name 项目中的字段。

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

注解

输入和输出处理器都必须接收一个迭代器作为它们的第一个参数。这些函数的输出可以是任何东西。输入处理器的结果将附加到包含收集值(针对该字段)的内部列表(在加载程序中)。输出处理器的结果是最终分配给该项的值。

如果要将普通函数用作处理器,请确保它接收 self 作为第一个论点:

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

class MyItemLoader(ItemLoader):
    name_in = lowercase_processor

这是因为每当函数被指定为类变量时,它就会成为一个方法,并在被调用时作为第一个参数传递实例。见 this answer on stackoverflow 了解更多详细信息。

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

最后,但并非最不重要的是,Scrapy有一些 commonly used processors 内置方便。

声明项加载器

通过使用类定义语法,将项加载器声明为项。下面是一个例子:

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 属性。

声明输入和输出处理器

如前一节所述,可以在项目加载器定义中声明输入和输出处理器,并且用这种方式声明输入处理器是非常常见的。但是,还有一个地方可以指定要使用的输入和输出处理器:在 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'}

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

  1. 项目加载器字段特定属性: field_infield_out (最优先)
  2. 字段元数据( input_processoroutput_processor 关键)
  3. 项目加载器默认值: ItemLoader.default_input_processor()ItemLoader.default_output_processor() (最低优先级)

参见: 重复使用和扩展项目加载器 .

项目加载器上下文

项目加载器上下文是任意键/值的dict,在项目加载器中的所有输入和输出处理器之间共享。它可以在声明、实例化或使用项加载器时传递。它们用于修改输入/输出处理器的行为。

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

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

接受一个 loader_context 参数该函数显式地告诉项加载器它能够接收项加载器上下文,因此项加载器在调用时传递当前活动的上下文和处理器函数( parse_length 在这种情况下)可以使用它们。

修改项目加载器上下文值有几种方法:

  1. 通过修改当前活动的项加载器上下文( context 属性):
    loader = ItemLoader(product)
    loader.context['unit'] = 'cm'
    
  1. 在项加载器实例化时(项加载器构造函数的关键字参数存储在项加载器上下文中)::
    loader = ItemLoader(product, unit='cm')
    
  1. 在项目加载器声明中,用于那些支持用项目加载器上下文实例化它们的输入/输出处理器。 MapCompose 是其中之一:
    class ProductLoader(ItemLoader):
     length_out = MapCompose(parse_length, unit='cm')
    

项加载器对象

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

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

当用 selector 或A response 参数: ItemLoader 类提供了方便的机制,可以使用 selectors .

| 参数: |

item、selector、response和剩余的关键字参数被分配给加载程序上下文(可通过 context 属性)。

ItemLoader 实例具有以下方法:

get_value(value, *processors, **kwargs)

处理给定的 value 根据给定的 processors 和关键字参数。

可用关键字参数:

参数: re (str or compiled regex) — 用于从给定值提取数据的正则表达式,使用 extract_regex() 方法,在处理器之前应用

实例:

>>> 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 对于给定字段。

首先传递值 get_value() 通过给予 processorskwargs ,然后通过 field input processor 其结果附加到为该字段收集的数据中。如果字段已包含收集的数据,则添加新数据。

给定的 field_name 可以是 None ,在这种情况下,可以添加多个字段的值。处理后的值应该是字段名映射到值的dict。

实例:

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而不是一个值,该值用于从与此关联的选择器中提取Unicode字符串列表。 ItemLoader .

| 参数: |

  • xpath (str) — 要从中提取数据的xpath
  • re (str or compiled regex) — 用于从选定的xpath区域提取数据的正则表达式 | | | —- |

实例:

# 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而不是一个值,该值用于从与此关联的选择器中提取Unicode字符串列表。 ItemLoader .

get_xpath() 对于 kwargs .

参数: xpath (str) — 要从中提取数据的xpath

实例:

# 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选择器而不是值,该值用于从与此关联的选择器中提取Unicode字符串列表 ItemLoader .

| 参数: |

  • css (str) — 从中提取数据的CSS选择器
  • re (str or compiled regex) — 用于从选定的CSS区域提取数据的正则表达式 | | | —- |

实例:

# 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选择器而不是值,该值用于从与此关联的选择器中提取Unicode字符串列表 ItemLoader .

get_css() 对于 kwargs .

参数: css (str) — 从中提取数据的CSS选择器

实例:

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

用迄今收集的数据填充该项,然后返回它。收集的数据首先通过 output processors 获取要分配给每个项字段的最终值。

nested_xpath(xpath)

使用xpath选择器创建嵌套加载程序。提供的选择器将相对于与此关联的选择器应用 ItemLoader . 嵌套加载程序共享 Item 与父母 ItemLoader 所以呼吁 add_xpath()add_value()replace_value() 等将按预期工作。

nested_css(css)

使用CSS选择器创建嵌套加载程序。提供的选择器将相对于与此关联的选择器应用 ItemLoader . 嵌套加载程序共享 Item 与父母 ItemLoader 所以呼吁 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 此项加载器正在分析的对象。

context

当前活动的 Context 此项目加载器的。

default_item_class

项类(或工厂),用于在构造函数中未给定项时实例化项。

default_input_processor

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

default_output_processor

用于未指定字段的默认输出处理器。

default_selector_class

用于构造 selector 其中 ItemLoader ,如果构造函数中只给出响应。如果在构造函数中给定了选择器,则忽略此属性。此属性有时在子类中被重写。

selector

这个 Selector 从中提取数据的对象。它要么是构造函数中给定的选择器,要么是使用 default_selector_class . 此属性是只读的。

嵌套装载机

从文档的子部分分析相关值时,创建嵌套加载器可能很有用。假设您正在从一个页面的页脚提取细节,该页面的外观如下:

例子::

<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:whatever@example.com">Email Us</a>
</footer>

如果没有嵌套加载程序,则需要为要提取的每个值指定完整的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()

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

例子::

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选择器。作为一般准则,当嵌套加载器使您的代码更简单,但不要过度嵌套,否则您的解析器可能会变得难以读取。

重复使用和扩展项目加载器

随着项目规模的扩大和 Spider 数量的增加,维护成为一个基本问题,特别是当您必须为每个 Spider 处理许多不同的解析规则时,有许多异常,但也希望重用公共处理器。

项目加载器旨在减轻解析规则的维护负担,而不会失去灵活性,同时为扩展和重写规则提供了方便的机制。因此,项目加载器支持传统的Python类继承来处理特定spider(或spider组)的差异。

例如,假设某个特定站点用三个破折号(例如 ---Plasma TV--- )你不想最后在最终的产品名称中删除这些破折号。

下面介绍如何通过重用和扩展默认的产品项加载器来删除这些破折号。( 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只提供了这种机制;它不会强制任何特定的装载机集合组织——这取决于您和项目的需要。

可用的内置处理器

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

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

class scrapy.loader.processors.Identity

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

例子::

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

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

例子::

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

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

使用默认分隔符时,此处理器相当于以下函数: 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 context 通过那个参数。

构造函数中传递的关键字参数用作传递给每个函数调用的默认加载程序上下文值。但是,传递给函数的最终加载器上下文值将被通过 ItemLoader.context() 属性。

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

一种由给定函数组成的处理器,类似于 Compose 处理器。与此处理器不同的是内部结果在函数之间传递的方式,如下所示:

此处理器的输入值为 iterated 第一个函数应用于每个元素。这些函数调用的结果(每个元素一个)被连接起来,以构造一个新的iterable,然后使用它来应用第二个函数,依此类推,直到最后一个函数应用到迄今为止收集的值列表的每个值为止。最后一个函数的输出值被连接在一起以产生这个处理器的输出。

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

该处理器提供了一种方便的方法来组合只使用单个值(而不是iterables)的函数。因此, MapCompose 处理器通常用作输入处理器,因为通常使用 extract() 方法 selectors ,返回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 处理器获取更多信息。

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.

例子::

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