【问题标题】:Running multiple scrapy spiders inside Klein/Twisted在 Klein/Twisted 中运行多个 scrapy 蜘蛛
【发布时间】:2021-07-30 15:57:22
【问题描述】:

目前我正在开发一个作为 API 运行的蜘蛛项目,因此我对在 HTTP 服务器中运行 scrapy 进行了一些研究。为了简单起见,我选择了 Python Klein,基本上是这样的:

https://github.com/betinacosta/scrapy-klein-tutorial/blob/master/README%5BEN-US%5D.md

目前,我的代码如下所示(Python 3.9):


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 crawl(self, Spider, *args, **kwargs):
        self.items = []

        # create spider instance
        crawler = self.create_crawler(Spider)
        crawler.signals.connect(self.storeItem, signals.item_scraped)

        # create deferred crawler-object and register callback
        deferred = self._crawl(crawler, *args, **kwargs)
        deferred.addCallback(self.getItems)

        return deferred

    def storeItem(self, item):
        self.items.append(item)

    def getItems(self, item):
        return self.items

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()
        a = Runner.crawl(MySpiderCls, product=product)
        a.addCallback(getSpiderResult)
        return a


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

这工作正常,正如预期的那样。现在,我很想从这一点上运行多个蜘蛛。文档对此非常清楚:

https://docs.scrapy.org/en/latest/topics/practices.html

但是,当我做类似的事情时

runner = CrawlerRunner()
runner.crawl(MySpider1)
runner.crawl(MySpider2)
d = runner.join()

我看到所有蜘蛛都在运行,但是一旦第一个蜘蛛完成,HTTP 请求就完成了。此时,可能还有蜘蛛尚未完成,因此我缺少项目。为了说明这种行为,请参阅一些示例日志:

2021-07-29 21:14:27+0200 [-] save item
2021-07-29 21:14:27+0200 [-] (TCP Port 6024 Closed)
2021-07-29 21:14:27+0200 [-] "127.0.0.1" - - [29/Jul/2021:19:14:27 +0000] "GET /?product=anything HTTP/1.1" 200 3 "-" "curl/7.78.0"
2021-07-29 21:14:27+0200 [-] save item

如您所见,最终保存的项目将永远不会回显给用户,因为请求已在上面完成。

有人知道如何通过这个设置运行多个蜘蛛吗?另外,如果有什么我没有按照scrapy-doctrine做的事情,请告诉我。

谢谢!

【问题讨论】:

    标签: python asynchronous scrapy twisted


    【解决方案1】:

    您正在使用CrawlerRunner 的子类,并且您已经重载了crawl() 方法,因此它不会跟踪活动的爬网。你必须添加self._crawl(...),但我不建议这样做,因为你会调用一个 Scrapy 作者打算私有的函数。

    就像一个学习练习,如果你不改变你的TwistedRunner,你的代码就是这样的

    @app.route('/', methods=['GET', 'POST'])
    def scrape(self, request):
       ds= set()
       Runner = TwistedRunner()
       ds.add(Runner.crawl(MySpider1))
       ds.add(Runner.crawl(MySpider2))
       ds.add(Runner.crawl(MySpider3))
       return defer.gatherResults(ds)
    

    由于您的 Runner.crawl() 返回延迟,本示例将它们添加到集合中,然后您等待使用 defer.gatherResults()

    最后,这是你应该做的。不要继承您自己的 CrawlerRunner 并按原样使用该类。按照 Scrapy 文档中的示例,您将能够使用多个蜘蛛进行抓取。

    【讨论】:

    • 谢谢!我根据您的建议更新了我的def scrape(),并添加了原始回调:ds.addCallback(getSpiderResult) return ds
    • 前面好像有新的麻烦。但是一切正常,爬虫结果通过 item-pipeline 保存 N 次,其中 N 是运行的蜘蛛数。我只能提取第一个完整的结果帧,但为什么会发生这种情况?谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-21
    • 2017-02-15
    • 1970-01-01
    相关资源
    最近更新 更多