项目加载器¶
项目加载器提供了一种便捷的机制来填充抓取的项目。尽管可以直接填充项目,但项目加载器通过自动化一些常见任务(如在分配之前解析原始提取的数据),为从抓取过程中填充项目提供了更加便捷的API。
换句话说,items 提供了抓取数据的容器,而 Item Loaders 提供了填充该容器的机制。
项目加载器旨在提供一种灵活、高效且简单的机制,用于扩展和覆盖不同的字段解析规则,无论是通过爬虫还是通过源格式(HTML、XML等),而不会成为维护的噩梦。
注意
项目加载器是itemloaders库的扩展,通过增加对响应的支持,使得使用Scrapy更加方便。
使用项目加载器填充项目¶
要使用项目加载器,您必须首先实例化它。您可以使用项目对象实例化它,或者不使用,在这种情况下,项目加载器的__init__方法会自动创建一个项目对象,使用ItemLoader.default_item_class属性中指定的项目类。
然后,你开始将值收集到Item Loader中,通常使用 Selectors。你可以向同一个项目字段添加多个值;Item Loader稍后会知道如何使用适当的处理函数“连接”这些值。
注意
收集的数据在内部存储为列表,允许向同一字段添加多个值。如果在创建加载器时传递了item参数,则每个项的值将按原样存储(如果它已经是可迭代的),或者如果它是单个值,则用列表包装。
以下是在Spider中典型的Item Loader使用示例,使用了在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位置提取:
//div[@class="product_name"]//div[@class="product_title"]
换句话说,数据是通过从两个XPath位置提取来收集的,使用的是add_xpath()方法。这些数据稍后将被分配给name字段。
随后,类似的调用用于 price 和 stock 字段
(后者使用带有 add_css() 方法的 CSS 选择器),
最后,last_update 字段直接使用字面值
(today)通过不同的方法填充:add_value()。
最后,当所有数据收集完毕后,调用ItemLoader.load_item()方法,该方法实际上返回了之前通过add_xpath()、add_css()和add_value()调用提取和收集的数据填充的项。
处理数据类项目¶
默认情况下,dataclass items 在创建时需要传递所有字段。当与项目加载器一起使用 dataclass items 时,这可能会成为一个问题:除非将预先填充的项目传递给加载器,否则字段将使用加载器的 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.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)
所以发生的情况是:
从
xpath1提取的数据,通过name字段的输入处理器进行处理。输入处理器的结果被收集并保存在Item Loader中(但尚未分配给项目)。从
xpath2提取的数据,并通过与(1)中使用的相同的输入处理器进行处理。输入处理器的结果将附加到(1)中收集的数据(如果有的话)。这个案例与之前的案例类似,不同之处在于数据是从
cssCSS选择器中提取的,并通过与(1)和(2)中使用的相同的输入处理器进行处理。输入处理器的结果将附加到(1)和(2)中收集的数据(如果有的话)。这个案例也与之前的案例类似,不同之处在于要收集的值是直接分配的,而不是从XPath表达式或CSS选择器中提取的。 然而,该值仍然通过输入处理器传递。在这种情况下,由于该值不可迭代,因此在将其传递给输入处理器之前,会将其转换为单个元素的可迭代对象,因为输入处理器总是接收可迭代对象。
在步骤(1)、(2)、(3)和(4)中收集的数据通过
name字段的输出处理器进行处理。输出处理器的结果是分配给项目中name字段的值。
值得注意的是,处理器只是可调用对象,它们被调用时会传入要解析的数据,并返回解析后的值。因此,你可以使用任何函数作为输入或输出处理器。唯一的要求是它们必须接受一个(且仅一个)位置参数,该参数将是一个可迭代对象。
在版本2.0中更改:处理器不再需要是方法。
注意
输入和输出处理器都必须接收一个可迭代对象作为它们的第一个参数。这些函数的输出可以是任何内容。输入处理器的结果将被附加到一个内部列表(在加载器中),该列表包含收集的值(针对该字段)。输出处理器的结果将是最终分配给项目的值。
你需要注意的另一件事是,输入处理器返回的值在内部收集(在列表中),然后传递给输出处理器以填充字段。
最后但同样重要的是,itemloaders 提供了一些内置的常用处理器以便使用。
声明项目加载器¶
项目加载器使用类定义语法声明。以下是一个示例:
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_processor和ItemLoader.default_output_processor属性声明默认的输入/输出处理器。
声明输入和输出处理器¶
如前一节所示,输入和输出处理器可以在项目加载器定义中声明,这种方式声明输入处理器非常常见。然而,还有另一个地方可以指定要使用的输入和输出处理器:在项目字段元数据中。以下是一个示例:
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", ["€", "<span>1000</span>"])
>>> il.load_item()
{'name': 'Welcome to my website', 'price': '1000'}
输入和输出处理器的优先级顺序如下:
项目加载器字段特定属性:
field_in和field_out(最高优先级)字段元数据(
input_processor和output_processor键)项目加载器默认值:
ItemLoader.default_input_processor()和ItemLoader.default_output_processor()(最低优先级)
另请参阅:重用和扩展项目加载器。
项目加载器上下文¶
项目加载器上下文是一个任意键/值的字典,它在项目加载器中的所有输入和输出处理器之间共享。它可以在声明、实例化或使用项目加载器时传递。它们用于修改输入/输出处理器的行为。
例如,假设你有一个函数 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)因此可以使用它们。
有几种方法可以修改项目加载器的上下文值:
通过修改当前活动的项目加载器上下文 (
context属性):loader = ItemLoader(product) loader.context["unit"] = "cm"
在项目加载器实例化时(项目加载器的关键字参数
__init__方法存储在项目加载器上下文中):loader = ItemLoader(product, unit="cm")
在项目加载器声明时,对于那些支持使用项目加载器上下文实例化的输入/输出处理器。
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]¶
一个用户友好的抽象,通过将字段处理器应用于抓取的数据来填充项目。 当使用
selector或response实例化时,它支持使用选择器从网页中提取数据。- Parameters:
item (scrapy.item.Item) – 使用后续调用
add_xpath(),add_css(), 或add_value()来填充的item实例。selector (
Selector对象) – 用于提取数据的选择器,当使用add_xpath(),add_css(),replace_xpath(), 或replace_css()方法时。response (
Response对象) – 用于构造选择器的响应,使用default_selector_class,除非提供了选择器参数, 在这种情况下,此参数将被忽略。
如果没有提供项目,则会使用
default_item_class中的类自动实例化一个。项目、选择器、响应和剩余的关键字参数被分配给加载器上下文(可通过
context属性访问)。- item¶
正在被此Item Loader解析的项目对象。 这主要用作属性,因此,在尝试覆盖此值时,您可能希望先查看
default_item_class。
- default_input_processor¶
用于那些未指定输入处理器的字段的默认输入处理器。
- default_output_processor¶
用于那些未指定输出处理器的字段的默认输出处理器。
- default_selector_class¶
用于构造此
selector的类,如果在__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[源代码]¶
类似于
ItemLoader.add_value(),但接收一个CSS选择器 而不是一个值,该选择器用于从与此ItemLoader关联的选择器中提取一个Unicode字符串列表。请参阅
get_css()以了解kwargs。- Parameters:
css (str) – 用于提取数据的CSS选择器
- Returns:
当前用于方法链的ItemLoader实例。
- Return type:
示例:
# 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 字符串列表。请参阅
get_jmes()了解kwargs。- Parameters:
jmes (str) – 用于提取数据的JMESPath选择器
- Returns:
当前用于方法链的ItemLoader实例。
- Return type:
示例:
# 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]¶
处理并添加给定字段的给定
value。该值首先通过
get_value()传递,提供processors和kwargs,然后通过字段输入处理器传递,其结果附加到为该字段收集的数据中。如果该字段已经包含收集的数据,则新数据将被添加。给定的
field_name可以是None,在这种情况下,可以添加多个字段的值。处理后的值应该是一个字典,其中 field_name 映射到值。- Returns:
当前用于方法链的ItemLoader实例。
- Return type:
示例:
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 而不是值,用于从与此ItemLoader关联的选择器中提取字符串列表。请参阅
get_xpath()以了解kwargs。- Parameters:
xpath (str) – 用于提取数据的XPath
- Returns:
当前用于方法链的ItemLoader实例。
- Return type:
示例:
# 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_css(css: str | Iterable[str], *processors: Callable[[...], Any], re: str | Pattern[str] | None = None, **kw: Any) Any[source]¶
类似于
ItemLoader.get_value(),但接收一个CSS选择器 而不是一个值,该选择器用于从与此ItemLoader关联的选择器中提取一个Unicode字符串列表。示例:
# 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[源代码]¶
类似于
ItemLoader.get_value(),但接收的是一个JMESPath选择器 而不是一个值,该选择器用于从与此ItemLoader关联的选择器中提取一个Unicode字符串列表。示例:
# 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_value(value: Any, *processors: Callable[[...], Any], re: str | Pattern[str] | None = None, **kw: Any) Any[源代码]¶
通过给定的
processors和关键字参数处理给定的value。可用的关键字参数:
示例:
>>> 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而不是值,用于从与此ItemLoader关联的选择器中提取一组Unicode字符串。示例:
# 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 (.*)')
- 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()但替换收集的数据而不是添加它。- Returns:
当前用于方法链的ItemLoader实例。
- Return type:
- 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()但替换收集的数据而不是添加它。- Returns:
当前用于方法链的ItemLoader实例。
- Return type:
嵌套加载器¶
从文档的一个子部分解析相关值时,创建嵌套加载器可能很有用。想象一下,您正在从页面的页脚中提取详细信息,页脚看起来像这样:
示例:
<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类继承,以处理特定蜘蛛(或蜘蛛组)的差异。
例如,假设某个特定站点将他们的产品名称用三个破折号括起来(例如 ---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)
另一个扩展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只提供了机制;它不会对您的Loaders集合施加任何特定的组织方式——这取决于您和您的项目需求。