项目加载器¶
项目加载器提供了一种便捷的机制来填充已抓取的项目。尽管可以直接填充项目,但项目加载器通过自动化一些常见任务(例如在分配之前解析提取的原始数据)提供了更便捷的 API 来填充它们,从而更方便地从抓取过程中填充它们。
换句话说,项目提供了抓取数据的容器,而项目加载器提供了填充该容器的机制。
项目加载器旨在提供一种灵活、高效且易于使用的机制来扩展和覆盖不同的字段解析规则,无论是按爬虫还是按源格式(HTML、XML 等)进行,而不会成为维护的噩梦。
注意
项目加载器是itemloaders库的扩展,它通过添加对响应的支持,使使用 Scrapy 变得更容易。
使用项目加载器填充项目¶
要使用项目加载器,您必须首先实例化它。您可以使用项目对象或不使用它来实例化它,在这种情况下,项目对象会自动在 Item Loader 的__init__
方法中使用项目类创建,该类在ItemLoader.default_item_class
属性中指定。
然后,您开始将值收集到项目加载器中,通常使用选择器。您可以向同一个项目字段添加多个值;项目加载器将知道如何使用适当的处理函数稍后“连接”这些值。
注意
收集到的数据在内部存储为列表,允许向同一个字段添加多个值。如果在创建加载器时传递了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 位置提取的
//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()
调用提取和收集。
使用数据类项目¶
默认情况下,数据类项目要求在创建时传递所有字段。在将数据类项目与项目加载器一起使用时,这可能是一个问题:除非将预填充的项目传递给加载器,否则将使用加载器的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()
方法来填充并获取已填充的项目对象。此时会调用输出处理器,并使用先前收集的数据(并使用输入处理器进行处理)。输出处理器的结果是分配给项目的最终值。
让我们看一个示例来说明输入和输出处理器是如何为特定字段调用的(其他任何字段也适用相同的情况)
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) 中收集的数据(如果有)。这种情况与前面的情况类似,只是数据是从
css
CSS 选择器提取的,并通过在 (1) 和 (2) 中使用的相同输入处理器传递。输入处理器的结果将附加到在 (1) 和 (2) 中收集的数据(如果有)。这种情况也与前面的情况类似,只是要收集的值是直接分配的,而不是从 XPath 表达式或 CSS 选择器提取的。但是,该值仍会通过输入处理器传递。在这种情况下,由于该值不可迭代,因此在传递给输入处理器之前将其转换为单个元素的可迭代对象,因为输入处理器始终接收可迭代对象。
步骤 (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_processor
和 ItemLoader.default_output_processor
属性声明默认的输入/输出处理器。
声明输入和输出处理器¶
如上一节所示,可以在 Item Loader 定义中声明输入和输出处理器,并且通常以这种方式声明输入处理器。但是,您还可以指定要使用的输入和输出处理器的另一个位置:在Item 字段 元数据中。以下是一个示例
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'}
输入和输出处理器的优先级顺序如下
Item Loader 字段特定的属性:
field_in
和field_out
(最高优先级)字段元数据(
input_processor
和output_processor
键)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]¶
一个用户友好的抽象,通过将字段处理器应用于抓取的数据来填充项目。当使用
selector
或response
实例化时,它支持使用选择器从网页提取数据。- 参数:
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
构造选择器的响应,除非提供了选择器参数,否则将忽略此参数。
如果没有给出项目,则会使用
default_item_class
中的类自动实例化一个项目。项目、选择器、响应和剩余的关键字参数被分配到加载器上下文(可以通过
context
属性访问)。- item¶
此 Item Loader 正在解析的项目对象。这主要用作属性,因此,当尝试覆盖此值时,您可能希望首先查看
default_item_class
。
- default_input_processor¶
用于未指定输入处理器字段的默认输入处理器。
- default_output_processor¶
用于未指定输出处理器字段的默认输出处理器。
- default_selector_class¶
用于构建此
ItemLoader
的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 [source]¶
类似于
ItemLoader.add_value()
,但接收 CSS 选择器而不是值,该选择器用于从与此ItemLoader
关联的选择器中提取 Unicode 字符串列表。有关
kwargs
,请参阅get_css()
。- 参数:
css (str) – 用于提取数据的 CSS 选择器
- 返回值:
用于方法链的当前 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 实例。
- 返回类型:
示例
# 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 映射到值。- 返回值:
用于方法链的当前 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 而不是值,用于从与此ItemLoader
关联的选择器中提取字符串列表。参见
get_xpath()
获取kwargs
。- 参数:
xpath (str) – 用于提取数据的 XPath
- 返回值:
用于方法链的当前 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_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 [source]¶
类似于
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 [source]¶
使用给定的
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()
,但替换收集到的数据而不是添加它。- 返回值:
用于方法链的当前 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 实例。
- 返回类型:
嵌套加载器¶
当从文档的某个子部分解析相关值时,创建嵌套加载器可能很有用。假设您要从页面页脚中提取详细信息,该页脚看起来像
示例
<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)。
示例
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 加载器¶
随着项目的规模越来越大,蜘蛛也越来越多,维护成为一个基本问题,尤其是在您必须处理每个蜘蛛的许多不同解析规则时,存在大量异常,但同时也希望重用公共处理器。
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 只提供机制;它不强加任何特定的 Loaders 集合组织 - 这取决于您和您的项目需求。