Spaces:
Running
Running
| """ | |
| 动态代理获取模块 | |
| 支持通过外部 API 获取动态代理 URL | |
| """ | |
| import logging | |
| import re | |
| from typing import Optional | |
| logger = logging.getLogger(__name__) | |
| def fetch_dynamic_proxy(api_url: str, api_key: str = "", api_key_header: str = "X-API-Key", result_field: str = "") -> Optional[str]: | |
| """ | |
| 从代理 API 获取代理 URL | |
| Args: | |
| api_url: 代理 API 地址,响应应为代理 URL 字符串或含代理 URL 的 JSON | |
| api_key: API 密钥(可选) | |
| api_key_header: API 密钥请求头名称 | |
| result_field: 从 JSON 响应中提取代理 URL 的字段路径,支持点号分隔(如 "data.proxy"),留空则使用响应原文 | |
| Returns: | |
| 代理 URL 字符串(如 http://user:pass@host:port),失败返回 None | |
| """ | |
| try: | |
| from curl_cffi import requests as cffi_requests | |
| headers = {} | |
| if api_key: | |
| headers[api_key_header] = api_key | |
| response = cffi_requests.get( | |
| api_url, | |
| headers=headers, | |
| timeout=10, | |
| impersonate="chrome110" | |
| ) | |
| if response.status_code != 200: | |
| logger.warning(f"动态代理 API 返回错误状态码: {response.status_code}") | |
| return None | |
| text = response.text.strip() | |
| # 尝试解析 JSON | |
| if result_field or text.startswith("{") or text.startswith("["): | |
| try: | |
| import json | |
| data = json.loads(text) | |
| if result_field: | |
| # 按点号路径逐层提取 | |
| for key in result_field.split("."): | |
| if isinstance(data, dict): | |
| data = data.get(key) | |
| elif isinstance(data, list) and key.isdigit(): | |
| data = data[int(key)] | |
| else: | |
| data = None | |
| if data is None: | |
| break | |
| proxy_url = str(data).strip() if data is not None else None | |
| else: | |
| # 无指定字段,尝试常见键名 | |
| for key in ("proxy", "url", "proxy_url", "data", "ip"): | |
| val = data.get(key) if isinstance(data, dict) else None | |
| if val: | |
| proxy_url = str(val).strip() | |
| break | |
| else: | |
| proxy_url = text | |
| except (ValueError, AttributeError): | |
| proxy_url = text | |
| else: | |
| proxy_url = text | |
| if not proxy_url: | |
| logger.warning("动态代理 API 返回空代理 URL") | |
| return None | |
| # 若未包含协议头,默认加 http:// | |
| if not re.match(r'^(http|socks5)://', proxy_url): | |
| proxy_url = "http://" + proxy_url | |
| logger.info(f"动态代理获取成功: {proxy_url[:40]}..." if len(proxy_url) > 40 else f"动态代理获取成功: {proxy_url}") | |
| return proxy_url | |
| except Exception as e: | |
| logger.error(f"获取动态代理失败: {e}") | |
| return None | |
| def get_proxy_url_for_task() -> Optional[str]: | |
| """ | |
| 为注册任务获取代理 URL。 | |
| 优先使用动态代理(若启用),否则使用静态代理配置。 | |
| Returns: | |
| 代理 URL 或 None | |
| """ | |
| from ..config.settings import get_settings | |
| settings = get_settings() | |
| # 优先使用动态代理 | |
| if settings.proxy_dynamic_enabled and settings.proxy_dynamic_api_url: | |
| api_key = settings.proxy_dynamic_api_key.get_secret_value() if settings.proxy_dynamic_api_key else "" | |
| proxy_url = fetch_dynamic_proxy( | |
| api_url=settings.proxy_dynamic_api_url, | |
| api_key=api_key, | |
| api_key_header=settings.proxy_dynamic_api_key_header, | |
| result_field=settings.proxy_dynamic_result_field, | |
| ) | |
| if proxy_url: | |
| return proxy_url | |
| logger.warning("动态代理获取失败,回退到静态代理") | |
| # 使用静态代理 | |
| return settings.proxy_url | |