144 lines
5.2 KiB
Python
144 lines
5.2 KiB
Python
"""代理池管理器 - 从URL动态获取代理IP"""
|
||
|
||
import asyncio
|
||
import aiohttp
|
||
import time
|
||
from typing import Optional, List
|
||
from app.core.logger import logger
|
||
|
||
|
||
class ProxyPool:
|
||
"""代理池管理器"""
|
||
|
||
def __init__(self):
|
||
self._pool_url: Optional[str] = None
|
||
self._static_proxy: Optional[str] = None
|
||
self._current_proxy: Optional[str] = None
|
||
self._last_fetch_time: float = 0
|
||
self._fetch_interval: int = 300 # 5分钟刷新一次
|
||
self._enabled: bool = False
|
||
self._lock = asyncio.Lock()
|
||
|
||
def configure(self, proxy_url: str, proxy_pool_url: str = "", proxy_pool_interval: int = 300):
|
||
"""配置代理池
|
||
|
||
Args:
|
||
proxy_url: 静态代理URL(socks5h://xxx 或 http://xxx)
|
||
proxy_pool_url: 代理池API URL,返回单个代理地址
|
||
proxy_pool_interval: 代理池刷新间隔(秒)
|
||
"""
|
||
self._static_proxy = proxy_url if proxy_url else None
|
||
self._pool_url = proxy_pool_url if proxy_pool_url else None
|
||
self._fetch_interval = proxy_pool_interval
|
||
self._enabled = bool(self._pool_url)
|
||
|
||
if self._enabled:
|
||
logger.info(f"[ProxyPool] 代理池已启用: {self._pool_url}, 刷新间隔: {self._fetch_interval}s")
|
||
elif self._static_proxy:
|
||
logger.info(f"[ProxyPool] 使用静态代理: {self._static_proxy}")
|
||
self._current_proxy = self._static_proxy
|
||
else:
|
||
logger.info("[ProxyPool] 未配置代理")
|
||
|
||
async def get_proxy(self) -> Optional[str]:
|
||
"""获取代理地址
|
||
|
||
Returns:
|
||
代理URL或None
|
||
"""
|
||
# 如果未启用代理池,返回静态代理
|
||
if not self._enabled:
|
||
return self._static_proxy
|
||
|
||
# 检查是否需要刷新
|
||
now = time.time()
|
||
if not self._current_proxy or (now - self._last_fetch_time) >= self._fetch_interval:
|
||
async with self._lock:
|
||
# 双重检查
|
||
if not self._current_proxy or (now - self._last_fetch_time) >= self._fetch_interval:
|
||
await self._fetch_proxy()
|
||
|
||
return self._current_proxy
|
||
|
||
async def force_refresh(self) -> Optional[str]:
|
||
"""强制刷新代理(用于403错误重试)
|
||
|
||
Returns:
|
||
新的代理URL或None
|
||
"""
|
||
if not self._enabled:
|
||
return self._static_proxy
|
||
|
||
async with self._lock:
|
||
await self._fetch_proxy()
|
||
|
||
return self._current_proxy
|
||
|
||
async def _fetch_proxy(self):
|
||
"""从代理池URL获取新的代理"""
|
||
try:
|
||
logger.debug(f"[ProxyPool] 正在从代理池获取新代理: {self._pool_url}")
|
||
|
||
timeout = aiohttp.ClientTimeout(total=10)
|
||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||
async with session.get(self._pool_url) as response:
|
||
if response.status == 200:
|
||
proxy_text = await response.text()
|
||
proxy = proxy_text.strip()
|
||
|
||
# 验证代理格式
|
||
if self._validate_proxy(proxy):
|
||
self._current_proxy = proxy
|
||
self._last_fetch_time = time.time()
|
||
logger.info(f"[ProxyPool] 成功获取新代理: {proxy}")
|
||
else:
|
||
logger.error(f"[ProxyPool] 代理格式无效: {proxy}")
|
||
# 降级到静态代理
|
||
if not self._current_proxy:
|
||
self._current_proxy = self._static_proxy
|
||
else:
|
||
logger.error(f"[ProxyPool] 获取代理失败: HTTP {response.status}")
|
||
# 降级到静态代理
|
||
if not self._current_proxy:
|
||
self._current_proxy = self._static_proxy
|
||
|
||
except asyncio.TimeoutError:
|
||
logger.error("[ProxyPool] 获取代理超时")
|
||
if not self._current_proxy:
|
||
self._current_proxy = self._static_proxy
|
||
|
||
except Exception as e:
|
||
logger.error(f"[ProxyPool] 获取代理异常: {e}")
|
||
# 降级到静态代理
|
||
if not self._current_proxy:
|
||
self._current_proxy = self._static_proxy
|
||
|
||
def _validate_proxy(self, proxy: str) -> bool:
|
||
"""验证代理格式
|
||
|
||
Args:
|
||
proxy: 代理URL
|
||
|
||
Returns:
|
||
是否有效
|
||
"""
|
||
if not proxy:
|
||
return False
|
||
|
||
# 支持的协议
|
||
valid_protocols = ['http://', 'https://', 'socks5://', 'socks5h://']
|
||
|
||
return any(proxy.startswith(proto) for proto in valid_protocols)
|
||
|
||
def get_current_proxy(self) -> Optional[str]:
|
||
"""获取当前使用的代理(同步方法)
|
||
|
||
Returns:
|
||
当前代理URL或None
|
||
"""
|
||
return self._current_proxy or self._static_proxy
|
||
|
||
|
||
# 全局代理池实例
|
||
proxy_pool = ProxyPool()
|