【问题标题】:Issue using Scrapy Spider Output in Python script在 Python 脚本中使用 Scrapy Spider 输出的问题
【发布时间】:2019-11-10 23:39:15
【问题描述】:

我想在 python 脚本中使用蜘蛛的输出。为此,我基于另一个thread 编写了以下代码。

我面临的问题是函数 spider_results() 只会一遍又一遍地返回最后一个项目的列表,而不是包含所有找到的项目的列表。当我使用 scrapy crawl 命令手动运行同一个蜘蛛时,我得到了所需的输出。下面是脚本的输出、手动json输出和蜘蛛本身。

我的代码有什么问题?

from scrapy import signals
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
from circus.spiders.circus import MySpider

from scrapy.signalmanager import dispatcher


def spider_results():
    results = []

    def crawler_results(signal, sender, item, response, spider):
        results.append(item)


    dispatcher.connect(crawler_results, signal=signals.item_passed)

    process = CrawlerProcess(get_project_settings())
    process.crawl(MySpider)
    process.start()  # the script will block here until the crawling is finished
    return results


if __name__ == '__main__':
    print(spider_results())

脚本输出:

{'away_odds': 1.44,
 'away_team': 'Los Angeles Dodgers',
 'event_time': datetime.datetime(2019, 6, 8, 2, 15),
 'home_odds': 2.85,
 'home_team': 'San Francisco Giants',
 'last_update': datetime.datetime(2019, 6, 6, 20, 58, 41, 655497),
 'league': 'MLB'}, {'away_odds': 1.44,
 'away_team': 'Los Angeles Dodgers',
 'event_time': datetime.datetime(2019, 6, 8, 2, 15),
 'home_odds': 2.85,
 'home_team': 'San Francisco Giants',
 'last_update': datetime.datetime(2019, 6, 6, 20, 58, 41, 655497),
 'league': 'MLB'}, {'away_odds': 1.44,
 'away_team': 'Los Angeles Dodgers',
 'event_time': datetime.datetime(2019, 6, 8, 2, 15),
 'home_odds': 2.85,
 'home_team': 'San Francisco Giants',
 'last_update': datetime.datetime(2019, 6, 6, 20, 58, 41, 655497),
 'league': 'MLB'}]

Scrapy 抓取的 Json 输出:

[
{"home_team": "Los Angeles Angels", "away_team": "Seattle Mariners", "event_time": "2019-06-08 02:07:00", "home_odds": 1.58, "away_odds": 2.4, "last_update": "2019-06-06 20:48:16", "league": "MLB"},
{"home_team": "San Diego Padres", "away_team": "Washington Nationals", "event_time": "2019-06-08 02:10:00", "home_odds": 1.87, "away_odds": 1.97, "last_update": "2019-06-06 20:48:16", "league": "MLB"},
{"home_team": "San Francisco Giants", "away_team": "Los Angeles Dodgers", "event_time": "2019-06-08 02:15:00", "home_odds": 2.85, "away_odds": 1.44, "last_update": "2019-06-06 20:48:16", "league": "MLB"}
]

我的蜘蛛:

from scrapy.spiders import Spider
from ..items import MatchItem
import json
import datetime
import dateutil.parser

class MySpider(Spider):
    name = 'first_spider'

    start_urls = ["https://websiteXYZ.com"]

    def parse(self, response):
        item = MatchItem()

        timestamp = datetime.datetime.utcnow()

        response_json = json.loads(response.body)

        for event in response_json["el"]:
            for team in event["epl"]:
                if team["so"] == 1: item["home_team"] = team["pn"]
                if team["so"] == 2: item["away_team"] = team["pn"]

            for market in event["ml"]:
                if market["mn"] == "Match result":
                    item["event_time"] = dateutil.parser.parse(market["dd"]).replace(tzinfo=None)
                    for outcome in market["msl"]:
                        if outcome["mst"] == "1": item["home_odds"] = outcome["msp"]
                        if outcome["mst"] == "X": item["draw_odds"] = outcome["msp"]
                        if outcome["mst"] == "2": item["away_odds"] = outcome["msp"]

                if market["mn"] == 'Moneyline':
                    item["event_time"] = dateutil.parser.parse(market["dd"]).replace(tzinfo=None)
                    for outcome in market["msl"]:
                        if outcome["mst"] == "1": item["home_odds"] = outcome["msp"]
                        #if outcome["mst"] == "X": item["draw_odds"] = outcome["msp"]
                        if outcome["mst"] == "2": item["away_odds"] = outcome["msp"]


            item["last_update"] = timestamp
            item["league"] = event["scn"]

            yield item

编辑:

根据下面的答案,我尝试了以下两个脚本:

controller.py

import json
from scrapy import signals
from scrapy.crawler import CrawlerRunner
from twisted.internet import reactor, defer
from betsson_controlled.spiders.betsson import Betsson_Spider
from scrapy.utils.project import get_project_settings


class MyCrawlerRunner(CrawlerRunner):
    def crawl(self, crawler_or_spidercls, *args, **kwargs):
        # keep all items scraped
        self.items = []

        # create crawler (Same as in base CrawlerProcess)
        crawler = self.create_crawler(crawler_or_spidercls)

        # handle each item scraped
        crawler.signals.connect(self.item_scraped, signals.item_scraped)

        # create Twisted.Deferred launching crawl
        dfd = self._crawl(crawler, *args, **kwargs)

        # add callback - when crawl is done cal return_items
        dfd.addCallback(self.return_items)
        return dfd

    def item_scraped(self, item, response, spider):
        self.items.append(item)

    def return_items(self, result):
        return self.items

def return_spider_output(output):
    return json.dumps([dict(item) for item in output])

settings = get_project_settings()
runner = MyCrawlerRunner(settings)
spider = Betsson_Spider()
deferred = runner.crawl(spider)
deferred.addCallback(return_spider_output)


reactor.run()
print(deferred)

当我执行 controller.py 时,我得到:

<Deferred at 0x7fb046e652b0 current result: '[{"home_team": "St. Louis Cardinals", "away_team": "Pittsburgh Pirates", "home_odds": 1.71, "away_odds": 2.19, "league": "MLB"}, {"home_team": "St. Louis Cardinals", "away_team": "Pittsburgh Pirates", "home_odds": 1.71, "away_odds": 2.19, "league": "MLB"}, {"home_team": "St. Louis Cardinals", "away_team": "Pittsburgh Pirates", "home_odds": 1.71, "away_odds": 2.19, "league": "MLB"}, {"home_team": "St. Louis Cardinals", "away_team": "Pittsburgh Pirates", "home_odds": 1.71, "away_odds": 2.19, "league": "MLB"}, {"home_team": "St. Louis Cardinals", "away_team": "Pittsburgh Pirates", "home_odds": 1.71, "away_odds": 2.19, "league": "MLB"}, {"home_team": "St. Louis Cardinals", "away_team": "Pittsburgh Pirates", "home_odds": 1.71, "away_odds": 2.19, "league": "MLB"}, {"home_team": "St. Louis Cardinals", "away_team": "Pittsburgh Pirates", "home_odds": 1.71, "away_odds": 2.19, "league": "MLB"}, {"home_team": "St. Louis Cardinals", "away_team": "Pittsburgh Pirates", "home_odds": 1.71, "away_odds": 2.19, "league": "MLB"}]'>

【问题讨论】:

  • 这是在黑暗中拍摄的,但他们已经重构了爬虫在新发布的 Scrapy 中的工作方式。在文档中查看此处所做的更改,并确定它是否有助于您的事业。你的结果表明你的 deferred 正在工作,但不知何故蜘蛛要么没有完成,要么没有关闭。 docs.scrapy.org/en/1.7/news.html
  • 感谢您为我着想。我会调查的。不确定我是否会继续在这个项目中使用 Scrapy,如果实现如此简单的功能有那么复杂。
  • 我知道我的答案是正确的答案,我们只是遗漏了一些东西。我有这段代码在 API 端点上运行生产。但我知道试图弄清楚这样的事情时的感觉。使用scrapy的所有项目和功能来实现请求同时运行可能与解决这个问题一样困难。我们至少知道 deferred 是作为回调工作的,因此您应该能够从这里解决问题。
  • 尝试在爬行函数中运行你的代码,就像我在最后一段代码中使用延迟回调装饰器所做的那样,看看是否有任何作用。我认为您可能必须停止反应器才能完成代码执行。 reactor.run() 应该阻塞,直到脚本完成但它永远不会完成。完成后,您的所有项目都应该在 deferred 变量中......
  • 更新了答案并再次尝试...尝试 crawlerprocess 而不是 runner 它似乎更多你需要的地方,因为我需要 runner。

标签: python scrapy


【解决方案1】:

近期编辑:阅读CrawlerProcess vs CrawlerRunner 后,我意识到您可能想要 CrawlerProcess。我必须使用 runner,因为我需要 klein 才能使用延迟对象。 Process 只期望在 runner 期望其他脚本/程序与之交互的地方进行抓取。希望这会有所帮助。

您需要修改 CrawlerRunner/Process 并使用信号和/或回调将项目传递到 CrawlerRunner 中的脚本中。

How to integrate Flask & Scrapy? 如果您查看顶部答案中的选项,那么带有twisted klein 和scrapy 的选项就是您正在寻找的一个示例,因为它在做同样的事情,只是在抓取后将其发送到Klein http 服务器.您可以使用 CrawlerRunner 设置类似的方法,以便在爬行时将每个项目发送到您的脚本。注意:收集项目后,此特定问题会将结果发送到 Klein Web 服务器。答案是创建一个 API,它收集结果并等待直到爬取完成并将其作为转储发送到 JSON,但您可以将相同的方法应用于您的情况。主要要看的是 CrawlerRunner 如何被子类化和扩展以添加额外的功能。

您要做的是拥有一个单独的脚本,您可以执行该脚本导入您的 Spider 并扩展 CrawlerRunner。然后你执行这个脚本,它会启动你的 Twisted reactor 并使用你的自定义运行器开始爬取过程。

也就是说——这个问题可能会在项目管道中解决。创建自定义项目管道并将项目传递到您的脚本中,然后再返回项目。

# main.py

import json
from scrapy import signals
from scrapy.crawler import CrawlerProcess
from twisted.internet import reactor, defer # import we missed
from myproject.spiders.mymodule import MySpiderName
from scrapy.utils.project import get_project_settings


class MyCrawlerProcess(CrawlerProcess):
    def crawl(self, crawler_or_spidercls, *args, **kwargs):
        # keep all items scraped
        self.items = []

        crawler = self.create_crawler(crawler_or_spidercls)

        crawler.signals.connect(self.item_scraped, signals.item_scraped)

        dfd = self._crawl(crawler, *args, **kwargs)

        dfd.addCallback(self.return_items)
        return dfd

    def item_scraped(self, item, response, spider):
        self.items.append(item)

    def return_items(self, result):
        return self.items


def return_spider_output(output):
    return json.dumps([dict(item) for item in output])


process = MyCrawlerProcess()
deferred = process.crawl(MySpider)
deferred.addCallback(return_spider_output)


process.start() - Script should block here again but I'm not sure if it will work right without using reactor.run()
print(deferred)

同样,这段代码是我没有测试过的猜测。我希望它能让你朝着更好的方向前进。

参考资料:

【讨论】:

  • 我认为使用项目管道无济于事,因为我需要另一个脚本内一个列表中的所有项目,并且我不想在每个项目上运行特定的脚本?我认为必须有一个比上述线程中提出的更简单的解决方案。想要在脚本中使用抓取的数据而不事先写入数据库应该是很常见的。
  • 所以你想在它们同时完成时传递它们?如果是这种情况,我使用 klein API 向您展示的解决方案就是您所需要的。您还可以将这两个命令链接在一起 scrapy crawl foo -o bar.csv && python foobar bar.csv 。 Scrapy 是异步的,所以它让事情变得不同。我关于创建自己的 CrawlerRunner 以收集我认为正确的项目的答案。如果您想一次传递一个,则项目管道可以正常工作。
  • 我想要一个 main_script,我可以从中:1) 运行不同的爬虫,当它们完成时,它们会返回一个列表,其中包含所有找到的项目返回到 main_script 2) 处理数据 3) 重复
  • 我在上面编辑了我的帖子。如果你能看一下就好了,
  • 查看我所做的更改。这应该可以正常工作。它应该运行收集所有项目,然后它们应该在延迟中可用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-26
  • 2018-05-18
  • 2018-10-04
  • 2020-09-26
  • 2019-08-07
  • 2015-09-13
相关资源
最近更新 更多