"""代理可用性验证。""" 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)} 个代理均不可用"