网络请求天然不可靠,一个生产级爬虫必须能优雅地处理超时、SSL 错误和请求失败。本文系统讲解 timeout 参数用法与 retrying 自动重试装饰器的最佳实践。
目录
为什么超时控制不可或缺
timeout 参数详解
verify 参数:跳过 SSL 验证
用 retrying 实现自动重试
retrying 核心参数速查
组合使用:超时 + 重试 + 代理池联动

①为什么超时控制不可或缺
requests 默认没有超时限制——如果目标服务器不响应,程序会永远挂起等待。这在批量爬取时是灾难性的:一个卡死的请求会阻塞整个队列,代理池里的失效节点也无法被及时剔除。
发出请求→服务器无响应→无超时:永久阻塞vs有超时:抛出异常 → 继续下一个
设置合理的超时是爬虫从"能跑"到"稳定跑"的关键一步,也是代理池健康管理的前提。
②timeout 参数详解
requests 的 timeout 参数支持两种写法:传单个数值或传元组,语义不同。
# 写法一:单值,同时限制连接超时和读取超时 resp = requests.get(url, timeout=3) # 写法二:元组 (connect_timeout, read_timeout),分别控制 # 3 秒内未建立连接就放弃;建立连接后 10 秒内没有新数据也放弃 resp = requests.get(url, timeout=(3, 10))
写法 | 含义 | 适用场景 |
timeout=3 | 连接 + 读取总共不超过 3 秒 | 快速 API、轻量页面 |
timeout=(3, 10) | 连接 3 秒,读取 10 秒 | 大文件下载、响应体较大的页面 |
timeout=None | 无限等待(默认值) | 几乎不应使用 |
import requestsdef fetch(url): headers = {"User-Agent": "Mozilla/5.0 ..."} data = {"uname": "admin", "passwd": "admin"} try: resp = requests.get( url, headers=headers, data=data, verify=False, # 跳过 SSL 证书验证(见下节) timeout=3 # 3 秒无响应则放弃 ) return resp.content.decode("utf-8") except requests.exceptions.Timeout: print("超时,跳过此请求") except requests.exceptions.RequestException as e: print(f"请求异常:{e}") return Noneif __name__ == "__main__": print(fetch("http://example.com/page"))
③verify 参数:跳过 SSL 验证
访问 HTTPS 网站时,requests 默认验证 SSL 证书。某些网站使用自签名证书或证书已过期,会导致 SSLError。传入 verify=False 可临时跳过这一检查。
import requests import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) resp = requests.get("https://self-signed.example.com", verify=False, timeout=5)
④用 retrying 实现自动重试
网络抖动、服务器偶发 5xx、代理临时失效——这些故障往往是暂时的,重试一次就能成功。手写重试逻辑繁琐,retrying 库提供了装饰器方案,一行注解搞定。
import requestsfrom retrying import retry# 失败后最多重试 3 次(含首次),仍失败则向上抛出异常@retry(stop_max_attempt_number=3)def fetch_page(url): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90" } # 没有 try/except:抛出异常才会触发 retrying 的重试机制 res = requests.get(url=url, headers=headers, timeout=5) res.raise_for_status() # 4xx/5xx 状态码同样触发重试 with open("page.html", "wb") as f: f.write(res.content)def main(): try: fetch_page("http://example.com/list/page-1/") except Exception as e: print(f"3 次重试均失败:{e}") # 记录日志或标记任务失败if __name__ == "__main__": main()
⑤retrying 核心参数速查
参数 | 含义 | 示例 |
stop_max_attempt_number | 最大尝试总次数(含首次) | =3 |
stop_max_delay | 从首次调用起最长等待毫秒数 | =10000(10 秒) |
wait_fixed | 每次重试前固定等待毫秒 | =2000(等 2 秒再重试) |
wait_random_min / max | 随机等待区间(毫秒),模拟人类行为 | wait_random_min=1000, wait_random_max=3000 |
retry_on_exception | 传入函数,决定哪些异常触发重试 | =lambda e: isinstance(e, Timeout) |
⑥组合使用:超时 + 重试 + 代理池联动
三者结合是构建稳健爬虫的标准模式:超时快速识别失效代理,重试自动切换并补偿,代理池及时淘汰失效节点。
import requestsfrom retrying import retryPROXY_POOL = [ "http://1.2.3.4:8888", "http://5.6.7.8:9999",]@retry(stop_max_attempt_number=3, wait_fixed=1000)def fetch_with_proxy(url, proxy): proxies = {"http": proxy, "https": proxy} resp = requests.get(url, proxies=proxies, timeout=5) resp.raise_for_status() return resp.contentdef main(url): for proxy in PROXY_POOL: try: content = fetch_with_proxy(url, proxy) print(f"成功,代理:{proxy}") return content except Exception: print(f"代理 {proxy} 失效,从池中移除") PROXY_POOL.remove(proxy) # 超时/失败则剔除代理 print("代理池已耗尽")if __name__ == "__main__": main("http://example.com/data")
✓小结
超时设置 timeout=(3, 10)
SSL问题 verify=False
自动重试 @retry(次数=3)
代理联动 失效即剔除
超时是爬虫稳定性的最基础保障,重试是应对偶发性网络故障的标准手段,两者结合代理池的动态管理,共同构成生产级爬虫的容错体系。