Lzh on GitHub

Item Loaders(项目加载器)

Item Loaders 提供了一种方便的机制来填充抓取到的项目(items)。尽管项目可以直接填充,但 Item Loaders 通过自动化一些常见任务(例如在分配之前解析原始提取数据)提供了一种更方便的 API 来从抓取过程中填充它们。

换句话说,项目提供了抓取数据的容器,而 Item Loaders 提供了填充该容器的机制。

Item Loaders 旨在提供一种灵活、高效且易于扩展和覆盖不同字段解析规则的机制,无论是通过爬虫还是通过源格式(HTML、XML 等),而不会变成维护的噩梦。

注意

Item Loaders 是 itemloaders 库的扩展,通过添加对响应的支持,使 Scrapy 更易于使用。

使用 Item Loaders 填充项目

要使用 Item Loader,您必须首先实例化它。您可以带一个项目对象实例化它,也可以不带,在后一种情况下,ItemLoader.init 方法中会自动使用 ItemLoader.default_item_class 属性中指定的 item 类创建一个项目对象

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

注意

收集到的数据在内部存储为列表,允许向同一个字段添加多个值。如果在创建加载器时传递了 item 参数,则如果项目的每个值已经是可迭代的,它将按原样存储;如果它是单个值,则会用列表包装。

以下是在 Spider 中使用 Item Loader 的典型用法,使用项目章节中声明的 Product 项目

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

  • //div[@class="product_name"]
  • //div[@class="product_title"]

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

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

最后,当所有数据都收集完毕时,调用 ItemLoader.load_item() 方法,该方法实际上返回用之前使用 add_xpath()add_css()add_value() 调用提取和收集的数据填充的项目。

使用 dataclass 项目

从 2.2 版开始新增。

默认情况下,dataclass 项目要求在创建时传递所有字段。这在使用 dataclass 项目与 Item Loaders 时可能会成为问题:除非将预填充的项目传递给加载器,否则字段将使用加载器的 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)

输入和输出处理器

一个 Item Loader 为每个(项目)字段包含一个输入处理器和一个输出处理器。输入处理器在接收到提取数据后立即处理(通过 add_xpath()add_css()add_value() 方法),输入处理器的结果被收集并保存在 ItemLoader 内部。收集所有数据后,调用 ItemLoader.load_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. xpath1 中提取数据,并通过 name 字段的输入处理器。输入处理器的结果被收集并保存在 Item Loader 中(但尚未分配给项目)。
  2. xpath2 中提取数据,并通过与 (1) 中相同的输入处理器。输入处理器的结果被追加到 (1) 中收集的数据(如果有)。
  3. 此情况类似于前两种情况,不同之处在于数据是从 css CSS 选择器中提取的,并通过与 (1) 和 (2) 中相同的输入处理器。输入处理器的结果被追加到 (1) 和 (2) 中收集的数据(如果有)。
  4. 此情况也类似于前几种情况,不同之处在于要收集的值是直接分配的,而不是从 XPath 表达式或 CSS 选择器中提取的。但是,该值仍然通过输入处理器。在这种情况下,由于该值不可迭代,因此在将其传递给输入处理器之前将其转换为单个元素的迭代器,因为输入处理器始终接收迭代器。
  5. 步骤 (1)、(2)、(3) 和 (4) 中收集的数据通过 name 字段的输出处理器。输出处理器的结果是分配给项目中 name 字段的值。

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

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

注意

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

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

最后但同样重要的是,itemloaders 附带了一些方便的常用内置处理器

声明 Item Loaders

Item Loaders 使用类定义语法声明。这是一个示例:

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 Loader 定义中声明,并且以这种方式声明输入处理器非常常见。但是,还有一个地方可以指定要使用的输入和输出处理器:在 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. Item Loader 字段特定属性:field_infield_out(最高优先级)
  2. 字段元数据(input_processoroutput_processor 键)
  3. Item Loader 默认值:ItemLoader.default_input_processor()ItemLoader.default_output_processor()(最低优先级)

另请参阅:重用和扩展 Item Loaders

Item Loader 上下文

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 上下文值:

  • 通过修改当前活动的 Item Loader 上下文(context 属性):
    loader = ItemLoader(product)
    loader.context["unit"] = "cm"
    
  • 在 Item Loader 实例化时(Item Loader init 方法的关键字参数存储在 Item Loader 上下文中):
    loader = ItemLoader(product, unit="cm")
    
  • 在 Item Loader 声明时,对于那些支持使用 Item Loader 上下文实例化它们的输入/输出处理器。MapCompose 就是其中之一:
    class ProductLoader(ItemLoader):
        length_out = MapCompose(parse_length, unit="cm")
    

ItemLoader 对象

class scrapy.loader.ItemLoader(item: Any = None, selector: Selector | None = None, response: TextResponse | None = None, parent: itemloaders.ItemLoader | None = None, **context: Any)[source]

一个用户友好的抽象,通过将字段处理器应用于抓取到的数据来填充项目。当使用 selectorresponse 实例化时,它支持使用选择器从网页中提取数据。

参数:

  • item (scrapy.item.Item) – 用于通过后续调用 add_xpath()add_css()add_value() 填充的项目实例。
  • selector (Selector 对象) – 当使用 add_xpath()add_css()replace_xpath()replace_css() 方法时,从中提取数据的选择器。
  • response (Response 对象) – 用于使用 default_selector_class 构造选择器的响应,除非提供了 selector 参数,在这种情况下此参数将被忽略。

如果没有给出 item,则会自动使用 default_item_class 中的类实例化一个。

itemselectorresponse 和其余的关键字参数被分配给 Loader 上下文(可以通过 context 属性访问)。

  • item 此 Item Loader 正在解析的项目对象。这主要用作属性,因此,当尝试覆盖此值时,您可能首先要检查 default_item_class
  • context 此 Item Loader 当前活动的上下文
  • default_item_class 一个项目类(或工厂),用于在 __init__ 方法中未给出项目时实例化项目。
  • default_input_processor 那些未指定输入处理器的字段的默认输入处理器。
  • default_output_processor 那些未指定输出处理器的字段的默认输出处理器。
  • default_selector_class 用于构造此 ItemLoaderselector 的类,如果 __init__ 方法中仅给出了响应。如果在 __init__ 方法中给出了选择器,则此属性将被忽略。此属性有时在子类中被覆盖。
  • selector 从中提取数据的 Selector 对象。它是 __init__ 方法中给出的选择器,或者是使用 default_selector_class__init__ 方法中给出的响应创建的选择器。此属性旨在为只读。

add_css(field_name: str | None, css: str | Iterable[str], *processors: Callable[..., Any], re: str | Pattern[str] | None = None, **kw: Any) -> Self[source]

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

有关 kwargs,请参阅 get_css()

参数:

  • css (str) – 从中提取数据的 CSS 选择器

返回:

当前 ItemLoader 实例用于方法链。

返回类型:

ItemLoader

示例:

# 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 (.*)')

add_jmes(field_name: str | None, jmes: str, *processors: Callable[..., Any], re: str | Pattern[str] | None = None, **kw: Any) -> Self[source]

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

有关 kwargs,请参阅 get_jmes()

参数:

  • jmes (str) – 从中提取数据的 JMESPath 选择器

返回:

当前 ItemLoader 实例用于方法链。

返回类型:

ItemLoader

示例:

# HTML snippet: {"name": "Color TV"}
loader.add_jmes('name')
# HTML snippet: {"price": the price is $1200"}
loader.add_jmes('price', TakeFirst(), re='the price is (.*)')

add_value(field_name: str | None, value: Any, *processors: Callable[..., Any], re: str | Pattern[str] | None = None, **kw: Any) -> Self[source]

处理然后为给定字段添加给定

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

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

返回:

当前 ItemLoader 实例用于方法链。

返回类型:

ItemLoader

示例:

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

add_xpath(field_name: str | None, xpath: str | Iterable[str], *processors: Callable[..., Any], re: str | Pattern[str] | None = None, **kw: Any) -> Self[source]

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

有关 kwargs,请参阅 get_xpath()

参数:

  • xpath (str) – 从中提取数据的 XPath

返回:

当前 ItemLoader 实例用于方法链。

返回类型:

ItemLoader

示例:

# 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 (.*)')

get_collected_values(field_name: str) -> List[Any][source]

返回给定字段的收集值。

get_css(css: str | Iterable[str], *processors: Callable[[...], Any], re: str | Pattern[str] | None = None, **kw: Any) -> Any[source]

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

参数:

  • css (str) – 从中提取数据的 CSS 选择器
  • re (strPattern[str]) – 用于从选定的 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 (.*)')

get_jmes(jmes: str | Iterable[str], *processors: Callable[[...], Any], re: str | Pattern[str] | None = None, **kw: Any) -> Any[source]

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

参数:

  • jmes (str) – 从中提取数据的 JMESPath 选择器
  • re (strPattern) – 用于从选定的 JMESPath 中提取数据的正则表达式

示例:

# HTML snippet: {"name": "Color TV"}
loader.get_jmes('name')
# HTML snippet: {"price": the price is $1200"}
loader.get_jmes('price', TakeFirst(), re='the price is (.*)')

get_output_value(field_name: str) -> Any[source]

返回使用输出处理器解析的给定字段的收集值。此方法根本不填充或修改项目。

get_value(value: Any, *processors: Callable[[...], Any], re: str | Pattern[str] | None = None, **kw: Any) -> Any[source]

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

可用的关键字参数:

参数:

  • re (strPattern[str]) – 在处理器之前,用于使用 extract_regex() 方法从给定值中提取数据的正则表达式

示例:

>>> from itemloaders import ItemLoader
>>> from itemloaders.processors import TakeFirst
>>> loader = ItemLoader()
>>> loader.get_value('name: foo', TakeFirst(), str.upper, re='name: (.+)')
'FOO'

get_xpath(xpath: str | Iterable[str], *processors: Callable[[...], Any], re: str | Pattern[str] | None = None, **kw: Any) -> Any[source]

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

参数:

  • xpath (str) – 从中提取数据的 XPath
  • re (strPattern[str]) – 用于从选定的 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 (.*)')

load_item() -> Any[source]

用目前收集的数据填充项目,并返回它。收集的数据首先通过输出处理器以获得最终值,然后分配给每个项目字段。

nested_css(css: str, **context: Any) -> Self[source]

使用 CSS 选择器创建一个嵌套加载器。提供的选择器相对于与此 ItemLoader 关联的选择器应用。嵌套加载器与父 ItemLoader 共享项目,因此对 add_xpath()add_value()replace_value() 等的调用将按预期运行。

nested_xpath(xpath: str, **context: Any) -> Self[source]

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

replace_css(field_name: str | None, css: str | Iterable[str], *processors: Callable[..., Any], re: str | Pattern[str] | None = None, **kw: Any) -> Self[source]

类似于 add_css() 但替换收集的数据而不是添加它。

返回:

当前 ItemLoader 实例用于方法链。

返回类型:

ItemLoader

replace_jmes(field_name: str | None, jmes: str | Iterable[str], *processors: Callable[..., Any], re: str | Pattern[str] | None = None, **kw: Any) -> Self[source]

类似于 add_jmes() 但替换收集的数据而不是添加它。

返回:

当前 ItemLoader 实例用于方法链。

返回类型:

ItemLoader

replace_value(field_name: str | None, value: Any, *processors: Callable[..., Any], re: str | Pattern[str] | None = None, **kw: Any) -> Self[source]

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

返回:

当前 ItemLoader 实例用于方法链。

返回类型:

ItemLoader

replace_xpath(field_name: str | None, xpath: str | Iterable[str], *processors: Callable[..., Any], re: str | Pattern[str] | None = None, **kw: Any) -> Self[source]

类似于 add_xpath() 但替换收集的数据而不是添加它。

返回:

当前 ItemLoader 实例用于方法链。

返回类型:

ItemLoader

嵌套加载器

当从文档的某个部分解析相关值时,创建嵌套加载器会很有用。想象一下您正在从页脚提取详细信息,页脚看起来像这样:

示例:

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

重用和扩展 Item Loaders

随着您的项目越来越大并拥有越来越多的爬虫,维护成为一个基本问题,特别是当您必须处理每个爬虫的许多不同解析规则,存在大量异常,但也希望重用通用处理器时。

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

例如,假设某个特定网站将其产品名称用三个破折号括起来(例如 ---Plasma TV---),并且您不希望最终在最终产品名称中抓取这些破折号。

以下是如何通过重用和扩展默认的 Product Item Loader(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)

另一个扩展 Item Loaders 非常有用的情况是当您有多种源格式时,例如 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)

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

至于输出处理器,更常见的是在字段元数据中声明它们,因为它们通常只依赖于字段,而不依赖于每个特定的站点解析规则(就像输入处理器一样)。另请参阅:声明输入和输出处理器

还有许多其他可能的方式来扩展、继承和覆盖您的 Item Loaders,不同的 Item Loaders 层次结构可能更适合不同的项目。Scrapy 只提供了机制;它不强加您的加载器集合的任何特定组织——这取决于您和您项目的需求。