如何编写clock.py实现Heroku已部署Scrapy爬虫的定时调度
问题产生原因
- APScheduler的默认执行逻辑不会等待
crawl()函数返回的Twisted Deferred对象执行完成:你用@defer.inlineCallbacks装饰的crawl()函数返回的是异步Deferred对象,APScheduler触发任务后接收到Deferred就直接标记任务执行成功,没有等待爬虫实际爬取逻辑运行完成,所以出现日志显示任务成功但没有采集到数据的情况。 - 原代码中
crawl()函数执行完成后调用了reactor.stop():定时调度场景下Reactor需要持续运行才能响应后续的定时任务,第一次任务执行完停止Reactor后,后续所有定时任务都会失效。 - 全局CrawlerRunner实例复用可能存在的上下文冲突:多次触发任务时复用同一个CrawlerRunner实例,可能出现上一次任务残留的配置或状态影响下一次任务运行的问题。
解决方法
步骤1:修改crawl函数逻辑
移除reactor.stop()调用,改为每次触发任务时新建CrawlerRunner实例,避免上下文冲突,同时确保Deferred执行完成后自动清理资源。
步骤2:配置APScheduler正确处理Deferred异步任务
TwistedScheduler原生支持Deferred返回值,只要调整任务提交逻辑即可让调度器等待爬虫任务实际完成再标记任务状态。
修正后完整代码示例
from scrapy.utils.project import get_project_settings from twisted.internet import reactor, defer from scrapy.crawler import CrawlerRunner from scrapy.utils.log import configure_logging from apscheduler.schedulers.twisted import TwistedScheduler import pytz from bike_price.spiders.a4en1 import A4en1Spider from bike_price.spiders.lepoledeloccasion import LepoledeloccasionSpider configure_logging() @defer.inlineCallbacks def crawl(): # 每次任务触发新建CrawlerRunner实例,避免上下文冲突 runner = CrawlerRunner(get_project_settings()) yield runner.crawl(A4en1Spider) yield runner.crawl(LepoledeloccasionSpider) # 移除reactor.stop(),保持reactor持续运行供后续调度使用 if __name__ == '__main__': scheduler = TwistedScheduler(timezone=pytz.timezone('Europe/Paris')) scheduler.add_job(crawl, trigger='cron', hour='17', minute='49') scheduler.start() reactor.run()
Heroku平台适配注意点
- 若使用免费dyno,休眠机制会导致定时任务无法按时触发,可升级付费dyno,或替换为Heroku官方Scheduler插件实现定时触发,配置成本更低。
- 确保
Procfile中已正确配置时钟进程:clock: python clock.py,部署后需要在Heroku后台手动开启clock进程的运行规模。
内容的提问来源于stack exchange,提问作者Anthony De Faria
相关产品推荐
相关产品推荐

