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

在Klein/Twisted框架中运行多个Scrapy爬虫的问题

解决Klein+Scrapy架构下多爬虫结果丢失问题

你遇到的核心问题是当前实现仅等待第一个爬虫完成就返回HTTP响应,没有等待所有启动的爬虫全部结束。当你多次调用runner.crawl()时,每个调用都会生成独立的Deferred,但你只绑定了第一个Deferred的回调逻辑,导致第一个爬虫结束就触发了响应,后续爬虫的结果自然无法被收集返回。

以下是具体的解决方案和规范纠正:

1. 重构TwistedRunner以支持多爬虫结果收集

首先需要让Runner能够跟踪所有爬虫的结果,而不是单个爬虫的。调整存储结构为全局累积列表,同时确保每个HTTP请求的Runner实例完全独立(避免并发请求时结果混淆):

import json
import os
from klein import Klein
from scrapy import signals
from scrapy.crawler import CrawlerRunner
from twisted.web.server import Site
Site.displayTracebacks = False

class TwistedRunner(CrawlerRunner):
    def __init__(self, settings=None, crawler=None):
        super().__init__(settings, crawler)
        self.all_items = []  # 存储所有爬虫的爬取结果

    def crawl(self, Spider, *args, **kwargs):
        # 创建爬虫实例并绑定结果收集信号
        crawler = self.create_crawler(Spider)
        crawler.signals.connect(self.store_item, signals.item_scraped)
        return self._crawl(crawler, *args, **kwargs)

    def store_item(self, item):
        self.all_items.append(item)

    def format_results(self):
        # 将所有结果转换为JSON可序列化格式
        return json.dumps([dict(item) for item in self.all_items])

2. 在路由中等待所有爬虫完成

在路由处理函数中,启动所有需要的爬虫后,必须调用runner.join()等待所有爬虫任务结束,再返回完整结果:

def getSpiderResult(output):
    """Format spider result"""
    return json.dumps([dict(item) for item in output])

class Router(object):
    app = Klein()
    scrapeArgument = os.getenv('argument', 'product').encode()

    @app.route('/<path:catchall>', methods=['POST', 'GET'])
    def catchAll(self, request, catchall):
        """catch-all route"""
        request.redirect('/')

    @app.route('/', methods=['GET', 'POST'])
    def scrape(self, request):
        """Serve request for scrape"""
        if self.scrapeArgument not in request.args:
            return None
            
        runner = TwistedRunner()
        product = request.args.get(self.scrapeArgument).pop()
        
        # 启动多个爬虫
        runner.crawl(MySpiderCls, product=product)
        runner.crawl(MyOtherSpiderCls, product=product)  # 第二个示例爬虫
        
        # 等待所有爬虫任务完成
        deferred = runner.join()
        # 所有任务完成后返回格式化结果
        deferred.addCallback(lambda _: runner.format_results())
        return deferred

if __name__ == '__main__':
    Router = Router()
    Router.app.run(os.getenv('address', '0.0.0.0'), os.getenv('port', 8080))

3. 原实现的规范问题纠正

  • 并发数据隔离:原代码中每个请求创建独立的Runner实例是正确的,但单爬虫的结果存储逻辑不适用于多爬虫场景,现在改为全局累积列表后可以覆盖多爬虫需求。
  • Scrapy API使用:原代码重写crawl方法时的逻辑是合规的,但原实现只处理单个爬虫的回调,不符合多爬虫场景的需求,调整后更贴合Scrapy官方推荐的多爬虫运行方式。
  • 调试便利性:Site.displayTracebacks = False在生产环境是合理配置,但开发阶段建议暂时开启,方便排查爬虫运行中的异常。

修改完成后,HTTP请求会等待所有启动的爬虫完成后再返回完整结果,日志中所有save item记录都会出现在响应日志之前,不会再出现结果丢失的情况。

内容的提问来源于stack exchange,提问作者yusuke.yengkong

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 22:48:11