项目加载器

物品装载机为填充刮削物提供了一种方便的机制。 items . 尽管可以直接填充项,但是条目加载器通过自动执行一些常见任务(如在分配原始提取数据之前对其进行解析),为从抓取过程填充它们提供了一个更为方便的API。

换言之, items 提供 容器 当项目加载器为 人口增长 那个容器。

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

注解

项目加载器是 itemloaders 库,通过添加对 responses .

使用项目加载器填充项目

要使用项加载器,必须首先实例化它。您可以使用 item object 或者没有,在这种情况下 item object 在项目加载器中自动创建 __init__ 方法使用 item 中指定的类 ItemLoader.default_item_class 属性。

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

注解

收集的数据以列表的形式在内部存储,允许向同一字段添加多个值。如果 item 参数是在创建加载程序时传递的,如果项的每个值已经是iterable,则将按原样存储;如果是单个值,则将用列表包装。

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

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() 方法。这是将分配给 name 以后再说。

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

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

使用dataclass项

默认情况下, dataclass items 创建时要求传递所有字段。在使用带有项加载器的dataclass项时,这可能是一个问题:除非将预先填充的项传递给加载程序,否则将使用加载程序的 add_xpath()add_css()add_value() 方法。

克服这个问题的一种方法是使用 field() 函数,带有 default 论点:

from dataclasses import dataclass, field
from typing import Optional

@dataclass
class InventoryItem:
    name: Optional[str] = field(default=None)
    price: Optional[float] = field(default=None)
    stock: Optional[int] = field(default=None)

输入和输出处理器

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

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

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. 数据来自 xpath1 提取并通过 输入处理器name 字段。输入处理器的结果被收集并保存在项目加载器中(但尚未分配给项目)。

  2. 数据来自 xpath2 提取并通过 输入处理器 用于(1)。输入处理器的结果将附加到(1)中收集的数据(如果有)中。

  3. 这种情况与以前的情况类似,只是数据是从 css 并通过相同的 输入处理器 用于(1)和(2)。输入处理器的结果将附加到(1)和(2)中收集的数据(如果有)中。

  4. 这种情况也与前面的情况类似,只是要收集的值是直接分配的,而不是从xpath表达式或css选择器中提取的。但是,该值仍然通过输入处理器传递。在这种情况下,由于该值不可重设,因此在将其传递给输入处理器之前,它将转换为单个元素的可重设值,因为输入处理器始终接收可重设值。

  5. 步骤(1)、(2)、(3)和(4)中收集的数据通过 输出处理器name 字段。输出处理器的结果是分配给 name 项目中的字段。

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

在 2.0 版更改: 处理器不再需要是方法。

注解

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

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

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

声明项加载器

项加载器使用类定义语法声明。举个例子:

from itemloaders.processors import TakeFirst, MapCompose, Join
from scrapy.loader import ItemLoader

class ProductLoader(ItemLoader):

    default_output_processor = TakeFirst()

    name_in = MapCompose(str.title)
    name_out = Join()

    price_in = MapCompose(str.strip)

    # ...

如您所见,输入处理器使用 _in 当输出处理器使用 _out 后缀。您还可以使用 ItemLoader.default_input_processorItemLoader.default_output_processor 属性。

声明输入和输出处理器

如前一节所述,可以在项目加载器定义中声明输入和输出处理器,并且用这种方式声明输入处理器是非常常见的。但是,还有一个地方可以指定要使用的输入和输出处理器:在 Item Field 元数据。下面是一个例子:

import scrapy
from itemloaders.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', ['Welcome to my', '<strong>website</strong>'])
>>> il.add_value('price', ['&euro;', '<span>1000</span>'])
>>> il.load_item()
{'name': 'Welcome to my website', 'price': '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'
    
  2. 项目加载器实例化(项目加载器的关键字参数 __init__ 方法存储在项加载器上下文中)::

    loader = ItemLoader(product, unit='cm')
    
  3. 在项目加载器声明中,用于那些支持用项目加载器上下文实例化它们的输入/输出处理器。 MapCompose 是其中之一:

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

项加载器对象

嵌套装载机

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

例子::

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

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

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

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

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

下面介绍如何通过重用和扩展默认的产品项加载器来删除这些破折号。 (ProductLoader ):

from itemloaders.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 itemloaders.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只提供了这种机制;它不会强制任何特定的装载机集合组织——这取决于您和项目的需要。