如何在Scrapy爬虫结束前重试未完成的请求?
Scrapy爬虫结束前重试未完成URL的解决方案
问题描述
使用Scrapy和Python3.6.8搭建爬虫时,尝试通过__del__方法在爬虫类销毁前重试记录的未完成URL,但仅执行打印操作后爬虫直接退出,重试逻辑未生效。相关代码如下:
记录未完成URL的逻辑:
self.urls.append(url) item = myItem() item["mylink"] = url yield scrapy.Request( url="myurl", method='GET', headers=self.headers, callback=self.parse_detail, errback=self.make_new_request, meta={"item":item})
尝试在析构函数中重试:
def __del__(self): print("\033[31myielded:",len(self.yielded),"scrapying:",len(self.urls),"\033[0m") if len(self.urls)>0: print(self.urls) print("\033[31mretry uncompleted\033[0m") self.RetryUncompletedUrls()
原因分析
__del__是Python的析构函数,当它被调用时,Scrapy的爬虫生命周期已接近尾声,引擎已经停止调度新请求。此时即使调用重试方法,也无法将请求提交给引擎执行,因此只会打印日志后直接退出。
正确实现方案
使用Scrapy的**spider_closed信号**,该信号在爬虫准备关闭时触发,此时引擎仍可接受并调度新请求,适合处理未完成URL的重试。
完整代码示例
from scrapy import signals from scrapy.exceptions import DontCloseSpider import scrapy class myItem(scrapy.Item): mylink = scrapy.Field() class MySpider(scrapy.Spider): name = "my_spider" urls = [] # 存储未完成/失败的URL yielded = [] # 记录已提交的请求数 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 连接spider_closed信号到自定义处理函数 self.crawler.signals.connect(self.retry_uncompleted_urls, signal=signals.spider_closed) def start_requests(self): # 初始请求逻辑(示例) yield scrapy.Request(url="初始URL", callback=self.parse) def parse(self, response): # 解析页面生成待爬取URL(示例逻辑) url = "待爬取的目标URL" self.urls.append(url) item = myItem() item["mylink"] = url self.yielded.append(url) yield scrapy.Request( url=url, method='GET', headers=self.headers, callback=self.parse_detail, errback=self.handle_request_failure, meta={"item": item, "retry_count": 0} # 添加重试计数,避免无限重试 ) def parse_detail(self, response): # 详情页解析逻辑 item = response.meta["item"] # 解析完成后从urls中移除该URL if item["mylink"] in self.urls: self.urls.remove(item["mylink"]) # 处理并输出item yield item def handle_request_failure(self, failure): # 请求失败时的处理逻辑 request = failure.request item = request.meta["item"] retry_count = request.meta.get("retry_count", 0) # 设置最大重试次数(示例为3次) max_retry = 3 if retry_count < max_retry: # 更新重试计数,重新发起请求 new_meta = request.meta.copy() new_meta["retry_count"] = retry_count + 1 self.logger.warning(f"请求失败,重试第{new_meta['retry_count']}次: {item['mylink']}") yield request.replace(meta=new_meta) else: # 超过重试次数,加入未完成URL列表等待最终重试 if item["mylink"] not in self.urls: self.urls.append(item["mylink"]) def retry_uncompleted_urls(self, spider, reason): # 处理未完成URL的最终重试 if not self.urls: return self.logger.info(f"开始重试{len(self.urls)}个未完成/失败的URL") for url in self.urls.copy(): # 使用copy避免遍历中修改列表 item = myItem() item["mylink"] = url # 构造重试请求,设置初始重试计数 req = scrapy.Request( url=url, method='GET', headers=self.headers, callback=self.parse_detail, errback=self.handle_request_failure, meta={"item": item, "retry_count": 0} ) # 直接通过引擎调度请求 self.crawler.engine.crawl(req, spider=self) # 从列表中移除,避免重复处理 self.urls.remove(url) # 抛出DontCloseSpider,阻止爬虫立即关闭,直到重试请求处理完成 raise DontCloseSpider("正在处理未完成URL的重试请求")
关键说明
- 信号连接:在爬虫初始化时连接
spider_closed信号到处理函数,确保在爬虫关闭前触发重试逻辑。 - 请求调度:通过
self.crawler.engine.crawl()直接将重试请求提交给Scrapy引擎,确保请求被执行。 - 避免无限重试:给每个请求添加
retry_count元数据,设置最大重试次数,防止陷入死循环。 - 阻止爬虫关闭:抛出
DontCloseSpider异常,让爬虫继续运行直到所有重试请求处理完成。
内容的提问来源于stack exchange,提问作者user2155362
相关产品推荐
相关产品推荐

