在 Klein/Twisted 中运行多个爬虫蜘蛛

时间:2021-07-30 15:57:22

标签: python asynchronous scrapy twisted

目前我正在研究一个作为 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 有什么我没有做的事情,请告诉我。

谢谢!

1 个答案:

答案 0 :(得分: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 文档中的示例,您将能够使用多个蜘蛛进行抓取。