为什么需要异常捕获与重试?
当你用Python写爬虫,通过代理IP去访问网站时,网络世界的不确定性就来了。你可能会遇到各种突发状况:代理服务器突然没响应了、IP被目标网站识别并封禁了、网络延迟导致请求超时……如果代码不做任何处理,爬虫很可能就卡在那里,或者直接崩溃,导致数据抓取中断。异常捕获与重试机制,就像是给爬虫穿上了一层“防弹衣”和“自动修复装置”。它能在遇到问题时,先尝试自己解决(比如换个IP重试几次),而不是立刻“躺平”。这能显著提升爬虫的健壮性和数据采集的成功率,尤其是在大规模、长时间运行的任务中。
构建稳健的代理IP请求核心
一个健壮的代理IP请求模块,不能只靠简单的requests.get()。我们需要从几个核心层面来加固它。
会话管理与超时设置至关重要。使用requests.Session()可以复用TCP连接,提升效率。必须设置合理的timeout参数(包括连接超时和读取超时),防止因某个慢速或失效的代理IP导致整个线程无限期等待。
异常类型的精准识别是重试策略的基础。我们需要捕获的异常通常包括:
连接类异常(如requests.exceptions.ProxyError, ConnectionError),这通常意味着代理服务器本身出了问题;
超时类异常(Timeout),说明代理或目标网站响应太慢;
HTTP错误码(如403、429、503),这往往暗示当前使用的IP可能触发了目标网站的反爬规则。
代理IP的灵活注入。我们的代码应该能方便地接收一个代理IP(例如‘http://12.34.56.78:8080’),并将其应用到请求中。一个高质量的代理IP池是这一切的基础,例如天启代理提供的IP资源,因其高可用率和低延迟,能大幅减少连接类和超时类异常的发生概率,让重试机制更多地去应对目标网站的反爬,而不是在代理本身的稳定性上浪费次数。
实现智能重试策略
重试不是无脑地循环。一个智能的重试策略需要考虑以下几点:
1. 分层重试: 不是所有错误都值得重试。我们可以将错误分为两类:一类是可能通过更换代理IP解决的(如代理错误、因IP被封导致的403/429),另一类是重试意义不大的(如目标页面不存在返回的404,或请求参数错误导致的400)。我们的重试逻辑应聚焦于第一类错误。
2. 指数退避: 在连续失败后,不要立即重试,而是等待一段时间,并且等待时间随失败次数指数级增加(例如等待1秒、2秒、4秒…)。这既能减轻对目标网站的压力,也符合许多网站反爬策略的“冷却”预期。
3. 代理IP切换: 这是爬虫代理IP应用中的核心重试手段。一旦判断异常可能与当前IP有关,重试前必须从IP池中获取一个新IP。天启代理的API接口请求时间小于1秒,且支持高并发调用,这使得在重试过程中快速、大批量地更换IP成为可能,极大地提高了突破反爬的效率。
4. 重试上限: 必须为单个请求设置一个重试上限(比如3-5次),防止因一个不可能成功的请求陷入死循环。
Python最佳实践代码示例
下面是一个融合了上述思想的Python代码示例。它使用了requests库和tenacity库(一个优秀的重试库)来构建一个带有异常捕获、代理切换和指数退避的健壮请求函数。
import requests
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type, before_retry
import logging
假设这是从天启代理API获取一个IP的函数
def get_proxy_from_tianqi():
这里调用天启代理API,返回格式如 ‘http://ip:port’
天启代理API响应快(<1秒),IP纯净度高,适合频繁更换
实际代码中需处理API请求和响应解析
pass
class ProxyRotator:
def __init__(self):
self.current_proxy = None
def get_proxy(self):
"""获取一个新代理,内部可加入IP有效性校验逻辑"""
self.current_proxy = get_proxy_from_tianqi()
return self.current_proxy
设置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
proxy_rotator = ProxyRotator()
定义需要重试的异常类型
RETRY_EXCEPTIONS = (requests.exceptions.ProxyError,
requests.exceptions.ConnectionError,
requests.exceptions.Timeout)
@retry(
stop=stop_after_attempt(4), 最多重试4次(即首次请求+3次重试)
wait=wait_exponential(multiplier=1, min=2, max=10), 指数退避,等待2,4,8,10秒
retry=retry_if_exception_type(RETRY_EXCEPTIONS), 仅对指定异常重试
before_retry=lambda retry_state: logger.warning(f”请求失败,准备第{retry_state.attempt_number}次重试,异常:{retry_state.outcome.exception()}”),
reraise=True 重试耗尽后抛出原异常
)
def robust_request_with_proxy(url, headers=None):
"""
使用代理进行稳健的HTTP请求
"""
proxies = {
‘http’: proxy_rotator.get_proxy(),
‘https’: proxy_rotator.get_proxy()
}
session = requests.Session()
设置一个合理的总超时时间
timeout_config = (5, 15) (连接超时, 读取超时)
try:
response = session.get(url, headers=headers, proxies=proxies, timeout=timeout_config)
response.raise_for_status() 如果状态码不是200,抛出HTTPError
这里可以加入针对特定HTTP状态码的处理,例如遇到429时主动触发重试(更换IP)
if response.status_code == 429:
logger.error(“触发频率限制,主动抛出异常以触发重试(更换代理)”)
主动抛出一个会被重试的异常,促使tenacity进行重试(并更换代理)
raise requests.exceptions.ConnectionError(“Rate limited”)
return response
except requests.exceptions.HTTPError as http_err:
对于明确的客户端错误(如404),不进行重试,直接记录并抛出
if http_err.response.status_code in [400, 404]:
logger.error(f”客户端错误,无需重试: {http_err}”)
raise
对于其他HTTP错误(如403, 503),可以认为是代理IP或临时问题,触发重试
else:
logger.warning(f”HTTP错误码 {http_err.response.status_code},触发重试”)
raise requests.exceptions.ProxyError(f”HTTP {http_err.response.status_code}”) from http_err
使用示例
if __name__ == “__main__”:
try:
resp = robust_request_with_proxy(‘https://httpbin.org/ip’)
print(“成功获取响应:”, resp.text)
except Exception as e:
logger.error(f”所有重试尝试均失败: {e}”)
这段代码的核心在于@retry装饰器和robust_request_with_proxy函数。装饰器定义了重试的策略(次数、等待、触发条件)。在请求函数内部,每次重试都会通过proxy_rotator.get_proxy()获取一个新的天启代理IP,从而确保每次尝试都是一个新的网络身份。对于HTTP状态码,我们做了精细化处理:400/404等错误直接失败,而403、429、503等则通过转换为ProxyError来触发重试和更换IP。
常见问题与解答(QA)
Q1:我捕获了异常也重试了,但爬虫还是很快被屏蔽,怎么办?
A1:异常重试是“补救”措施。要避免被屏蔽,关键在于预防。除了更换IP,你还需要:1) 在请求头中模拟真实浏览器;2) 控制访问频率,在请求间加入随机延迟;3) 使用像天启代理这样的高质量IP池,其自建机房的纯净IP被识别为代理的风险相对较低,高可用率也能保证你获取的IP大部分是有效的,从源头减少异常。
Q2:代码中为什么推荐使用tenacity库而不是自己写循环?
A2:tenacity库提供了声明式的、功能强大的重试逻辑,支持复杂的等待策略、异常过滤、重试前后的回调等。自己写循环容易遗漏细节(如异常类型嵌套、退避逻辑),代码也不够清晰。使用成熟的库可以让开发者更专注于业务逻辑。
Q3:如何判断一个代理IP是否已经失效或被封?
A3:在将IP加入有效池之前,可以进行一次有效性校验:用该IP访问一个已知稳定且速度快的网站(如https://httpbin.org/ip),检查响应状态码、响应时间以及返回内容是否包含该代理IP。天启代理提供的IP可用率高达99%以上,响应延迟低,这本身就极大降低了IP失效的概率,你可以在获取IP后做一次轻量级校验,或者信任其服务质量直接使用,将校验重点放在针对目标网站的反爬检测上。
Q4:我的爬虫需要高并发,这种重试模式会影响速度吗?
A4:重试和退避确实会引入延迟,但这是为了整体成功率的必要权衡。在高并发场景下,你可以结合异步IO(如aiohttp+asyncio)来构建爬虫。即使某个请求因重试而等待,其他并发的请求仍然可以继续执行。天启代理支持高并发调用API,其分布式集群架构也能支撑你同时使用大量IP进行异步采集,确保你的并发能力不会受限于代理服务本身。


