- 新建 ProxyFetcher 替代 ProxyManager,每次从API取1个代理,无池无锁无冷却 - 极验最多3次尝试,失败直接抛异常回到login整体重试取新代理 - 删掉极验内部的 refresh_proxy/proxy_switches/_soft_fail_streak 逻辑 - login整体重试时取新代理从头走完整流程 - 代理验证超时从(5,8)缩短为(3,5),白名单同步后立即重试不等待 - login_service 删掉60行代理池预检/等待代码 - 修复邮件验证码日期比较:Roundcube分钟精度与秒级时间戳对齐
103 lines
3.5 KiB
Python
103 lines
3.5 KiB
Python
"""代理可用性验证。"""
|
|
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
from typing import Optional
|
|
|
|
import requests
|
|
from loguru import logger
|
|
|
|
|
|
def verify_proxy_url(proxy_url: str, timeout: tuple = (3, 5)) -> tuple[bool, str]:
|
|
"""
|
|
验证代理是否可用,只验证斗鱼主站可达。
|
|
|
|
超时缩短为 (3, 5) 以减少短效代理在验证期间过期浪费。
|
|
|
|
Returns:
|
|
(是否可用, 消息)
|
|
"""
|
|
proxies = {'http': proxy_url, 'https': proxy_url}
|
|
|
|
# ── 斗鱼主站可达性验证 ──
|
|
try:
|
|
response = requests.get(
|
|
'https://www.douyu.com',
|
|
proxies=proxies,
|
|
timeout=timeout,
|
|
headers={'User-Agent': 'Mozilla/5.0'},
|
|
)
|
|
response.raise_for_status()
|
|
return True, '代理可用 → 斗鱼可达'
|
|
except Exception as exc:
|
|
err_msg = str(exc)
|
|
if 'Tunnel connection failed' in err_msg or '503' in err_msg:
|
|
detail = '代理拒绝连接(白名单可能未生效)'
|
|
elif 'timed out' in err_msg.lower():
|
|
detail = '连接超时'
|
|
else:
|
|
detail = type(exc).__name__
|
|
logger.debug(f"代理验证失败 [{proxy_url}]: 斗鱼主站不可达: {detail}")
|
|
return False, detail
|
|
|
|
|
|
def verify_proxies_concurrent(
|
|
proxy_urls: list[str],
|
|
timeout: tuple = (5, 8),
|
|
max_workers: int = 5,
|
|
return_all: bool = False,
|
|
) -> tuple[Optional[str | list[str]], str]:
|
|
"""
|
|
并发验证多个代理 URL。
|
|
|
|
Returns:
|
|
return_all=False: (可用代理 URL 或 None, 消息)
|
|
return_all=True: (可用代理 URL 列表或 None, 消息)
|
|
"""
|
|
if not proxy_urls:
|
|
return None, '无代理可验证'
|
|
|
|
if len(proxy_urls) == 1:
|
|
ok, msg = verify_proxy_url(proxy_urls[0], timeout)
|
|
if ok:
|
|
return (proxy_urls if return_all else proxy_urls[0]), msg
|
|
return None, msg
|
|
|
|
if return_all:
|
|
available: list[str] = []
|
|
with ThreadPoolExecutor(max_workers=min(max_workers, len(proxy_urls))) as executor:
|
|
future_map = {
|
|
executor.submit(verify_proxy_url, proxy, timeout): proxy
|
|
for proxy in proxy_urls
|
|
}
|
|
for future in as_completed(future_map):
|
|
try:
|
|
ok, _ = future.result()
|
|
if ok:
|
|
available.append(future_map[future])
|
|
except Exception:
|
|
continue
|
|
if available:
|
|
logger.success(f"并发验证找到 {len(available)}/{len(proxy_urls)} 个可用代理")
|
|
return available, f'找到 {len(available)} 个可用代理'
|
|
return None, f'共 {len(proxy_urls)} 个代理均不可用'
|
|
|
|
with ThreadPoolExecutor(max_workers=min(max_workers, len(proxy_urls))) as executor:
|
|
future_map = {
|
|
executor.submit(verify_proxy_url, proxy, timeout): proxy
|
|
for proxy in proxy_urls
|
|
}
|
|
for future in as_completed(future_map):
|
|
proxy_url = future_map[future]
|
|
try:
|
|
ok, msg = future.result()
|
|
if ok:
|
|
logger.success(f"并发验证找到可用代理: {proxy_url}")
|
|
for item in future_map:
|
|
if item != future:
|
|
item.cancel()
|
|
return proxy_url, msg
|
|
except Exception:
|
|
continue
|
|
|
|
return None, f'共 {len(proxy_urls)} 个代理均不可用'
|