项目(Items)
抓取的主要目标是从非结构化来源(通常是网页)中提取结构化数据。爬虫 可以将提取的数据作为 项目(items) 返回,项目是定义键值对的 Python 对象。
Scrapy 支持 多种类型的项目。当您创建一个项目时,您可以使用任何您想要的项目类型。当您编写接收项目的代码时,您的代码应该 适用于任何项目类型。
项目类型
Scrapy 通过 itemadapter 库支持以下类型的项目:字典、Item 对象、dataclass 对象 和 attrs 对象。
字典
作为项目类型,dict 既方便又熟悉。
Item 对象
Item 提供了一个类似 dict 的 API 以及额外的功能,使其成为功能最完善的项目类型:
class scrapy.Item(*args: Any, **kwargs: Any)[source]
抓取项目的基类。
在 Scrapy 中,如果一个对象受 itemadapter 库支持,则它被视为项目。例如,当评估爬虫回调的输出时,只有此类对象才会传递给项目管道。Item 是 itemadapter 默认支持的类之一。
项目必须声明 Field 属性,这些属性经过处理并存储在 fields 属性中。这限制了允许的字段名称集并防止拼写错误,当引用未定义的字段时会引发 KeyError。此外,字段可用于定义元数据并控制数据在内部的处理方式。有关其他信息,请参阅有关字段的文档。
与 dict 实例不同,Item 实例可以被跟踪以调试内存泄漏。
copy() -> Self[source]deepcopy() -> Self[source]返回此项目的deepcopy()。fields: dict[str, Field] = {}一个字典,包含此 Item 的所有已声明字段,而不仅仅是已填充的字段。键是字段名称,值是 Item 声明中使用的 Field 对象。
Item 对象复制了标准的 dict API,包括其 __init__ 方法。
Item 允许定义字段名称,以便:
- 使用未定义字段名称时会引发
KeyError(即防止拼写错误被忽视) - Item 导出器默认可以导出所有字段,即使第一个抓取对象没有所有字段的值
Item 还允许定义字段元数据,可用于自定义序列化。
trackref 跟踪 Item 对象以帮助查找内存泄漏(请参阅使用 trackref 调试内存泄漏)。
示例:
from scrapy.item import Item, Field
class CustomItem(Item):
one_field = Field()
another_field = Field()
Dataclass 对象
2.2 版新增。
dataclass() 允许定义带有字段名称的项目类,以便项目导出器默认可以导出所有字段,即使第一个抓取对象没有所有字段的值。
此外,dataclass 项目还允许您:
- 定义每个已定义字段的类型和默认值。
- 通过
dataclasses.field()定义自定义字段元数据,可用于自定义序列化。
示例:
from dataclasses import dataclass
@dataclass
class CustomItem:
one_field: str
another_field: int
注意
字段类型在运行时不强制执行。
attr.s 对象
2.2 版新增。
attr.s() 允许定义带有字段名称的项目类,以便项目导出器默认可以导出所有字段,即使第一个抓取对象没有所有字段的值。
此外,attr.s 项目还允许:
- 定义每个已定义字段的类型和默认值。
- 定义自定义字段元数据,可用于自定义序列化。
为了使用此类型,需要安装 attrs 包。
示例:
import attr
@attr.s
class CustomItem:
one_field = attr.ib()
another_field = attr.ib()
使用 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)
注意
熟悉 Django 的人会注意到 Scrapy Item 的声明方式与 Django 模型类似,不同之处在于 Scrapy Item 要简单得多,因为没有不同字段类型的概念。
声明字段
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()
dict_keys(['name', 'price']) # Note: order of keys might vary in different Python versions
>>> product.items()
dict_items([('name', 'Desktop PC'), ('price', 1000)]) # Note: order of items might vary
复制项目
要复制项目,您必须首先决定是进行浅拷贝还是深拷贝。
如果您的项目包含可变值,如列表或字典,浅拷贝将在所有不同的拷贝中保持对相同可变值的引用。
例如,如果您有一个带标签列表的项目,并且您创建了该项目的浅拷贝,则原始项目和拷贝都具有相同的标签列表。向其中一个项目的列表中添加标签也会将标签添加到另一个项目。
如果这不是所需的行为,请改用深拷贝。
有关更多信息,请参阅 copy。
要创建项目的浅拷贝,您可以对现有项目调用 copy()(product2 = product.copy())或从现有项目实例化您的项目类(product2 = Product(product))。
要创建深拷贝,请改为调用 deepcopy()(product2 = product.deepcopy())。
其他常见任务
从项目创建字典:
>>> dict(product) # create a dict from all populated values
{'name': 'Desktop PC', 'price': 1000} # Note: order of keys might vary
从字典创建项目:
>>> Product({"name": "Laptop PC", "price": 1500})
Product(name='Laptop PC', price=1500)
>>> 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 类编写适用于任何支持的项目类型的代码是一种很好的做法。
与项目相关的其他类
class scrapy.item.ItemMeta(class_name: str, bases: tuple[type, ...], attrs: dict[str, Any])[source]
Item 的元类,处理字段定义。