Scrapy爬虫启动后运行满20秒暂停请求再恢复的实现问题
问题描述
需要实现Scrapy爬取控制逻辑:爬虫从启动开始累计运行时长达到20秒时,暂停所有请求发送指定秒数,之后恢复正常爬取流程直到全部任务完成。现有脚本未实现预期暂停效果,爬虫始终保持正常运行,需要修改代码实现功能。
原有代码问题点
- 时间判断逻辑完全失效:判断条件
self.datetime == (self.datetime + datetime.timedelta(seconds = 20)).time()永远无法成立,左侧是启动时记录的datetime对象,右侧是time类型值,类型不匹配,且逻辑上拿固定值和自身叠加后的值对比,不可能满足相等条件 - 暂停逻辑无实际作用:代码中创建的是局部
Deferred对象,没有和Scrapy的请求调度流程绑定,且调用pause()后立刻调用unpause(),没有任何等待时长,完全不会产生暂停效果,另外Deferred.pause()也不接收请求计数作为参数 - 信号选择错误:绑定
request_scheduled信号做时长统计没有意义,该信号仅在请求被加入调度队列时触发,无法准确统计爬虫实际运行时长,也不能拦截请求发送动作
实现方案
要实现全局请求暂停,最可靠的方式是通过下载器中间件拦截所有待发送请求,配合Twisted的延迟工具实现定时暂停,具体修改步骤:
- 移除原有无效的信号绑定、时间判断、暂停相关代码
- 自定义下载中间件,在中间件中记录爬虫启动时间、暂停触发标记、需要暂停的时长
- 每次请求经过中间件时,校验运行时长,达到20秒且未触发过暂停时,返回延迟指定秒数的Deferred,延迟结束后自动恢复请求放行
- 在自定义配置中注册编写好的下载中间件
修改后的完整可运行代码:
import scrapy from scrapy import signals import datetime from twisted.internet import defer, reactor # 自定义暂停控制中间件 class PauseSpiderMiddleware: def __init__(self, pause_after_seconds, pause_duration): self.start_time = None self.pause_triggered = False self.pause_after = pause_after_seconds self.pause_duration = pause_duration @classmethod def from_crawler(cls, crawler): ext = cls( pause_after_seconds=crawler.settings.getint('PAUSE_AFTER_SECONDS', 20), pause_duration=crawler.settings.getint('PAUSE_DURATION', 10) ) crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened) return ext def spider_opened(self, spider): self.start_time = datetime.datetime.now() def process_request(self, request, spider): if self.pause_triggered: return None run_seconds = (datetime.datetime.now() - self.start_time).total_seconds() if run_seconds >= self.pause_after: self.pause_triggered = True spider.logger.info(f"已运行{run_seconds:.1f}秒,暂停爬取{self.pause_duration}秒") d = defer.Deferred() reactor.callLater(self.pause_duration, d.callback, None) return d return None class TestSpider(scrapy.Spider): name = 'signal' start_urls = [f'https://www.meadowhall.co.uk/eatdrinkshop?page={i}' for i in range(1, 15)] custom_settings = { 'DOWNLOAD_DELAY':2, 'DOWNLOADER_MIDDLEWARES': { '__main__.PauseSpiderMiddleware': 1, }, 'PAUSE_AFTER_SECONDS': 20, 'PAUSE_DURATION': 10 } def start_requests(self): for url in self.start_urls: yield scrapy.Request( url=url, callback=self.parse, cb_kwargs = { 'pg':url } ) def parse(self, response,pg): content_page = response.xpath("//div[@class='view-content']//div") url_split = pg.split('?')[-1] for cnt in content_page: image_url = cnt.xpath(".//img//@src").get() if image_url != None: yield { 'image_urls':image_url, 'url_page':url_split }
代码说明
- 暂停逻辑仅触发一次,触发后不再拦截请求,不影响后续爬取流程
- 可直接修改
PAUSE_DURATION配置项调整暂停时长,无需改动核心逻辑 - 中间件优先级设置为1,优先于其他下载中间件拦截请求,保证所有待发请求都受暂停逻辑控制
- 暂停动作不会中断已经在处理中的请求,仅阻塞暂停触发后待发送的新请求,符合爬取控制要求
内容的提问来源于stack exchange,提问作者Working dollar
相关产品推荐
相关产品推荐

