Scrapy循环爬取时如何单次生成含完整数组的Item
解决Scrapy异步请求导致Item多次输出的问题
这个问题的核心在于Scrapy的请求是异步并行执行的,你每次在parse_individual_tabs里yield data,都会立刻输出一个Item,而不是等所有请求完成后再输出完整结果。加上字典是可变对象,多个回调操作同一个data,就会出现每次请求返回就输出当前状态的Item,最终得到多个不完整的结果。
下面是修改后的解决方案,能让你等所有子请求完成后,只输出一次包含完整数组的Item:
步骤1:初始化完整数据结构并传递请求上下文
首先在parse_individual_listings里,把field3初始化为空列表,同时传递总请求数,方便后续判断是否所有请求都完成:
from urllib.parse import urlparse import scrapy def parse_individual_listings(self, response): # 初始化完整的数据结构,field3设为空列表 data = { 'field1': 'data1', 'field2': 'data2', # 修复你原代码里重复赋值field1的错误 'field3': [] } total_requests = 3 # 明确总共有3次请求 for i in range(total_requests): # 假设link是你从页面提取的每个子链接,这里保持你的逻辑 link = response.css('your-link-selector')[i] yield scrapy.Request( urlparse.urljoin(response.url, link['href']), callback=self.parse_individual_tabs, # 使用cb_kwargs传递数据(Scrapy 1.7+推荐,比meta更清晰) cb_kwargs={ 'data': data, 'total_requests': total_requests } )
步骤2:收集数据,完成所有请求后再yield
在parse_individual_tabs里,每次提取到数据后添加到field3,然后检查是否所有请求都已完成——只有当field3的长度等于总请求数时,才yield完整的Item:
def parse_individual_tabs(self, response, data, total_requests): # 从当前页面提取field3的对应数据(替换成你实际的提取逻辑) current_data3 = response.css('your-data-selector::text').get().strip() # 或者按你的原逻辑生成,比如 f'data3{len(data["field3"])+1}' # 把新数据添加到field3列表 data['field3'].append(current_data3) # 检查是否所有请求都已完成 if len(data['field3']) == total_requests: # 所有数据收集完毕,只yield一次完整的Item yield data
关键说明
- 避免异步顺序问题:用
append而非按索引赋值,因为异步请求的完成顺序不一定和发起顺序一致,append能保证所有提取到的数据都被加入列表。 - 单线程安全:Scrapy的回调函数在单线程事件循环中执行,多个回调操作同一个
data字典不会出现线程安全问题。 - cb_kwargs的优势:相比
meta,cb_kwargs更直观,不需要从response.meta中取数据,代码可读性更高。
这样修改后,你就会得到期望的输出:
{'field1': 'data1', 'field2': 'data2', 'field3': ['data31', 'data32', 'data33']}
内容的提问来源于stack exchange,提问作者user3360155
相关产品推荐
相关产品推荐

