使用Scrapy爬取多页面数据实现单赛事嵌套字典输出的问题
解决方案
完全可以实现你要的嵌套输出效果,核心思路是把已解析到的赛事对象通过请求的参数传递给 roster 页面的回调函数,等所有队伍的roster数据都采集完成后,再一次性yield完整的赛事对象。
核心实现步骤
- 第一步:解析赛事主页面时,先把赛事名称、场地、日期这些基础字段存到一个赛事item里,同时解析出所有队伍的队名、排名,以及对应的roster页面链接,把这些队伍基础信息暂存到一个列表里,同时统计需要请求的roster页面总数。
- 第二步:遍历所有队伍的roster链接,发起请求时通过
cb_kwargs(Scrapy 2.0+ 推荐)或者meta参数把当前的赛事item、当前请求的队伍索引、待完成的请求计数这些信息传递给回调函数。 - 第三步:在roster页面的回调函数里,解析出当前队伍的球员名单,填充到对应队伍对象的roster字段里,每完成一个请求就把待完成计数减1。
- 第四步:当待完成计数归0时,说明所有队伍的roster都采集完成,把填充完所有队伍数据的rank数组赋值给赛事item的rank字段,再yield这个完整的赛事item即可。
示例代码片段
import scrapy from yourproject.items import TournamentItem, TeamItem class TournamentSpider(scrapy.Spider): name = "tournamentspider" def start_requests(self): yield scrapy.Request(url=self.start_url, callback=self.parse_tournament) def parse_tournament(self, response): # 先解析赛事基础信息 tournament = TournamentItem() tournament['name'] = response.xpath('//h1/text()').get() tournament['venue'] = response.xpath('//div[@class="venue"]/text()').get() tournament['date'] = response.xpath('//div[@class="date"]/text()').get() tournament['rank'] = [] # 解析排名表的所有队伍 team_rows = response.xpath('//table[@class="rank"]//tr')[1:] # 暂存所有队伍的基础信息和roster链接 team_tasks = [] for row in team_rows: team = TeamItem() team['name'] = row.xpath('./td[2]/a/text()').get() team['rank'] = row.xpath('./td[1]/text()').get() roster_url = row.xpath('./td[2]/a/@href').get() team_tasks.append({ 'team': team, 'roster_url': response.urljoin(roster_url) }) # 总共有多少个队伍需要采集roster total_teams = len(team_tasks) if total_teams == 0: # 没有队伍直接返回赛事item yield tournament return # 遍历发起roster请求,传递必要的上下文 for idx, task in enumerate(team_tasks): yield scrapy.Request( url=task['roster_url'], callback=self.parse_roster, cb_kwargs={ 'tournament': tournament, 'team': task['team'], 'total_teams': total_teams } ) def parse_roster(self, response, tournament, team, total_teams): # 解析球员名单 roster = response.xpath('//ul[@class="players"]/li/text()').getall() team['roster'] = roster # 把当前队伍加到赛事的rank列表里 tournament['rank'].append(team) # 当所有队伍都处理完了再返回完整的赛事对象 if len(tournament['rank']) == total_teams: yield tournament
注意事项
- 如果要保证rank数组里的队伍顺序和页面上的排名顺序一致,可以预先给tournament['rank']初始化一个和总队伍数等长的空列表,填充的时候按索引赋值,避免因为异步请求返回顺序不一致导致的排名混乱。
- 如果是旧版本的Scrapy不支持cb_kwargs,把参数放到
meta={'tournament': tournament, ...}里,回调函数里通过response.meta取值即可。 - 不需要额外做数据关联,所有数据都在请求的上下文里传递,最终输出的就是你要的嵌套结构的单个赛事JSON对象。
内容的提问来源于stack exchange,提问作者kiril
相关产品推荐
相关产品推荐

