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

如何使用Scrapy生成嵌套数据结构并爬取指定网站数据

问题分析与解决方案

你的代码核心错误在于直接将response.follow()返回的Request对象作为字典值yield,这不符合Scrapy的工作机制——Scrapy仅能处理yield出的Request或Item对象,这种混合输出会直接触发报错。

要在不牺牲并发特性的前提下生成目标嵌套结构,我们可以通过「请求传递上下文+Pipeline聚合」的方式实现:

步骤1:修改Spider,传递层级上下文

利用Scrapy请求的meta参数,逐层传递denomination(面额)和date(日期)信息,最终在最底层解析出完整的单条数据:

import scrapy

class Savings(scrapy.Spider):
    name = 'savings'
    start_urls = ['https://savings.gov.pk/download-draws/']

    def parse(self, response):
        # 遍历所有面额选项
        for option in response.css('select option'):
            denomination = option.css('::text').get().strip()
            url = option.css('::attr(value)').get()
            # 跳过空URL和默认提示选项
            if url and denomination != "Select Denomination":
                yield scrapy.Request(
                    url,
                    callback=self.parse_draw_list,
                    meta={'denomination': denomination}
                )

    def parse_draw_list(self, response):
        # 取出上层传递的面额信息
        denomination = response.meta['denomination']
        # 遍历该面额下的所有日期选项
        for a in response.css('select option'):
            date = a.css('::text').get().strip()
            url = a.css('::attr(value)').get()
            if url and date != "Select Draw Date":
                yield scrapy.Request(
                    url,
                    callback=self.parse_draw,
                    meta={'denomination': denomination, 'date': date}
                )

    def parse_draw(self, response):
        # 解析债券号码
        bond_numbers = response.selector.re(r'\d{6}')
        # 生成包含完整层级的扁平化数据
        yield {
            'denomination': response.meta['denomination'],
            'date': response.meta['date'],
            'bondNumbers': bond_numbers
        }

步骤2:编写Pipeline聚合嵌套结构

Scrapy的Pipeline会异步接收每条爬取到的数据,我们可以在这里逐步构建目标嵌套字典:

  1. 在项目的pipelines.py中添加如下代码:
import json

class SavingsPipeline:
    def open_spider(self, spider):
        # 初始化存储结果的字典
        self.final_data = {}

    def process_item(self, item, spider):
        denom = item['denomination']
        draw_date = item['date']
        bonds = item['bondNumbers']

        # 初始化面额层级
        if denom not in self.final_data:
            self.final_data[denom] = {}
        # 初始化日期层级
        if draw_date not in self.final_data[denom]:
            self.final_data[denom][draw_date] = {'bondNumbers': []}
        # 追加债券号码(如需去重可自行添加逻辑)
        self.final_data[denom][draw_date]['bondNumbers'].extend(bonds)
        return item

    def close_spider(self, spider):
        # 将最终嵌套结构保存为JSON文件
        with open('savings_bonds_data.json', 'w', encoding='utf-8') as f:
            json.dump(self.final_data, f, ensure_ascii=False, indent=2)
  1. 在项目的settings.py中启用该Pipeline:
ITEM_PIPELINES = {
    'your_project_name.pipelines.SavingsPipeline': 300,
}

(注意替换your_project_name为你的Scrapy项目名称)

为什么这个方案不牺牲并发?

Scrapy的请求依然是异步并发执行的,每个请求完成后独立将数据传递给Pipeline,Pipeline只是在后台逐步聚合数据,完全不影响爬虫的并发效率。这种方式既符合Scrapy的设计理念,又能完美生成你需要的嵌套结构。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 03:59:57