项
抓取的主要目标是从非结构化来源(通常是网页)中提取结构化数据。爬虫可能会将提取的数据作为项(items)返回,这些项是定义键值对的 Python 对象。
Scrapy 支持多种项类型。在创建项时,你可以使用任何你想要的项类型。当你编写接收项的代码时,你的代码应该适用于任何项类型。
项类型
Scrapy 通过 itemadapter 库支持以下类型的项:字典、Item 对象、数据类对象和 attrs 对象。
字典
作为一种项类型,dict 方便且熟悉。
Item 对象
Item 提供类似 dict 的 API 以及额外功能,使其成为功能最完善的项类型
- class scrapy.Item(*args: Any, **kwargs: Any)[source]
抓取项的基类。
在 Scrapy 中,如果一个对象受到 itemadapter 库的支持,它就被视为一个
item。例如,当评估爬虫回调的输出时,只有此类对象会被传递给 项管道。Item是 itemadapter 默认支持的类之一。项必须声明
Field属性,这些属性会在fields属性中进行处理和存储。这限制了允许的字段名称集合,并防止拼写错误,在引用未定义字段时会引发KeyError。此外,字段还可以用于定义元数据并控制数据在内部的处理方式。请参阅字段文档以获取更多信息。
Item 对象复制了标准的 dict API,包括其 __init__ 方法。
Item 允许定义字段名,以便
trackref 追踪 Item 对象以帮助查找内存泄漏(参见使用 trackref 调试内存泄漏)。
示例:
from scrapy.item import Item, Field
class CustomItem(Item):
one_field = Field()
another_field = Field()
数据类对象
dataclass() 允许定义带有字段名的项类,以便项导出器默认可以导出所有字段,即使第一个抓取对象没有所有字段的值。
此外,dataclass 项还允许你
定义每个已定义字段的类型和默认值。
通过
dataclasses.field()定义自定义字段元数据,这可用于自定义序列化。
示例:
from dataclasses import dataclass
@dataclass
class CustomItem:
one_field: str
another_field: int
注意
字段类型在运行时不强制执行。
attr.s 对象
attr.s() 允许定义带有字段名的项类,以便项导出器默认可以导出所有字段,即使第一个抓取对象没有所有字段的值。
此外,attr.s 项还允许
要使用此类型,需要安装 attrs 包。
示例:
import attr
@attr.s
class CustomItem:
one_field = attr.ib()
another_field = attr.ib()
Pydantic 模型
Pydantic 模型允许定义带有字段名的项类,以便项导出器默认可以导出所有字段,即使第一个抓取对象没有所有字段的值。
此外,pydantic 项还允许你
定义每个已定义字段的类型和默认值,并进行运行时类型验证。
通过 pydantic.Field 定义自定义字段元数据,这可用于自定义序列化。
受益于基于类型注解的自动数据验证和转换。
要使用此类型,需要安装 pydantic 包。
示例:
from pydantic import BaseModel, Field
class CustomItem(BaseModel):
one_field: str = Field(default="", description="First field")
another_field: int = Field(default=0, description="Second field")
注意
与其他项类型不同,Pydantic 模型在运行时强制执行字段类型,并会针对无效数据类型引发验证错误。
使用 Item 对象
声明 Item 子类
Item 子类使用简单的类定义语法和 Field 对象声明。示例如下:
import scrapy
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
tags = scrapy.Field()
last_updated = scrapy.Field(serializer=str)
声明字段
Field 对象用于为每个字段指定元数据。例如,上例中所示的 last_updated 字段的序列化器函数。
你可以为每个字段指定任何类型的元数据。Field 对象接受的值没有限制。出于同样的原因,没有所有可用元数据键的参考列表。在 Field 对象中定义的每个键都可以被不同的组件使用,并且只有这些组件知道它们。你也可以在你的项目中定义和使用任何其他 Field 键,以满足你自己的需求。Field 对象的主要目标是提供一种在一个地方定义所有字段元数据的方式。通常,其行为依赖于每个字段的组件会使用某些字段键来配置该行为。你必须查阅它们的文档以了解每个组件使用了哪些元数据键。
需要注意的是,用于声明项的 Field 对象不会作为类属性保留。相反,可以通过 fields 属性访问它们。
- class scrapy.Field[source]
字段元数据的容器
Field类只是内置dict类的一个别名,不提供任何额外功能或属性。换句话说,Field对象就是普通的 Python 字典。使用单独的类是为了支持基于类属性的项声明语法。
注意
字段元数据也可以为 dataclass 和 attrs 项声明。请参阅 dataclasses.field 和 attr.ib 的文档以获取更多信息。
使用 Item 对象
以下是一些使用项执行常见任务的示例,使用上面声明的 Product 项。你会注意到其 API 与 dict API 非常相似。
创建项
>>> product = Product(name="Desktop PC", price=1000)
>>> print(product)
Product(name='Desktop PC', price=1000)
获取字段值
>>> product["name"]
Desktop PC
>>> product.get("name")
Desktop PC
>>> product["price"]
1000
>>> product["last_updated"]
Traceback (most recent call last):
...
KeyError: 'last_updated'
>>> product.get("last_updated", "not set")
not set
>>> product["lala"] # getting unknown field
Traceback (most recent call last):
...
KeyError: 'lala'
>>> product.get("lala", "unknown field")
'unknown field'
>>> "name" in product # is name field populated?
True
>>> "last_updated" in product # is last_updated populated?
False
>>> "last_updated" in product.fields # is last_updated a declared field?
True
>>> "lala" in product.fields # is lala a declared field?
False
设置字段值
>>> product["last_updated"] = "today"
>>> product["last_updated"]
today
>>> product["lala"] = "test" # setting unknown field
Traceback (most recent call last):
...
KeyError: 'Product does not support field: lala'
访问所有已填充的值
要访问所有已填充的值,只需使用典型的 dict API
>>> product.keys()
['price', 'name']
>>> product.items()
[('price', 1000), ('name', 'Desktop PC')]
复制项
要复制一个项,你必须首先决定是想要浅拷贝还是深拷贝。
如果你的项包含像列表或字典这样的可变值,浅拷贝会在所有不同副本之间保留对相同可变值的引用。
例如,如果你有一个包含标签列表的项,并且你创建了该项的浅拷贝,那么原始项和副本都拥有相同的标签列表。向其中一个项的列表中添加标签,也会将标签添加到另一个项。
如果这不是期望的行为,请使用深拷贝。
有关更多信息,请参阅 copy。
要创建项的浅拷贝,你可以调用现有项上的 copy() 方法(product2 = product.copy()),或者从现有项实例化你的项类(product2 = Product(product))。
要创建深拷贝,请改用 deepcopy() 方法(product2 = product.deepcopy())。
其他常见任务
从项创建字典
>>> dict(product) # create a dict from all populated values
{'price': 1000, 'name': 'Desktop PC'}
Creating items from dicts:
>>> Product({"name": "Laptop PC", "price": 1500})
Product(price=1500, name='Laptop PC')
>>> Product({"name": "Laptop PC", "lala": 1500}) # warning: unknown field in dict
Traceback (most recent call last):
...
KeyError: 'Product does not support field: lala'
扩展 Item 子类
你可以通过声明原始 Item 的子类来扩展 Item(添加更多字段或更改某些字段的元数据)。
例如
class DiscountedProduct(Product):
discount_percent = scrapy.Field(serializer=str)
discount_expiration_date = scrapy.Field()
你还可以通过使用先前的字段元数据并追加更多值,或更改现有值来扩展字段元数据,如下所示:
class SpecificProduct(Product):
name = scrapy.Field(Product.fields["name"], serializer=my_serializer)
这会添加(或替换)name 字段的 serializer 元数据键,同时保留所有先前存在的元数据值。
支持所有项类型
在接收项的代码中,例如项管道或爬虫中间件的方法中,使用 ItemAdapter 类编写适用于任何受支持项类型的代码是一种好做法。