【问题标题】:Scrapy on a schedule按计划报废
【发布时间】:2017-10-28 22:59:08
【问题描述】:

让 Scrapy 按计划运行让我在 Twist(ed) 中四处奔波。

我认为下面的测试代码会起作用,但是当蜘蛛被第二次触发时,我得到一个twisted.internet.error.ReactorNotRestartable 错误:

from quotesbot.spiders.quotes import QuotesSpider
import schedule
import time
from scrapy.crawler import CrawlerProcess

def run_spider_script():
    process.crawl(QuotesSpider)
    process.start()


process = CrawlerProcess({
    'USER_AGENT': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)',
})


schedule.every(5).seconds.do(run_spider_script)

while True:
    schedule.run_pending()
    time.sleep(1)

我猜想,作为 CrawlerProcess 的一部分,Twisted Reactor 会被调用以重新启动,而这不是必需的,因此程序会崩溃。有什么办法可以控制吗?

此外,在这个阶段,如果有另一种方法可以使 Scrapy 蜘蛛按计划自动运行,我会全力以赴。我试过 scrapy.cmdline.execute ,但也无法让它循环:

from quotesbot.spiders.quotes import QuotesSpider
from scrapy import cmdline
import schedule
import time
from scrapy.crawler import CrawlerProcess


def run_spider_cmd():
    print("Running spider")
    cmdline.execute("scrapy crawl quotes".split())


process = CrawlerProcess({
    'USER_AGENT': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)',
})


schedule.every(5).seconds.do(run_spider_cmd)

while True:
    schedule.run_pending()
    time.sleep(1)

编辑

添加代码,它使用 Twisted task.LoopingCall() 每隔几秒运行一次测试蜘蛛。我是否完全错误地安排每天在同一时间运行的蜘蛛?

from twisted.internet import reactor
from twisted.internet import task
from scrapy.crawler import CrawlerRunner
import scrapy

class QuotesSpider(scrapy.Spider):
    name = 'quotes'
    allowed_domains = ['quotes.toscrape.com']
    start_urls = ['http://quotes.toscrape.com/']

    def parse(self, response):

        quotes = response.xpath('//div[@class="quote"]')

        for quote in quotes:

            author = quote.xpath('.//small[@class="author"]/text()').extract_first()
            text = quote.xpath('.//span[@class="text"]/text()').extract_first()

            print(author, text)


def run_crawl():

    runner = CrawlerRunner()
    runner.crawl(QuotesSpider)


l = task.LoopingCall(run_crawl)
l.start(3)

reactor.run()

【问题讨论】:

  • 为什么不简单地使用 cron 或 systemd 计时器?
  • 网络抓取数据只是预期应用程序的一部分,我希望将所有内容作为单个程序的一部分运行。但是,是的,如果我不能按照描述的那样工作,我将使用 OS 任务调度程序来运行 Scrapy 脚本,其余的应用程序单独运行。

标签: python web-scraping scrapy twisted


【解决方案1】:

第一个值得注意的声明,通常只有 一个 Twisted reactor 正在运行并且它不可重新启动(正如您所发现的)。第二个是应避免阻塞任务/功能(即time.sleep(n)),并应替换为异步替代方案(例如'reactor.task.deferLater(n,...)`)。

要在 Twisted 项目中有效地使用 Scrapy,需要 scrapy.crawler.CrawlerRunner 核心 API,而不是 scrapy.crawler.CrawlerProcess。两者的主要区别在于 CrawlerProcess 为您运行 Twisted 的 reactor(因此很难重新启动反应器),而 CrawlerRunner 依赖于开发人员来启动反应器。以下是您的代码使用CrawlerRunner 后的样子:

from twisted.internet import reactor
from quotesbot.spiders.quotes import QuotesSpider
from scrapy.crawler import CrawlerRunner

def run_crawl():
    """
    Run a spider within Twisted. Once it completes,
    wait 5 seconds and run another spider.
    """
    runner = CrawlerRunner({
        'USER_AGENT': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)',
        })
    deferred = runner.crawl(QuotesSpider)
    # you can use reactor.callLater or task.deferLater to schedule a function
    deferred.addCallback(reactor.callLater, 5, run_crawl)
    return deferred

run_crawl()
reactor.run()   # you have to run the reactor yourself

【讨论】:

  • 谢谢@notorious.no ,这已经开始为我解决问题了,但不幸的是我无法按计划进行。我可能遗漏了一些明显的东西,但我不知道如何实现它以在每天的特定时间运行蜘蛛?我能得到的最接近的方法是使用 Twisted task.LoopingCall(),我可以使用它每 86400 秒运行一次蜘蛛以进行每日刮擦,但我是不是走错了路?我已经用循环代码更新了我的帖子,非常感谢您的指导!
  • LoopingCall 可以正常工作,是最简单的解决方案。您还可以修改示例代码(即addCallback(reactor.callLater, 5, run_crawl))并将5 替换为代表您接下来要抓取的秒数。与LoopingCall 相比,这将为您提供更高的精度
  • 谢谢@notorious.no。我误解了deferred.addCallback 发生的事情,调试中的一些时间戳,它开始变得有意义了。这终于对我有用了,非常感谢您的帮助!
【解决方案2】:

你可以使用apscheduler

pip install apscheduler
# -*- coding: utf-8 -*-
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings
from apscheduler.schedulers.twisted import TwistedScheduler

from Demo.spiders.baidu import YourSpider

process = CrawlerProcess(get_project_settings())
scheduler = TwistedScheduler()
scheduler.add_job(process.crawl, 'interval', args=[YourSpider], seconds=10)
scheduler.start()
process.start(False)

【讨论】:

  • 这不适用于 Django:蜘蛛将打开但不会抓取或阻止服务器的初始化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-12
  • 2013-12-06
  • 1970-01-01
相关资源
最近更新 更多