简化登录流程:砍掉代理池,极验不内部切代理
- 新建 ProxyFetcher 替代 ProxyManager,每次从API取1个代理,无池无锁无冷却 - 极验最多3次尝试,失败直接抛异常回到login整体重试取新代理 - 删掉极验内部的 refresh_proxy/proxy_switches/_soft_fail_streak 逻辑 - login整体重试时取新代理从头走完整流程 - 代理验证超时从(5,8)缩短为(3,5),白名单同步后立即重试不等待 - login_service 删掉60行代理池预检/等待代码 - 修复邮件验证码日期比较:Roundcube分钟精度与秒级时间戳对齐
This commit is contained in:
@@ -12,16 +12,13 @@ from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.douyu import DouyuLogin
|
||||
from core.douyu.proxy import resolve_working_proxy, get_proxy_manager
|
||||
from core.douyu.proxy_fetcher import ProxyFetcher
|
||||
from ..models import Account as AccountModel, LoginTask, ProxyConfig as ProxyConfigModel
|
||||
|
||||
|
||||
class LoginBatchRunner:
|
||||
"""批量登录执行器,在线程中运行,通过 ThreadPoolExecutor 并发登录多个账号。"""
|
||||
|
||||
# 代理池耗尽时等待恢复的最大秒数
|
||||
_PROXY_WAIT_MAX = 60
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
db: Session,
|
||||
@@ -52,21 +49,21 @@ class LoginBatchRunner:
|
||||
self._counter_lock = threading.Lock()
|
||||
self._completed = 0
|
||||
|
||||
# 共享代理管理器(带锁,避免并发白名单限流;极验失败时可刷新代理)
|
||||
self._shared_proxy_manager = None
|
||||
# 共享代理获取器(无池,每次取新代理)
|
||||
self._shared_proxy_fetcher = None
|
||||
if proxy_config and proxy_config.enabled and proxy_config.api_url:
|
||||
wl_platform = "xiequ"
|
||||
wl_credentials = None
|
||||
if proxy_config.whitelist_enabled:
|
||||
wl_platform = getattr(proxy_config, 'whitelist_platform', None) or "xiequ"
|
||||
wl_credentials = getattr(proxy_config, 'whitelist_credentials', None)
|
||||
# 向后兼容:旧字段有值但新字段为空
|
||||
# 向后兼容
|
||||
if not wl_credentials and proxy_config.whitelist_uid and proxy_config.whitelist_ukey:
|
||||
wl_platform = "xiequ"
|
||||
wl_credentials = {"uid": proxy_config.whitelist_uid, "ukey": proxy_config.whitelist_ukey}
|
||||
|
||||
self._shared_proxy_manager = get_proxy_manager(
|
||||
proxy_config.api_url,
|
||||
self._shared_proxy_fetcher = ProxyFetcher(
|
||||
api_url=proxy_config.api_url,
|
||||
whitelist_platform=wl_platform,
|
||||
whitelist_credentials=wl_credentials,
|
||||
)
|
||||
@@ -91,13 +88,7 @@ class LoginBatchRunner:
|
||||
)
|
||||
|
||||
def _resolve_static_proxy(self) -> tuple[Optional[dict], str]:
|
||||
"""
|
||||
解析静态代理配置(仅处理无代理和静态代理场景)。
|
||||
|
||||
API代理由 DouyuLogin 通过 proxy_manager 内部管理,
|
||||
不在此处预先获取——登录过程中的代理切换(极验失败、整体重试)
|
||||
都在 DouyuLogin 内部自治完成。
|
||||
"""
|
||||
"""解析静态代理配置。"""
|
||||
if not self.proxy_config or not self.proxy_config.enabled:
|
||||
return None, ''
|
||||
|
||||
@@ -106,11 +97,11 @@ class LoginBatchRunner:
|
||||
proxy_url = self.proxy_config.http or self.proxy_config.https
|
||||
return {'http': proxy_url, 'https': proxy_url}, f'使用静态代理: {proxy_url}'
|
||||
|
||||
# API代理:不在此处获取,由 DouyuLogin 通过 proxy_manager 内部管理
|
||||
# API代理:由 DouyuLogin 通过 proxy_fetcher 内部管理
|
||||
return None, ''
|
||||
|
||||
def _execute_one(self, task_id: int, acc_info: dict, total: int):
|
||||
"""在独立线程中执行单个账号登录,使用独立的 DB 会话和代理。"""
|
||||
"""在独立线程中执行单个账号登录,使用独立的 DB 会话。"""
|
||||
if self._stop.is_set():
|
||||
self._push_log("warning", f"任务已停止,跳过: {acc_info['username']}")
|
||||
return
|
||||
@@ -135,56 +126,8 @@ class LoginBatchRunner:
|
||||
if proxy_msg:
|
||||
self._push_log("info", f"[{current}] {proxy_msg}")
|
||||
|
||||
# API代理模式下,验证代理池是否有可用代理;池空时等待恢复(最多 _PROXY_WAIT_MAX 秒)
|
||||
# 注意:不预取代理传给 DouyuLogin,让 DouyuLogin 通过 proxy_manager 内部自治管理
|
||||
is_api_proxy = (
|
||||
self.proxy_config
|
||||
and self.proxy_config.enabled
|
||||
and not (self.proxy_config.http or self.proxy_config.https)
|
||||
and self._shared_proxy_manager
|
||||
)
|
||||
if is_api_proxy:
|
||||
# 先验证代理池是否有可用代理(获取后立即归还,不占用)
|
||||
test_proxy = self._shared_proxy_manager.get_proxy()
|
||||
if test_proxy:
|
||||
self._shared_proxy_manager.release_proxy(test_proxy)
|
||||
self._push_log("info", f"[{current}] 代理池可用,由 DouyuLogin 内部管理代理获取与切换")
|
||||
else:
|
||||
# 代理池暂时耗尽,等待冷却代理恢复或新代理入池
|
||||
self._push_log("warning", f"[{current}] 代理池暂时耗尽,等待恢复...")
|
||||
pool_available = False
|
||||
for wait_sec in range(0, self._PROXY_WAIT_MAX, 10):
|
||||
if self._stop.is_set():
|
||||
task.status = "error"
|
||||
task.message = "任务已停止"
|
||||
task.finished_at = datetime.now(timezone.utc)
|
||||
worker_db.commit()
|
||||
return
|
||||
if self._sleep_or_stop(10):
|
||||
task.status = "error"
|
||||
task.message = "任务已停止"
|
||||
task.finished_at = datetime.now(timezone.utc)
|
||||
worker_db.commit()
|
||||
return
|
||||
test_proxy = self._shared_proxy_manager.get_proxy()
|
||||
if test_proxy:
|
||||
self._shared_proxy_manager.release_proxy(test_proxy)
|
||||
pool_available = True
|
||||
self._push_log("info", f"[{current}] 代理池恢复,由 DouyuLogin 内部管理代理")
|
||||
break
|
||||
remaining = self._PROXY_WAIT_MAX - wait_sec - 10
|
||||
self._push_log("info", f"[{current}] 代理池仍为空,继续等待... (剩余 {remaining}s)")
|
||||
|
||||
if not pool_available:
|
||||
task.status = "error"
|
||||
task.message = f"代理池耗尽,等待 {self._PROXY_WAIT_MAX}s 后仍无可用代理"
|
||||
task.finished_at = datetime.now(timezone.utc)
|
||||
worker_db.commit()
|
||||
self._push_log("error", f"[{current}] {acc_info['username']} 代理池耗尽,放弃")
|
||||
return
|
||||
|
||||
# 静态代理启用但配置为空 → 不可用
|
||||
if self.proxy_config and self.proxy_config.enabled and not (self.proxy_config.http or self.proxy_config.https) and not self._shared_proxy_manager and not proxy_dict:
|
||||
if self.proxy_config and self.proxy_config.enabled and not (self.proxy_config.http or self.proxy_config.https) and not self._shared_proxy_fetcher and not proxy_dict:
|
||||
task.status = "error"
|
||||
task.message = "代理不可用: 未配置代理"
|
||||
task.finished_at = datetime.now(timezone.utc)
|
||||
@@ -209,7 +152,7 @@ class LoginBatchRunner:
|
||||
max_proxy_retries=self.max_proxy_retries,
|
||||
max_login_retries=self.max_login_retries,
|
||||
max_total_time=self.max_total_time,
|
||||
proxy_manager=self._shared_proxy_manager,
|
||||
proxy_fetcher=self._shared_proxy_fetcher,
|
||||
stop_event=self._stop,
|
||||
)
|
||||
result = loginer.login()
|
||||
@@ -333,24 +276,20 @@ class BatchRegistry:
|
||||
|
||||
def __init__(self):
|
||||
self._batches: dict[str, dict] = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def register(self, batch_id: str, log_queue: asyncio.Queue,
|
||||
loop: asyncio.AbstractEventLoop, runner: LoginBatchRunner):
|
||||
with self._lock:
|
||||
self._batches[batch_id] = {
|
||||
"log_queue": log_queue,
|
||||
"loop": loop,
|
||||
"runner": runner,
|
||||
}
|
||||
self._batches[batch_id] = {
|
||||
"log_queue": log_queue,
|
||||
"loop": loop,
|
||||
"runner": runner,
|
||||
}
|
||||
|
||||
def get(self, batch_id: str):
|
||||
with self._lock:
|
||||
return self._batches.get(batch_id)
|
||||
return self._batches.get(batch_id)
|
||||
|
||||
def pop(self, batch_id: str):
|
||||
with self._lock:
|
||||
return self._batches.pop(batch_id, None)
|
||||
return self._batches.pop(batch_id, None)
|
||||
|
||||
|
||||
# 模块级单例
|
||||
|
||||
Reference in New Issue
Block a user