From 2ccc2a933685eb896dffeda4d101be960d4fd78c Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 23 Jun 2026 01:41:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B9=B6=E5=8F=91=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E5=88=86=E9=85=8D=E5=88=B0=E5=90=8C=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:get_proxy每次返回池中第一个代理,导致并发账号用同一个代理, 容易触发斗鱼/极验风控。 修复: - 增加_in_use集合,get_proxy取代理时标记为使用中,避免重复分配 - 代理池空但所有代理都在使用中时,允许复用(兜底) - 新增release_proxy方法,登录完成后归还代理到池 - login.py的finally块中调用release_proxy,确保成功/失败都归还 - mark_bad同时从_in_use移除 --- core/douyu/login.py | 4 ++++ core/douyu/proxy.py | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/core/douyu/login.py b/core/douyu/login.py index c9e548a..700fb73 100644 --- a/core/douyu/login.py +++ b/core/douyu/login.py @@ -313,6 +313,10 @@ class DouyuLogin: except Exception as e: logger.error(f"登录失败: {e}") return LoginResult(success=False, message=str(e)) + finally: + # 归还代理到池,让其他账号可以复用 + if self.proxy_manager and self._current_proxy_url: + self.proxy_manager.release_proxy(self._current_proxy_url) def _first_login(self) -> Tuple[str, str, str, dict]: """ diff --git a/core/douyu/proxy.py b/core/douyu/proxy.py index e290331..40bdd69 100644 --- a/core/douyu/proxy.py +++ b/core/douyu/proxy.py @@ -20,17 +20,26 @@ class ProxyManager: self._cond = threading.Condition() # 已验证可用的代理池: {proxy_url: validated_timestamp} self._verified_pool: dict[str, float] = {} + # 正在使用中的代理(取走但未归还),避免并发账号用同一个代理 + self._in_use: set[str] = set() self._pool_ttl = 90 # 代理验证后90秒内可复用 self._fetching = False # 是否有线程正在获取代理 def _pick_from_pool_locked(self) -> Optional[str]: - """从池中取一个未过期的代理(调用前需持有锁)""" + """从池中取一个未过期且未在使用的代理(调用前需持有锁)""" now = time.time() # 清理过期代理 expired = [p for p, t in self._verified_pool.items() if now - t > self._pool_ttl] for p in expired: del self._verified_pool[p] - # 取第一个可用的 + self._in_use.discard(p) + # 取一个不在使用中的 + for proxy in self._verified_pool: + if proxy not in self._in_use: + self._in_use.add(proxy) + self.current_proxy = proxy + return proxy + # 所有代理都在使用中,但池非空(并发数>代理数),允许复用第一个 for proxy in self._verified_pool: self.current_proxy = proxy return proxy @@ -151,12 +160,19 @@ class ProxyManager: """标记代理为不可用,从池中移除(极验失败/代理连接失败时调用)""" with self._cond: removed = self._verified_pool.pop(proxy_url, None) + self._in_use.discard(proxy_url) if self.current_proxy == proxy_url: self.current_proxy = None if removed: logger.info(f"代理标记为不可用并移出池: {proxy_url} (池剩余 {len(self._verified_pool)})") self._cond.notify_all() + def release_proxy(self, proxy_url: str) -> None: + """归还代理到池(登录完成后调用,让其他账号可以复用)""" + with self._cond: + self._in_use.discard(proxy_url) + self._cond.notify_all() + def get_proxies_dict(self, proxy: str = None) -> dict: """获取requests使用的proxies字典""" proxy = proxy or self.current_proxy