2.4 Items:给数据上户口


文档摘要

2.4 Items:给数据上户口 本节摘要:Item 是抓取数据的契约模型:字段在模型里声明一次,Spider 只管往里装货,管道按统一口径处理。本节讲 Item 类的定义方式、itemadapter 桥接用法,以及"裸字典快、模型稳"的工程取舍——数据口径从此不再是口头约定。 拆包产出的数据,现在还是随手塞的字典。字典的问题不在能不能用,而在"口径靠记忆":三个月后价格字段是带币种的字符串还是数字?missing 的字段是空串还是不存在?字典答不了,Item 答得了。这一节给数据上户口——登记后的字段,整条旅程都认。 定义:字段就是契约 声明本身不含类型约束——Item 的价值在于"字段清单被固定下来",下游管道看到的是确定的形状。

2.4 Items:给数据上户口

本节摘要:Item 是抓取数据的契约模型:字段在模型里声明一次,Spider 只管往里装货,管道按统一口径处理。本节讲 Item 类的定义方式、itemadapter 桥接用法,以及"裸字典快、模型稳"的工程取舍——数据口径从此不再是口头约定。

拆包产出的数据,现在还是随手塞的字典。字典的问题不在能不能用,而在"口径靠记忆":三个月后价格字段是带币种的字符串还是数字?missing 的字段是空串还是不存在?字典答不了,Item 答得了。这一节给数据上户口——登记后的字段,整条旅程都认。

定义:字段就是契约

import scrapy class BookItem(scrapy.Item): # 每个字段一行,字段名即下游契约 title = scrapy.Field() # 书名,去首尾空白 price = scrapy.Field() # 价格,统一为浮点,无币种符号 stock = scrapy.Field() # 库存状态,布尔化 category = scrapy.Field() # 分类路径,如 文学/小说 detail_url = scrapy.Field() # 详情地址,兼作去重主键 crawled_at = scrapy.Field() # 采集时间戳,溯源用

声明本身不含类型约束——Item 的价值在于"字段清单被固定下来",下游管道看到的是确定的形状。Spider 侧的用法几乎无感:

from bookstation.items import BookItem def parse_detail(self, response): item = BookItem() item["title"] = response.css("h1::text").get(default="").strip() item["price"] = response.css("p.price::text").get() item["detail_url"] = response.url yield item

把 yield 字典改成 yield Item,就是迁移的全部工作量。收益在下游:管道不再需要猜测字段,编辑器也能补全提示。

元信息进字段:metadata 的正确用法

Field 支持挂任意元信息,把清洗口径直接写在模型旁:

class BookItem(scrapy.Item): title = scrapy.Field(required=True) # 缺了这条 Item 直接作废 price = scrapy.Field(process=lambda v: float(v.strip("£"))) # 口径随字段走 stock = scrapy.Field(default=False)

元信息本身框架不强制解释——它是一份"给管道读的说明"。管道里通过 item 字段的元信息决定校验强度,口径与字段同名同行,改口径不会漏改。这比把十几个 if 散落在管道里清晰得多。

桥接:管道里怎么统一处理两种形状

历史代码里常有字典与 Item 混杂的情况。框架提供 itemadapter 这层桥接,管道代码对两种形状一视同仁:

from itemadapter import ItemAdapter class NormalizePipeline: def process_item(self, item, spider): ad = ItemAdapter(item) # 字典或 Item 都能包 if "price" in ad: raw = ad["price"] or "" ad["price"] = float(raw.replace("£", "") or 0) ad["crawled_at"] = ad.get("crawled_at") or "" return item

adapt 后的字段读写同字典语法。这条桥的意义在团队协作:有人图快 yield 字典,有人守契约用 Item,管道层不会被两种形状撕开。

取舍:裸字典还是 Item

维度 yield 字典 yield Item
起步成本 零,随手写 先定义模型
字段口径 靠记忆与注释 模型固定,可查可审
下游处理 各管道自行猜形状 统一形状,adapter 桥接
重构安全 改字段名要全文搜 模型一处改,引用处编辑器报错
适合阶段 探索期、一次性任务 持续维护的正式工程

我的判断:探索站点结构的头两天用字典,字段清单稳定下来的当天就建模型——这个时机点不难找,难的是记得找。

⚠️ 常见坑:Item 字段一旦写入,重复赋同键不会报错,但给未声明字段赋值会立刻抛错。这正是设计意图——拼错字段名在 Spider 阶段炸,比在数据库里躺三个月后再被发现便宜得多。

常见建模反模式:户口本上的三种错

模型用错方式,契约反而变成负担。三种高频反模式值得提前避坑。

反模式一:字段长草。 模型里堆了二三十个"也许有用"的字段,其中大半从未被管道或下游消费。字段越多,清洗与校验的面积越大,缺字段的告警也越频繁。建模从下游需求倒推:数据库表里要什么,模型里就有什么,多余的一个不加。

反模式二:万能字符串。 所有字段都收文本,价格是"51.77"、库存是"in stock"、时间是"2026-08-29"全靠下游自己转。口径没锁在模型层,等于没上户口:

# 反例:全部收原始文本,下游各自猜格式 class RawItem(scrapy.Item): f1 = scrapy.Field() f2 = scrapy.Field() # 正解:在字段元信息里声明口径,管道照契约执行 class BookItem(scrapy.Item): price = scrapy.Field(cast=float, note="去除币种符号后的数值") stock = scrapy.Field(cast=bool, note="有货为真")

反模式三:一个模型打天下。 列表页摘要与详情页全量数据共用一个 Item,摘要场景大量字段缺失,校验告警被噪音淹没。按数据形态分模型:摘要一个轻量模型,详情一个完整模型,各自配各自的校验强度。模型数量不是负担,混淆才是。

Item 的传递路径:从 yield 到落库

把一个 Item 的完整旅程串起来,你会在排错时有"全景视角"。回调 yield 出 Item → 引擎把它递给蜘蛛中间件的产出钩子 → 进入管道清单按序处理 → 每层处理完返回,DropItem 即中断 → 走完全部管道后引擎广播 item_scraped 信号 → 统计计数加一。哪个环节出问题,症状各不相同:管道抛了普通异常,Item 停在该层并记入错误统计;全程通过但下游说没数据,先对账 item_scraped_count 与落库行数。

# 统计里的 Item 相关计数 'item_scraped_count': 340, # 通过全部管道的件数 'item_dropped_count': 26, # 被 DropItem 拦下的件数 'item_dropped_reason_count_count/缺标题': 19,

dropped 计数按原因分账这个细节很实用:某个原因的数字突然变大,往往精确指向站点改版击穿的那一个选择器。

本节要点回顾

  • Item 是契约不是容器:字段清单固定,下游不再猜形状;
  • 元信息随字段走:清洗与校验口径写在模型旁,避免散落各管道;
  • adapter 桥接两种形状:字典与 Item 在管道层统一处理,协作不撕裂;
  • 切换时机:探索期字典,口径稳定当天建模。

数据有户口了,但续票还在手写。下一节把"发现新链接"升级成规则声明——LinkExtractor 与 CrawlSpider。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U