You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Scrapy将多页面抓取结果合并为单个Item?

Scrapy多页面抓取结果合并为单个Item的解决方案

核心思路

解决重复抓取和Item数据缺失的矛盾,关键在于缓存已抓取的URL数据,让多个Item共享同一URL的抓取结果,同时保证每个URL只被抓取一次。具体流程是:从起始页生成初始Item后,遍历需要抓取的URL列表,先检查缓存——有数据直接填充,无数据才发起请求;等所有URL处理完成后,再提交完整的Item到Pipeline,最后在Pipeline中按ID保留优先级最高的Item。


具体实现代码

1. Spider实现(缓存URL数据+逐步填充Item)

import scrapy
from your_project.items import TargetItem

class MultiPageSpider(scrapy.Spider):
    name = "multi_page_spider"
    allowed_domains = ["your-target-domain.com"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 缓存已抓取URL的结果:key为URL,value为{image_list: [], head_list: []}
        self.url_data_cache = {}

    def start_requests(self):
        # 替换为你的起始页面列表
        start_pages = [
            "https://your-target-domain.com/page-o-1",
            "https://your-target-domain.com/page-o-2"
        ]
        for page_url in start_pages:
            yield scrapy.Request(page_url, callback=self.parse_start_page)

    def parse_start_page(self, response):
        # 解析起始页,生成初始Item
        item = TargetItem()
        item["id"] = response.css("div.item-card::attr(data-id)").get()
        item["prio"] = int(response.css("span.priority::text").get())
        # 提取需要抓取的URL列表,转换为绝对路径
        raw_urls = response.css("a.detail-link::attr(href)").getall()
        item["urls"] = [response.urljoin(url) for url in raw_urls if url]
        # 初始化需要填充的字段
        item["image_list"] = []
        item["head_list"] = []

        # 开始处理URL列表,确保所有URL数据填充完成后再提交Item
        yield from self.process_url_queue(item, item["urls"].copy())

    def process_url_queue(self, item, remaining_urls):
        # 所有URL处理完成,提交完整Item
        if not remaining_urls:
            yield item
            return

        current_url = remaining_urls.pop(0)
        if current_url in self.url_data_cache:
            # 缓存有数据,直接填充到Item
            cached_data = self.url_data_cache[current_url]
            item["image_list"].extend(cached_data["image_list"])
            item["head_list"].extend(cached_data["head_list"])
            # 递归处理剩余URL
            yield from self.process_url_queue(item, remaining_urls)
        else:
            # 缓存无数据,发起请求,并将Item和剩余URL存入meta
            yield scrapy.Request(
                current_url,
                callback=self.parse_detail_page,
                meta={
                    "current_item": item,
                    "remaining_urls": remaining_urls
                }
                # 无需设置dont_filter=True,因为同一URL只会在缓存为空时发起请求
            )

    def parse_detail_page(self, response):
        # 解析详情页,提取所需数据
        image_list = response.css("div.gallery img::attr(src)").getall()
        head_list = response.css("h2.section-title::text").getall()
        # 将数据存入缓存,供后续Item复用
        self.url_data_cache[response.url] = {
            "image_list": image_list,
            "head_list": head_list
        }

        # 取出meta中的Item和剩余URL
        item = response.meta["current_item"]
        remaining_urls = response.meta["remaining_urls"]

        # 填充当前详情页数据到Item
        item["image_list"].extend(image_list)
        item["head_list"].extend(head_list)

        # 继续处理剩余URL
        yield from self.process_url_queue(item, remaining_urls)

2. Pipeline实现(按ID保留最高优先级Item)

from scrapy.exceptions import DropItem

class HighestPriorityFilterPipeline:
    def __init__(self):
        # 存储每个ID对应的最高优先级Item
        self.id_item_map = {}

    def process_item(self, item, spider):
        item_id = item["id"]
        current_prio = item["prio"]

        if item_id not in self.id_item_map:
            self.id_item_map[item_id] = item
        else:
            existing_item = self.id_item_map[item_id]
            if current_prio > existing_item["prio"]:
                # 当前Item优先级更高,替换原有数据
                self.id_item_map[item_id] = item
            else:
                # 优先级更低,丢弃当前Item
                raise DropItem(f"丢弃ID为{item_id}的Item,优先级({current_prio})低于已存Item({existing_item['prio']})")
        return item

    def close_spider(self, spider):
        # 爬虫结束时,可在此处将最终保留的Item写入文件/数据库
        for final_item in self.id_item_map.values():
            spider.logger.info(f"最终保留Item:ID={final_item['id']},优先级={final_item['prio']}")

关键注意事项

  • 缓存有效性:如果是分布式爬虫,Spider级的内存缓存无法共享,需改用Redis等分布式缓存来存储URL数据。
  • URL去重:确保存入缓存的URL是标准化的绝对路径,避免因相对路径、参数顺序不同导致的重复缓存。
  • 异常处理:可在parse_detail_page中添加请求失败的重试逻辑(如errback),避免单个URL抓取失败导致整个Item数据缺失。
  • 内存占用:如果抓取的URL数量极大,需定期清理缓存中不再使用的数据,避免内存溢出。

内容的提问来源于stack exchange,提问作者DaveFar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 05:10:39