Files
live-hub-py/core/douyu/proxy.py
T
2026-06-22 13:59:03 +08:00

212 lines
7.0 KiB
Python

"""代理管理模块"""
import re
import time
import requests
from typing import Optional
from loguru import logger
class ProxyManager:
"""代理管理器"""
def __init__(self, api_url: str = ""):
self.api_url = api_url
self.current_proxy: Optional[str] = None
def get_proxy(self) -> Optional[str]:
"""
从代理API获取代理IP
Returns:
代理URL,格式: http://ip:port
"""
try:
logger.info("获取代理IP...")
response = requests.get(self.api_url, timeout=10)
response.raise_for_status()
text = response.text.strip()
logger.debug(f"代理API响应: {text}")
# 解析IP:Port格式
match = re.search(r'(\d+\.\d+\.\d+\.\d+):(\d+)', text)
if match:
ip = match.group(1)
port = match.group(2)
proxy = f"http://{ip}:{port}"
self.current_proxy = proxy
logger.info(f"获取到代理: {proxy}")
return proxy
logger.warning(f"无法解析代理地址: {text}")
return None
except Exception as e:
logger.error(f"获取代理失败: {e}")
return None
def get_proxies_dict(self, proxy: str = None) -> dict:
"""获取requests使用的proxies字典"""
proxy = proxy or self.current_proxy
if proxy:
return {'http': proxy, 'https': proxy}
return {}
def verify_proxy(self, proxy: str = None) -> bool:
"""验证代理是否可用"""
proxy = proxy or self.current_proxy
if not proxy:
return False
try:
response = requests.get(
'https://httpbin.org/ip',
proxies={'http': proxy, 'https': proxy},
timeout=10
)
if response.status_code == 200:
data = response.json()
logger.info(f"代理验证成功,当前IP: {data.get('origin')}")
return True
return False
except Exception as e:
logger.error(f"代理验证失败: {e}")
return False
def get_proxy_manager(api_url: str = "") -> ProxyManager:
"""获取代理管理器实例(每次传入 api_url 时创建新实例,避免全局状态污染)"""
return ProxyManager(api_url)
def parse_proxy_response(text: str) -> tuple[Optional[str], Optional[str]]:
"""
解析代理API响应。
Returns:
(proxy_url, whitelist_ip)
- proxy_url: 解析到的代理地址(http://ip:port),无法解析时为 None
- whitelist_ip: 需要添加到白名单的IP(当API返回白名单错误时),无错误时为 None
"""
text = text.strip()
# 正常代理地址
match = re.search(r'(\d+\.\d+\.\d+\.\d+):(\d+)', text)
if match:
ip, port = match.group(1), match.group(2)
# 排除 "请先添加白名单:1.2.3.4" 中误匹配到 ip:port 的情况
if '白名单' not in text:
return f"http://{ip}:{port}", None
# 白名单错误:DB1.请先添加白名单:39.144.114.76
if '添加白名单' in text or '白名单' in text:
ip_match = re.search(r'(\d+\.\d+\.\d+\.\d+)', text)
if ip_match:
return None, ip_match.group(1)
return None, None
def verify_proxy_url(proxy_url: str, timeout: tuple = (4, 6)) -> tuple[bool, str]:
"""
验证代理是否可用。
Returns:
(是否可用, 消息)
"""
proxies = {'http': proxy_url, 'https': proxy_url}
targets = [
'https://qifu-api.baidubce.com/ip/local/geo/v1/district',
'https://myip.ipip.net',
'https://4.ipw.cn',
]
for url in targets:
try:
response = requests.get(
url, proxies=proxies, timeout=timeout,
headers={'User-Agent': 'Mozilla/5.0'},
)
response.raise_for_status()
return True, f'代理可用: {url.split("/")[2]}'
except Exception as e:
err_msg = str(e)
if 'Tunnel connection failed' in err_msg or '503' in err_msg:
detail = '代理拒绝连接(白名单可能未生效)'
elif 'timed out' in err_msg.lower():
detail = '连接超时'
else:
detail = type(e).__name__
logger.debug(f"代理验证 {url} 失败: {detail}")
continue
return False, '代理验证失败(所有目标不可达)'
def resolve_working_proxy(
api_url: str,
whitelist_uid: str = "",
whitelist_ukey: str = "",
max_attempts: int = 5,
log_func=None,
) -> tuple[Optional[str], str]:
"""
从代理API获取可用代理,自动处理白名单同步。
Args:
api_url: 代理API地址
whitelist_uid: 白名单UID(启用白名单时传入)
whitelist_ukey: 白名单UKEY
max_attempts: 最大尝试次数
log_func: 日志回调函数 (level, message)
Returns:
(代理URL, 消息)
"""
def log(level, msg):
if log_func:
log_func(level, msg)
else:
getattr(logger, level if level in ('info', 'warning', 'error', 'success') else 'info', logger.info)(msg)
synced_whitelist = False
for attempt in range(1, max_attempts + 1):
log('info', f'代理预检 {attempt}/{max_attempts}: 正在获取代理')
try:
response = requests.get(api_url, timeout=10)
response.raise_for_status()
text = response.text.strip()
proxy_url, whitelist_ip = parse_proxy_response(text)
if proxy_url:
# 验证代理
ok, msg = verify_proxy_url(proxy_url)
if ok:
log('success', f'代理预检成功: {proxy_url}')
return proxy_url, msg
log('warning', f'代理预检 {attempt}/{max_attempts}: {msg}')
continue
# 代理API返回白名单错误
if whitelist_ip and not synced_whitelist and whitelist_uid and whitelist_ukey:
log('warning', f'代理需要白名单IP: {whitelist_ip},自动同步...')
from core.douyu.whitelist import WhitelistManager
manager = WhitelistManager(whitelist_uid, whitelist_ukey)
ok, sync_msg = manager.sync_ip(whitelist_ip)
log('success' if ok else 'error', f'白名单同步: {sync_msg}')
if ok:
synced_whitelist = True
log('info', '白名单已更新,等待2秒后重试...')
time.sleep(2)
continue
return None, f'白名单同步失败: {sync_msg}'
log('warning', f'代理预检 {attempt}/{max_attempts}: 代理API响应无法解析: {text[:80]}')
except Exception as exc:
log('warning', f'代理预检 {attempt}/{max_attempts}: 代理API请求失败: {exc}')
return None, f'代理预检失败,已尝试 {max_attempts} 次'