Files
2026-08-31 10:55:44 +08:00

108 lines
3.8 KiB
Python

"""代理可用性验证。"""
from concurrent.futures import ThreadPoolExecutor, as_completed
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: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
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[str | list[str] | None, 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 as exc: # noqa: BLE001
logger.debug(f"代理并发验证失败: {future_map[future]}: {exc}")
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 as exc: # noqa: BLE001
logger.debug(f"代理并发验证失败: {proxy_url}: {exc}")
continue
return None, f"共 {len(proxy_urls)} 个代理均不可用"