Scrapy框架中的Item Pipeline机制详解

Scrapy框架中的Item Pipeline机制详解

scrapy Scrapy, a fast high-level web crawling & scraping framework for Python. scrapy 项目地址: https://gitcode.com/gh_mirrors/sc/scrapy

什么是Item Pipeline

在Scrapy框架中,Item Pipeline(项目管道)是处理爬取到的数据的核心组件。当Spider成功抓取一个Item后,这个Item会被发送到Item Pipeline,经过一系列按顺序执行的组件进行处理。

可以把Item Pipeline想象成一条流水线,每个处理环节(Pipeline组件)都是一个独立的Python类,负责对Item进行特定的处理操作。每个组件都能决定是否继续传递Item到下一个环节,或者直接丢弃不再处理。

Item Pipeline的典型应用场景

  1. 数据清洗:清理HTML数据,去除无用标签或格式化内容
  2. 数据验证:检查Item是否包含必要的字段
  3. 去重处理:识别并丢弃重复的Item
  4. 数据存储:将Item保存到数据库或文件中

如何编写自定义Item Pipeline

基础方法实现

每个Item Pipeline组件必须实现process_item方法:

def process_item(self, item, spider):
    # 处理item的逻辑
    return item  # 或者返回Deferred对象,或者抛出DropItem异常
  • 参数
    • item:要处理的Item对象
    • spider:抓取该Item的Spider实例
  • 返回值
    • 返回处理后的Item对象
    • 返回Deferred对象(异步处理)
    • 抛出DropItem异常(丢弃该Item)

可选生命周期方法

def open_spider(self, spider):
    # 当Spider开启时调用
    # 常用于初始化资源(如打开文件、连接数据库)

def close_spider(self, spider):
    # 当Spider关闭时调用
    # 常用于清理资源(如关闭文件、断开数据库连接)

实用Pipeline示例

1. 价格验证与过滤

from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem

class PricePipeline:
    vat_factor = 1.15  # 增值税系数

    def process_item(self, item, spider):
        adapter = ItemAdapter(item)
        if adapter.get("price"):
            if adapter.get("price_excludes_vat"):
                adapter["price"] = adapter["price"] * self.vat_factor
            return item
        else:
            raise DropItem("价格字段缺失")

这个Pipeline会:

  1. 检查Item是否有价格字段
  2. 如果价格不含税,则加上增值税
  3. 没有价格的Item会被丢弃

2. JSON文件存储

import json
from itemadapter import ItemAdapter

class JsonWriterPipeline:
    def open_spider(self, spider):
        self.file = open("items.jsonl", "w")  # 打开文件

    def close_spider(self, spider):
        self.file.close()  # 关闭文件

    def process_item(self, item, spider):
        line = json.dumps(ItemAdapter(item).asdict()) + "\n"
        self.file.write(line)  # 写入JSON格式数据
        return item

注意:实际项目中推荐使用Scrapy内置的Feed导出功能。

3. MongoDB存储

import pymongo
from itemadapter import ItemAdapter

class MongoPipeline:
    collection_name = "scrapy_items"

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get("MONGO_URI"),
            mongo_db=crawler.settings.get("MONGO_DATABASE", "items"),
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        self.db[self.collection_name].insert_one(ItemAdapter(item).asdict())
        return item

关键点:

  • 使用from_crawler方法从设置中获取配置
  • open_spider中建立数据库连接
  • close_spider中关闭连接
  • 使用insert_one方法插入数据

4. 异步截图Pipeline

import hashlib
from pathlib import Path
from urllib.parse import quote
import scrapy
from itemadapter import ItemAdapter
from scrapy.http.request import NO_CALLBACK
from scrapy.utils.defer import maybe_deferred_to_future

class ScreenshotPipeline:
    SPLASH_URL = "http://localhost:8050/render.png?url={}"

    async def process_item(self, item, spider):
        adapter = ItemAdapter(item)
        encoded_item_url = quote(adapter["url"])
        screenshot_url = self.SPLASH_URL.format(encoded_item_url)
        request = scrapy.Request(screenshot_url, callback=NO_CALLBACK)
        response = await maybe_deferred_to_future(
            spider.crawler.engine.download(request)
        )

        if response.status != 200:
            return item

        url_hash = hashlib.md5(adapter["url"].encode("utf8")).hexdigest()
        filename = f"{url_hash}.png"
        Path(filename).write_bytes(response.body)
        adapter["screenshot_filename"] = filename
        return item

这个示例展示了:

  • 如何使用协程语法编写异步Pipeline
  • 如何与Splash服务交互获取网页截图
  • 如何将截图保存到本地文件
  • 如何将文件名添加到Item中

5. 去重过滤器

from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem

class DuplicatesPipeline:
    def __init__(self):
        self.ids_seen = set()  # 存储已见过的ID

    def process_item(self, item, spider):
        adapter = ItemAdapter(item)
        if adapter["id"] in self.ids_seen:
            raise DropItem(f"重复Item ID: {adapter['id']}")
        else:
            self.ids_seen.add(adapter["id"])
            return item

激活Item Pipeline

在Scrapy项目的设置文件中配置ITEM_PIPELINES

ITEM_PIPELINES = {
    "myproject.pipelines.PricePipeline": 300,
    "myproject.pipelines.JsonWriterPipeline": 800,
}

数字表示执行顺序(0-1000),数值小的先执行。

最佳实践建议

  1. 职责单一:每个Pipeline只负责一项特定任务
  2. 错误处理:合理处理可能出现的异常
  3. 资源管理:在open_spiderclose_spider中妥善管理资源
  4. 性能考虑:对于耗时操作考虑使用异步实现
  5. 配置化:通过设置文件配置参数,提高灵活性

通过合理使用Item Pipeline,你可以构建出强大而灵活的数据处理流程,满足各种复杂的爬虫数据处理需求。

scrapy Scrapy, a fast high-level web crawling & scraping framework for Python. scrapy 项目地址: https://gitcode.com/gh_mirrors/sc/scrapy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

诸锬泽Jemima

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值