继续优化登录逻辑

This commit is contained in:
yml2213
2026-06-25 08:40:00 +08:00
parent a62e7c192f
commit 0b4fe1daa2
6 changed files with 105 additions and 48 deletions
+54 -42
View File
@@ -66,6 +66,7 @@ class DouyuLogin:
"state=https%3A%2F%2Fwww.douyu.com%2Fdirectory"
)
REQUEST_TIMEOUT = (10, 30)
MAX_STATIC_RETRY = 3 # 静态代理失败上限(无法切换代理时的最大重试次数)
def __init__(
self,
@@ -73,7 +74,6 @@ class DouyuLogin:
proxy: Optional[str | Mapping[str, str]] = None,
proxy_api_url: Optional[str] = None,
timeout: tuple[float, float] = REQUEST_TIMEOUT,
max_proxy_retries: int = 0,
max_login_retries: int = 0,
max_total_time: float = 0,
proxy_fetcher: Optional[ProxyFetcher] = None,
@@ -82,7 +82,6 @@ class DouyuLogin:
self.account = account
self.proxy = proxy
self.timeout = timeout
self.max_proxy_retries = max_proxy_retries # 0=无限切换直到成功
self.max_login_retries = max_login_retries # 0=无限重试直到成功
self.max_total_time = max_total_time # 0=不限制单账号登录总时长
self.stop_event = stop_event
@@ -94,6 +93,7 @@ class DouyuLogin:
self._current_proxy_url: Optional[str] = None
self._cookie_enrich_error = ""
self._static_retry_count = 0
self._setup_session()
def _is_stopped(self) -> bool:
@@ -183,50 +183,41 @@ class DouyuLogin:
parsed = urlsplit(url)
return urlunsplit((parsed.scheme, parsed.netloc, parsed.path, "", ""))
def _request(self, method: str, url: str, max_retries: int = 2, **kwargs) -> requests.Response:
def _request(self, method: str, url: str, **kwargs) -> requests.Response:
"""
统一发送请求,附带分段超时和更明确的错误信息。
代理连接失败时直接抛异常,由 login 整体重试换新代理。
代理失败/超时直接抛异常,由 login 整体重试换新代理(短效代理失效后重试同一个无意义)
"""
timeout = kwargs.pop('timeout', self.timeout)
safe_url = self._safe_url(url)
for attempt in range(max_retries):
self._ensure_not_stopped()
started = time.monotonic()
self._ensure_not_stopped()
started = time.monotonic()
try:
response = self.session.request(method, url, timeout=timeout, **kwargs)
elapsed = time.monotonic() - started
logger.debug(
f"{method.upper()} {safe_url} -> {response.status_code} "
f"({elapsed:.2f}s)"
)
return response
except requests.Timeout as exc:
elapsed = time.monotonic() - started
raise TimeoutError(
f"{method.upper()} {safe_url} 超时,耗时 {elapsed:.1f}s"
f"timeout={timeout}"
) from exc
except requests.ConnectionError as exc:
elapsed = time.monotonic() - started
err_str = str(exc)
is_proxy_err = "proxy" in err_str.lower() or "Proxy" in type(exc).__name__
if is_proxy_err and attempt < max_retries - 1:
logger.warning(f"代理连接失败,尝试 {attempt + 1}/{max_retries}: {self._truncate_error(err_str)}")
self._sleep_interruptible(1)
continue
raise ConnectionError(
f"{method.upper()} {safe_url} 连接失败: {self._truncate_error(err_str)}"
) from exc
except requests.RequestException as exc:
elapsed = time.monotonic() - started
raise ConnectionError(
f"{method.upper()} {safe_url} 请求失败,耗时 {elapsed:.1f}s: {exc}"
) from exc
raise ConnectionError(f"{method.upper()} {safe_url} 连接失败,已重试 {max_retries}")
try:
response = self.session.request(method, url, timeout=timeout, **kwargs)
elapsed = time.monotonic() - started
logger.debug(
f"{method.upper()} {safe_url} -> {response.status_code} "
f"({elapsed:.2f}s)"
)
return response
except requests.Timeout as exc:
elapsed = time.monotonic() - started
raise TimeoutError(
f"{method.upper()} {safe_url} 超时,耗时 {elapsed:.1f}s"
f"timeout={timeout}"
) from exc
except requests.ConnectionError as exc:
err_str = str(exc)
raise ConnectionError(
f"{method.upper()} {safe_url} 连接失败: {self._truncate_error(err_str)}"
) from exc
except requests.RequestException as exc:
elapsed = time.monotonic() - started
raise ConnectionError(
f"{method.upper()} {safe_url} 请求失败,耗时 {elapsed:.1f}s: {exc}"
) from exc
def _request_json(self, method: str, url: str, source: str, **kwargs) -> dict:
"""请求 JSON 接口,并在响应异常时输出可定位的信息。"""
@@ -274,7 +265,11 @@ class DouyuLogin:
else:
logger.info(f"登录整体重试 {attempt} (无限重试),换新代理从头开始")
# 重试前:取新代理 + 重置 session
self._prepare_retry()
if not self._prepare_retry():
return LoginResult(
success=False,
message=f"静态代理连续失败 {self.MAX_STATIC_RETRY} 次,无法切换代理",
)
try:
# 1️⃣ 第一次登录(获取极验参数)
@@ -330,14 +325,31 @@ class DouyuLogin:
# 所有重试耗尽或超时
return LoginResult(success=False, message=str(e))
def _prepare_retry(self) -> None:
"""重试前准备:取新代理、重置 session cookies。"""
def _prepare_retry(self) -> bool:
"""重试前准备:取新代理、重置 session cookies。
Returns:
True - 可以继续重试
False - 静态代理已连续失败 MAX_STATIC_RETRY 次,无法切换代理,应放弃
"""
self.session.cookies.clear()
if self.proxy_fetcher:
new_proxy = self.proxy_fetcher.fetch_new_proxy()
if new_proxy:
self._apply_proxy(new_proxy)
logger.info(f"重试换新代理: {new_proxy}")
return True
if self.proxy:
self._static_retry_count += 1
if self._static_retry_count > self.MAX_STATIC_RETRY:
logger.error(
f"静态代理连续失败 {self.MAX_STATIC_RETRY} 次,无法切换代理"
)
return False
logger.warning(
f"静态代理失败重试 {self._static_retry_count}/{self.MAX_STATIC_RETRY}"
)
return True
def _first_login(self) -> Tuple[str, str, str, dict]:
"""