
1. 爬虫稳定性为何如此重要刚入门的爬虫开发者最容易犯的错误就是只关注功能实现而忽视稳定性建设。我见过太多新手写的爬虫脚本运行几小时后就莫名其妙卡死或是被目标网站封禁后完全无法恢复。这些问题的根源往往在于没有正确处理网络请求中的超时、重试和退避机制。上周就遇到个典型案例某电商爬虫在凌晨突然停止工作开发者排查半天才发现是因为某个API接口响应变慢而脚本中没有设置读取超时导致所有工作线程都被挂起。更糟的是由于缺乏重试机制整个爬取流程就此中断。这种问题在真实业务场景中可能造成严重后果——轻则数据丢失重则错过关键商机。2. 超时机制详解2.1 连接超时 vs 读取超时Requests库中的超时分为两种类型理解它们的区别至关重要# 同时设置连接超时和读取超时 response requests.get(url, timeout(3.05, 27))连接超时(connect timeout): 指从发起请求到建立TCP连接的最大等待时间建议设置为3-5秒。这个阶段可能遇到的典型问题包括DNS解析失败目标服务器拒绝连接网络路由问题读取超时(read timeout): 指建立连接后等待服务器返回数据的最大时间根据接口特性通常设置10-30秒。这个阶段常见的问题有服务器处理请求耗时过长网络延迟导致数据包传输缓慢大文件下载时带宽不足重要提示如果不设置读取超时请求可能会永远挂起这是新手最常踩的坑。2.2 超时值的黄金法则根据多年爬虫运维经验我总结出这些超时设置原则对于高可用API接口连接超时2-3秒读取超时5-10秒对于不稳定网站连接超时5-8秒读取超时15-30秒文件下载场景单独设置streamTrue和分块读取超时with requests.get(url, timeout(5, 30), streamTrue) as r: for chunk in r.iter_content(chunk_size8192): # 每个数据块有独立的超时控制 process(chunk)3. 智能重试机制实现3.1 基础重试方案最简单的重试实现方式import random from time import sleep def safe_request(url, max_retries3): for attempt in range(max_retries): try: response requests.get(url, timeout(3, 10)) return response except Exception as e: if attempt max_retries - 1: raise sleep(1 * (attempt 1)) # 线性等待 return None这种方案存在明显缺陷重试间隔固定容易触发服务器的反爬机制。3.2 指数退避算法进阶生产环境应该使用指数退避随机抖动def exponential_backoff(base1, max_wait10): 计算退避时间的生成器 n 0 while True: yield min(base * (2 ** n) random.random(), max_wait) n 1 def smart_request(url, max_retries5): backoff exponential_backoff() for _ in range(max_retries): try: return requests.get(url, timeout(3, 10)) except Exception: sleep(next(backoff)) raise RequestError(fMax retries exceeded for {url})这个算法有两个关键优化等待时间呈指数增长1s, 2s, 4s, 8s...每次增加随机扰动避免多个客户端同步4. 实战构建健壮的请求会话4.1 会话级配置使用Session对象可以统一配置所有请求from requests.adapters import HTTPAdapter session requests.Session() # 为所有请求添加默认超时 session.request functools.partial(session.request, timeout(3, 10)) # 配置重试策略 retry_adapter HTTPAdapter( max_retries3, pool_connections10, pool_maxsize30 ) session.mount(http://, retry_adapter) session.mount(https://, retry_adapter)4.2 异常处理最佳实践不同异常需要区别处理try: response session.get(url) except requests.exceptions.ConnectTimeout: # 处理连接超时 log_error(Connection timeout) except requests.exceptions.ReadTimeout: # 处理读取超时 log_error(Read timeout) except requests.exceptions.SSLError: # 处理SSL错误 log_error(SSL handshake failed) except requests.exceptions.ProxyError: # 处理代理错误 log_error(Proxy error) except requests.exceptions.RequestException as e: # 捕获所有其他请求异常 log_error(fRequest failed: {str(e)})5. 高级技巧与避坑指南5.1 动态超时调整智能爬虫应该根据历史表现动态调整超时class AdaptiveTimeout: def __init__(self, initial5.0): self.value initial def update(self, response_time): # 使用EWMA算法平滑超时值 self.value 0.8 * self.value 0.2 * response_time * 1.5 timeout AdaptiveTimeout() for url in urls: try: start time.time() response session.get(url, timeouttimeout.value) elapsed time.time() - start timeout.update(elapsed) except Timeout: timeout.update(timeout.value * 2) # 超时后增加等待5.2 熔断机制当错误率过高时自动暂停请求from circuitbreaker import circuit circuit(failure_threshold5, recovery_timeout60) def safe_fetch(url): return smart_request(url)5.3 真实世界注意事项遵守robots.txt虽然技术上可以忽略但这是基本的网络礼仪设置合理的User-Agent不要使用默认的python-requests控制请求频率即使有重试机制也不应该狂轰滥炸监控响应状态码遇到429/503应该立即退避分布式环境协调多个爬虫实例需要共享失败状态# 完善的请求头示例 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Accept-Language: en-US,en;q0.9, Referer: https://www.google.com/, Accept-Encoding: gzip, deflate }6. 性能优化实战6.1 连接池调优adapter HTTPAdapter( pool_connections20, # 连接池数量 pool_maxsize100, # 最大连接数 max_retries3, pool_blockTrue # 连接池满时等待 ) session.mount(https://, adapter)6.2 异步IO方案对于高并发场景可以考虑import aiohttp async def fetch(session, url): try: async with session.get(url, timeoutaiohttp.ClientTimeout(total10)) as response: return await response.text() except Exception as e: print(fError fetching {url}: {str(e)}) return None7. 监控与告警完善的爬虫系统需要监控这些指标请求成功率平均响应时间超时比例不同异常类型的分布重试次数统计# Prometheus监控示例 from prometheus_client import Counter, Histogram REQUEST_DURATION Histogram(request_duration_seconds, Request latency) REQUEST_ERRORS Counter(request_errors_total, Error count by type, [error_type]) REQUEST_DURATION.time() def monitored_request(url): try: response session.get(url) return response except Exception as e: REQUEST_ERRORS.labels(type(e).__name__).inc() raise记住没有完美的超时和重试配置只有最适合你特定场景的配置。建议在新爬虫上线前用真实URL列表进行长时间的稳定性测试观察不同参数下的表现逐步调整到最优状态。