fix: 极验重试区分代理死亡和临时波动,减少无效换代理
- 新增 _is_proxy_connection_error 判断异常类型 - 代理连接类错误(ProxyError/超时/502):立即换代理+标记坏 - 临时波动(slide/网络不给力/KeyError):先用原代理重试2次,才换代理 - _refresh_proxy 新增 mark_bad 参数,临时换代理不标记坏 - 解决日志中'网络不给力'反复换代理导致池枯竭的问题
This commit is contained in:
+72
-12
@@ -151,21 +151,55 @@ class DouyuLogin:
|
||||
}
|
||||
self._current_proxy_url = new_proxy
|
||||
|
||||
def _refresh_proxy(self) -> Optional[str]:
|
||||
"""刷新代理IP:先标记当前代理为坏(移出代理池),再获取新代理"""
|
||||
def _refresh_proxy(self, mark_bad: bool = True) -> Optional[str]:
|
||||
"""
|
||||
刷新代理IP。
|
||||
|
||||
Args:
|
||||
mark_bad: 是否标记当前代理为坏。默认True(代理确实不可用时)。
|
||||
设为False时仅换代理,不标记坏(临时网络波动,代理本身可能没问题)。
|
||||
"""
|
||||
if not self.proxy_manager:
|
||||
return None
|
||||
|
||||
# 标记当前代理为不可用,避免再被复用
|
||||
if self._current_proxy_url:
|
||||
if mark_bad and self._current_proxy_url:
|
||||
self.proxy_manager.mark_bad(self._current_proxy_url)
|
||||
|
||||
new_proxy = self.proxy_manager.get_proxy()
|
||||
if new_proxy:
|
||||
self._apply_proxy(new_proxy)
|
||||
logger.info(f"已切换代理: {new_proxy}")
|
||||
logger.info(f"已切换代理: {new_proxy}{' (旧代理已标记坏)' if mark_bad else ' (旧代理保留)'}")
|
||||
return new_proxy
|
||||
|
||||
@staticmethod
|
||||
def _is_proxy_connection_error(err_str: str) -> bool:
|
||||
"""判断异常是否为代理连接类错误(代理已死),而非临时网络波动。"""
|
||||
err_lower = err_str.lower()
|
||||
# 代理连接/超时类:代理本身不可达
|
||||
proxy_dead_keywords = [
|
||||
'proxyerror',
|
||||
'tunnel connection failed',
|
||||
'connecttimeouterror',
|
||||
'connection refused',
|
||||
'unable to connect to proxy',
|
||||
'proxy connection',
|
||||
'502 bad gateway',
|
||||
'503 service unavailable',
|
||||
]
|
||||
if any(kw in err_lower for kw in proxy_dead_keywords):
|
||||
return True
|
||||
# SSL/证书错误也视为代理问题
|
||||
if 'ssl' in err_lower and 'proxy' in err_lower:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _truncate_error(err_str: str, max_len: int = 80) -> str:
|
||||
"""截断错误信息,避免日志刷屏。"""
|
||||
if len(err_str) <= max_len:
|
||||
return err_str
|
||||
return err_str[:max_len] + "..."
|
||||
|
||||
def _safe_url(self, url: str) -> str:
|
||||
"""隐藏查询参数,避免日志泄露登录回调 code。"""
|
||||
parsed = urlsplit(url)
|
||||
@@ -385,6 +419,10 @@ class DouyuLogin:
|
||||
"""
|
||||
解决极验 fullpage 验证(带重试机制)
|
||||
|
||||
区分两类错误:
|
||||
- 临时波动(slide、网络不给力、KeyError):先用原代理重试,连续失败才换
|
||||
- 代理死亡(ProxyError、连接超时):立即换代理
|
||||
|
||||
Args:
|
||||
gt: 极验gt参数
|
||||
challenge: 极验challenge参数(第一次登录返回的)
|
||||
@@ -397,6 +435,10 @@ class DouyuLogin:
|
||||
# max_proxy_retries=0 表示无限重试直到成功
|
||||
max_attempts = self.max_proxy_retries if self.max_proxy_retries > 0 else 999999
|
||||
|
||||
# 连续临时失败计数(同一代理下),超过阈值才换代理
|
||||
_soft_fail_streak = 0
|
||||
_SOFT_FAIL_THRESHOLD = 2 # 同一代理连续临时失败2次才换
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
if self.max_proxy_retries > 0:
|
||||
@@ -435,9 +477,14 @@ class DouyuLogin:
|
||||
logger.success(f"极验 fullpage 验证成功! validate={validate[:20]}...")
|
||||
return validate, seccode
|
||||
else:
|
||||
logger.warning(f"极验验证失败: {message},重试中...")
|
||||
# 刷新代理
|
||||
self._refresh_proxy()
|
||||
# 极验返回失败(slide 等)—— 通常是临时问题,先原代理重试
|
||||
_soft_fail_streak += 1
|
||||
if _soft_fail_streak >= _SOFT_FAIL_THRESHOLD:
|
||||
logger.warning(f"极验验证失败: {message},同一代理连续 {_soft_fail_streak} 次,换代理")
|
||||
self._refresh_proxy(mark_bad=False)
|
||||
_soft_fail_streak = 0
|
||||
else:
|
||||
logger.warning(f"极验验证失败: {message},原代理重试 ({_soft_fail_streak}/{_SOFT_FAIL_THRESHOLD})")
|
||||
time.sleep(1)
|
||||
continue
|
||||
else:
|
||||
@@ -448,10 +495,23 @@ class DouyuLogin:
|
||||
return validate, seccode
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"极验验证异常: {e},重试中...")
|
||||
# 刷新代理
|
||||
self._refresh_proxy()
|
||||
# 增加等待时间,避免请求过于频繁
|
||||
err_str = str(e)
|
||||
is_proxy_dead = self._is_proxy_connection_error(err_str)
|
||||
|
||||
if is_proxy_dead:
|
||||
# 代理确实不可用:立即换,标记坏
|
||||
logger.warning(f"极验验证代理连接失败: {_truncate_error(err_str)},换代理")
|
||||
self._refresh_proxy(mark_bad=True)
|
||||
_soft_fail_streak = 0
|
||||
else:
|
||||
# 临时异常(KeyError、网络不给力等):先原代理重试
|
||||
_soft_fail_streak += 1
|
||||
if _soft_fail_streak >= _SOFT_FAIL_THRESHOLD:
|
||||
logger.warning(f"极验验证临时异常: {_truncate_error(err_str)},连续 {_soft_fail_streak} 次,换代理")
|
||||
self._refresh_proxy(mark_bad=False)
|
||||
_soft_fail_streak = 0
|
||||
else:
|
||||
logger.warning(f"极验验证临时异常: {_truncate_error(err_str)},原代理重试 ({_soft_fail_streak}/{_SOFT_FAIL_THRESHOLD})")
|
||||
time.sleep(2)
|
||||
continue
|
||||
|
||||
|
||||
Reference in New Issue
Block a user