如何使用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会异步接收每条爬取到的数据,我们可以在这里逐步构建目标嵌套字典:
- 在项目的
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)
- 在项目的
settings.py中启用该Pipeline:
ITEM_PIPELINES = { 'your_project_name.pipelines.SavingsPipeline': 300, }
(注意替换your_project_name为你的Scrapy项目名称)
为什么这个方案不牺牲并发?
Scrapy的请求依然是异步并发执行的,每个请求完成后独立将数据传递给Pipeline,Pipeline只是在后台逐步聚合数据,完全不影响爬虫的并发效率。这种方式既符合Scrapy的设计理念,又能完美生成你需要的嵌套结构。
内容的提问来源于stack exchange,提问作者Hammad Ahmed
相关产品推荐
相关产品推荐

