1. 爬虫异常处理的核心挑战
爬虫开发中最让人头疼的不是数据提取逻辑,而是那些防不胜防的异常情况。上周我的一个电商价格监控爬虫突然大面积失效,排查发现是目标网站改了CSS选择器结构,但更麻烦的是重试机制设计缺陷导致大量重复请求。这促使我系统梳理了Scrapy框架下的异常处理方案。
网络爬虫运行时会遇到几类典型问题:
- HTTP异常(403/404/500等状态码)
- 连接超时(ConnectTimeout/ReadTimeout)
- 反爬机制(验证码、IP封禁)
- 数据解析异常(XPath/CSS选择器失效)
- 业务逻辑异常(数据校验失败)
这些异常如果不妥善处理,轻则丢失部分数据,重则导致整个爬虫任务失败。合理的异常处理机制应该具备:
- 异常自动识别能力
- 分级处理策略(立即重试/延迟重试/放弃)
- 异常信息持久化记录
- 资源释放保障
2. Scrapy内置异常处理机制解析
2.1 默认异常处理流程
Scrapy框架本身已经内置了基础的异常处理逻辑。当下载器中间件(Downloader Middleware)或爬虫回调函数中抛出异常时,框架会按照以下路径处理:
- 下载器捕获到异常(如TimeoutError)
- 通过下载器中间件链传递异常
- 最终到达引擎的异常处理器
- 根据异常类型决定重试或丢弃请求
关键源码位置:
scrapy/core/downloader/middleware.pyscrapy/core/scraper.py
2.2 重试机制配置参数
在settings.py中有几个关键配置项控制重试行为:
# 启用重试中间件 RETRY_ENABLED = True # 最大重试次数(不包括第一次请求) RETRY_TIMES = 2 # 需要重试的HTTP状态码 RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408, 429] # 重试延迟策略(毫秒) RETRY_DELAY = 1000提示:对于反爬严格的网站,建议将429状态码(Too Many Requests)加入重试列表,但需要配合合理的延迟设置。
3. 自定义异常处理中间件开发
3.1 中间件基础结构
创建middlewares.py文件,添加自定义异常处理中间件:
from scrapy.downloadermiddlewares.retry import RetryMiddleware from scrapy.utils.response import response_status_message class CustomRetryMiddleware(RetryMiddleware): def __init__(self, settings): super().__init__(settings) self.max_retry_times = settings.getint('RETRY_TIMES') self.retry_http_codes = set(settings.getlist('RETRY_HTTP_CODES')) self.priority_adjust = settings.getint('RETRY_PRIORITY_ADJUST') def process_response(self, request, response, spider): # 自定义响应处理逻辑 return super().process_response(request, response, spider)3.2 异常分类处理策略
针对不同类型的异常需要采用差异化策略:
def process_exception(self, request, exception, spider): if isinstance(exception, TimeoutError): # 网络超时类异常立即重试 return self._retry(request, exception, spider) elif isinstance(exception, ConnectionError): # 连接错误延迟重试 request.meta['retry_delay'] = 5 # 5秒延迟 return self._retry(request, exception, spider) elif 'Captcha' in str(exception): # 遇到验证码触发特殊处理流程 return self.handle_captcha(request, spider) return None3.3 智能重试延迟算法
固定重试间隔容易被识别,建议实现动态延迟:
def get_retry_delay(self, request): base_delay = self.retry_delay retries = request.meta.get('retry_times', 0) # 指数退避算法 delay = min(base_delay * (2 ** retries), self.max_retry_delay) # 添加随机抖动避免规律性 jitter = random.uniform(0.8, 1.2) return delay * jitter4. 高级异常处理技巧
4.1 代理IP自动切换
在中间件中集成代理池管理:
def process_request(self, request, spider): proxy = get_proxy_from_pool() # 从代理池获取IP request.meta['proxy'] = f"http://{proxy.ip}:{proxy.port}" request.meta['proxy_fail_count'] = 0 # 初始化失败计数 def process_exception(self, request, exception, spider): if 'proxy' in request.meta: proxy = request.meta['proxy'] if isinstance(exception, (ConnectionError, TimeoutError)): # 代理失败时切换新IP mark_proxy_failed(proxy) new_proxy = get_proxy_from_pool() request.meta['proxy'] = new_proxy return request4.2 验证码识别集成
当检测到验证码时自动触发识别流程:
def handle_captcha(self, request, spider): if 'captcha_retries' not in request.meta: request.meta['captcha_retries'] = 0 if request.meta['captcha_retries'] >= 3: spider.crawler.stats.inc_value('captcha/failed') return None # 放弃重试 # 获取验证码图片并识别 captcha_url = extract_captcha_url(request) captcha_text = solve_captcha(captcha_url) # 修改原始请求添加验证码参数 new_request = request.replace( body=add_captcha_to_formdata(request.body, captcha_text) ) new_request.meta['captcha_retries'] += 1 return new_request4.3 异常数据监控
通过Scrapy扩展实现异常监控:
class ExceptionMonitorExtension: def __init__(self, stats): self.stats = stats @classmethod def from_crawler(cls, crawler): ext = cls(crawler.stats) crawler.signals.connect(ext.spider_error, signal=signals.spider_error) return ext def spider_error(self, failure, response, spider): exception = failure.value self.stats.inc_value(f'exception_count/{exception.__class__.__name__}') # 记录异常请求详情 if response: self.log_error_details(response.url, str(exception))5. 实战:电商网站爬虫异常处理
5.1 典型异常场景
以某电商网站为例,我们遇到的主要异常类型:
| 异常类型 | 出现频率 | 处理策略 |
|---|---|---|
| 403 Forbidden | 15% | 更换UserAgent+代理IP |
| 503 Service Unavailable | 8% | 指数退避重试 |
| 商品下架(404) | 5% | 直接丢弃请求 |
| 验证码弹窗 | 12% | 自动识别+人工备用 |
| JSON解析错误 | 3% | 数据清洗后重试 |
5.2 配置示例
# settings.py CUSTOM_RETRY_MIDDLEWARE = { 'RETRY_TIMES': 3, 'RETRY_DELAY': 2000, 'MAX_RETRY_DELAY': 60000, 'PROXY_POOL_SIZE': 10, 'CAPTCHA_SOLVER': 'third_party_service' } # middlewares.py class EcommerceRetryMiddleware(CustomRetryMiddleware): def process_response(self, request, response, spider): if 'anti_spider' in response.text: raise CaptchaException("Anti-spider detected") if 'product discontinued' in response.text: spider.crawler.stats.inc_value('product/discontinued') return None # 不重试 return super().process_response(request, response, spider)5.3 性能优化对比
优化前后关键指标对比:
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 成功率 | 68% | 92% | +35% |
| 平均耗时 | 4.2s | 3.1s | -26% |
| 代理IP消耗 | 120个/天 | 45个/天 | -62% |
| 验证码处理 | 手动 | 80%自动 | 效率提升5倍 |
6. 异常处理最佳实践
分级日志记录:
- DEBUG:记录所有重试操作
- INFO:记录异常类型统计
- WARNING:记录需要人工干预的异常
- ERROR:记录关键失败点
监控指标设计:
STATS_KEYS = [ 'retry/count', 'retry/reason_count/<reason>', 'exception_count/<type>', 'captcha/success', 'proxy/failed' ]熔断机制:
if self.stats.get_value('exception_count/ProxyError', 0) > 50: spider.logger.warning("Proxy pool quality degraded, pausing crawl") raise CloseSpider('ProxyPoolExhausted')自动化测试方案:
- 使用
responses库模拟各种异常响应 - 定期回放历史异常请求验证处理逻辑
- 混沌工程:随机注入异常测试系统健壮性
- 使用
7. 常见问题排查指南
问题1:重试循环导致无限请求
- 检查
RETRY_TIMES设置 - 确保异常类型判断准确
- 添加请求指纹去重
问题2:代理IP消耗过快
- 实现代理健康检查
- 不同异常类型使用不同代理策略
- 考虑使用按量付费的云代理服务
问题3:验证码识别率低
- 多引擎备用(本地OCR+第三方API)
- 人工打码队列集成
- 验证码出现频率监控
问题4:异常丢失不重试
- 检查中间件顺序
- 确认所有异常类型都被处理
- 检查
process_exception返回值
8. 进阶优化方向
机器学习异常预测:
- 使用历史异常数据训练预测模型
- 在请求发出前评估失败概率
- 动态调整请求优先级
分布式协同处理:
- 集群共享异常信息
- 统一代理IP黑名单
- 协同验证码识别
自适应策略调整:
def adjust_policy_based_on_time(): if 2 < datetime.now().hour < 6: # 夜间降低请求频率 self.retry_delay *= 1.5浏览器自动化降级:
if retry_times > 2 and 'dynamic_content' in request.meta: # 关键请求降级使用Selenium return selenium_fetch(request)
爬虫异常处理没有一劳永逸的方案,需要根据目标网站特点持续优化。我的经验是建立完善的监控体系比追求100%成功率更重要,知道哪里出了问题才能针对性改进。最后分享一个实用技巧:为每个重要爬虫建立异常处理checklist,记录历史问题和解决方案,这会大大减少后续维护成本。