Scrapy在外部切换IP时循环重试直至失败问题求助
问题分析与解决方案
嘿,我来帮你搞定这个Scrapy在IP切换时触发的SSL版本错误问题。从你贴的日志来看,核心报错是OpenSSL.SSL.Error: [('SSL routines', 'ssl3_get_record', 'wrong version number')],这个问题本质是IP切换后,Scrapy的下载器还在复用之前绑定旧IP的连接池,而新IP对应的代理/网络环境的SSL配置和旧连接不兼容,导致SSL握手失败,进而触发无限重试循环。
下面是针对性的解决办法,按优先级排序:
1. 禁用连接复用,强制每次请求新建连接
Scrapy默认开启HTTP持久连接(keep-alive),IP切换后旧连接依然绑定原IP,继续复用就会出现SSL不匹配。你可以在settings.py里添加这些配置:
# 指定兼容现代网站的TLS版本 DOWNLOADER_CLIENT_TLS_METHOD = 'TLSv1.2' # 增加请求延迟,给IP切换留足生效时间 DOWNLOAD_DELAY = 1.5 # 每个域名同时只发一个请求,避免连接池复用冲突 CONCURRENT_REQUESTS_PER_DOMAIN = 1
还可以自定义下载器中间件,彻底清除旧连接:
from scrapy.downloadermiddlewares.httpcompression import HttpCompressionMiddleware class ResetConnectionMiddleware(HttpCompressionMiddleware): def process_request(self, request, spider): # 强制关闭之前的连接池,避免复用旧IP的连接 if hasattr(self, 'pool'): self.pool.close() self.pool = None # 重置请求超时和重定向设置 request.meta['download_timeout'] = 12 return None
然后在settings.py里注册这个中间件:
DOWNLOADER_MIDDLEWARES = { 'your_project.middlewares.ResetConnectionMiddleware': 543, }
2. 自定义重试中间件,针对SSL错误强制换IP
当遇到特定SSL版本错误时,不要只是无脑重试,而是主动触发IP切换并重置请求的代理信息。扩展Scrapy的RetryMiddleware:
from scrapy.downloadermiddlewares.retry import RetryMiddleware import OpenSSL class CustomRetryMiddleware(RetryMiddleware): def process_exception(self, request, exception, spider): # 检测是否是目标SSL版本错误 if isinstance(exception, OpenSSL.SSL.Error) and 'wrong version number' in str(exception): # 调用你的IP切换程序切换IP spider.ip_switcher.switch_ip() # 更新请求的代理为新IP对应的代理 new_proxy = spider.ip_switcher.get_current_proxy() request.meta['proxy'] = new_proxy # 标记请求不被过滤,强制重新发起 request.dont_filter = True return self._retry(request, "SSL版本不匹配,已切换IP", spider) return super().process_exception(request, exception, spider)
接着在settings.py里替换默认的重试中间件:
DOWNLOADER_MIDDLEWARES = { 'scrapy.downloadermiddlewares.retry.RetryMiddleware': None, 'your_project.middlewares.CustomRetryMiddleware': 550, }
3. 调整IP切换时机,避开请求处理中
尽量不要在Scrapy正在建立连接、传输数据的时候切换IP。可以把IP切换逻辑放在请求完成后或者批量请求结束后:
比如用Scrapy的信号机制,每处理10个请求就切换一次IP:
from scrapy import signals import scrapy class EbaySpider(scrapy.Spider): name = 'ebay_spider' @classmethod def from_crawler(cls, crawler, *args, **kwargs): spider = super().from_crawler(crawler, *args, **kwargs) crawler.signals.connect(spider.switch_ip_after_batch, signal=signals.request_finished) return spider def switch_ip_after_batch(self, request, response, spider): # 每处理10个请求切换一次IP if self.crawler.stats.get_value('item_scraped_count', 0) % 10 == 0: self.ip_switcher.switch_ip()
4. 提前验证新代理的SSL可用性
IP切换后,先手动测试新代理是否能正常访问目标网站,再让Scrapy使用它:
import requests def validate_proxy(proxy): try: # 测试代理访问目标网站的SSL连接 resp = requests.get( 'http://www.ebay.com', proxies={'https': proxy}, verify=False, timeout=10 ) return resp.status_code == 200 except Exception as e: self.logger.warning(f"新代理 {proxy} 验证失败: {e}") return False
在你的IP切换程序里,只有当validate_proxy返回True时,才把这个代理传给Scrapy。
内容的提问来源于stack exchange,提问作者Cai Allin
相关产品推荐
相关产品推荐

