应使用线程安全的queue.queue构建代理池,仅存入已验证代理并记录使用次数与最后成功时间;捕获proxyerror等异常后立即剔除失效代理,配合超时设置与定期清理机制实现稳定调度。

为什么直接轮询代理列表会频繁触发 requests.exceptions.ProxyError
很多新手把代理IP存进列表,用 random.choice() 拿一个就发请求,结果几分钟后全挂——不是超时就是被目标站封端口。根本原因在于:代理本身是临时、不稳定、带地域/频次限制的资源,而你的代码没做任何健康检查或状态反馈闭环。没有响应时间阈值控制、没有失败计数、没有自动剔除机制,等于拿裸IP硬撞墙。
用 queue.Queue 实现线程安全的可用代理池
别用 list + lock 手动维护,容易漏锁或死锁。用线程安全的 queue.Queue 作为代理“缓冲区”,只往里放已验证过的 IP,并在每次取用后标记使用次数和最后成功时间:
from queue import Queue
import time
<p>class ProxyPool:
def <strong>init</strong>(self):
self._pool = Queue()</p><h1>{proxy_str: {'used': 0, 'last_ok': 0}}</h1><pre class="brush:python;toolbar:false;"> self._stats = {}
def put(self, proxy):
if proxy not in self._stats:
self._stats[proxy] = {"used": 0, "last_ok": 0}
self._pool.put(proxy)
def get(self):
try:
proxy = self._pool.get_nowait()
self._stats[proxy]["used"] += 1
return proxy
except:
return None
注意:get() 不做重试,只负责“出队”,验证和重入由调用方控制;put() 也不重复添加,靠 _stats 字典去重。
失效代理必须在 requests 异常后立即踢出,不能等下一轮
常见错误是捕获到 ProxyError 或 ConnectTimeout 后只打日志,然后继续用同一个代理重试——这只会让后续请求持续失败。正确做法是:在异常分支里立刻从 _stats 删除该代理,并跳过本次重试逻辑:
- 关注三类关键异常:
requests.exceptions.ProxyError、requests.exceptions.ConnectTimeout、requests.exceptions.ReadTimeout - 超时建议设为
timeout=(3, 7):3 秒连代理,7 秒等响应,避免卡死 - 单个代理连续失败 2 次,或 5 分钟内无成功记录,直接从
_stats和队列中清除(需额外线程定期清理)
代理更新不能靠定时任务“全量刷新”,得按需预热 + 延迟加载
爬虫高峰期突然换掉全部代理,会导致大量请求瞬间失败。更稳的方式是:维持一个最小可用量(比如 5 个),当池中剩余 put() 进队列。不要阻塞主爬取流程。
验证逻辑要模拟真实请求头、带 Referer、User-Agent,有些代理商(如芝麻、快代理)会拦截无头请求。另外,避免用 HEAD 验证——部分代理对 HEAD 返回 200 但实际不支持 GET。
真正难的不是加代理,而是让每个代理在失效前被识别出来;而最常被忽略的,是代理响应体内容是否符合预期(比如返回了验证码 HTML 却没报错)。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











