Files
live-hub-py/core/douyu/proxy.py
T
2026-06-22 21:14:38 +08:00

246 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""代理管理模块"""
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,支持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}")
proxy_urls, _ = parse_proxy_response(text)
if not proxy_urls:
logger.warning(f"无法解析代理地址: {text}")
return None
if len(proxy_urls) == 1:
proxy = proxy_urls[0]
self.current_proxy = proxy
logger.info(f"获取到代理: {proxy}")
return proxy
# 多个代理逐一验证,返回第一个可用的
logger.info(f"获取到 {len(proxy_urls)} 个代理,逐一验证")
for proxy in proxy_urls:
if self.verify_proxy(proxy):
self.current_proxy = proxy
logger.success(f"可用代理: {proxy}")
return proxy
logger.warning(f"代理 {proxy} 不可用,尝试下一个")
logger.warning("所有代理均不可用")
self.current_proxy = None
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[list[str], Optional[str]]:
"""
解析代理API响应,支持返回多个代理地址。
Returns:
(proxy_urls, whitelist_ip)
- proxy_urls: 解析到的所有代理地址列表(http://ip:port
- whitelist_ip: 需要添加到白名单的IP(当API返回白名单错误时),无错误时为 None
"""
text = text.strip()
# 白名单错误优先检测
if '添加白名单' in text or '白名单' in text:
ip_match = re.search(r'(\d+\.\d+\.\d+\.\d+)', text)
if ip_match:
return [], ip_match.group(1)
# 解析所有 ip:port,支持多行格式
matches = re.findall(r'(\d+\.\d+\.\d+\.\d+):(\d+)', text)
proxies = []
for ip, port in matches:
# 排除白名单提示中误匹配的
proxy = f"http://{ip}:{port}"
proxies.append(proxy)
if proxies:
return proxies, None
return [], None
def verify_proxy_url(proxy_url: str, timeout: tuple = (4, 6)) -> tuple[bool, str]:
"""
验证代理是否可用。
验证优先级:
1. 斗鱼主站(最相关,能访问斗鱼才是最终目的)
2. myip(快速 IP 验证)
3. 百度 IP 查询(备用)
Returns:
(是否可用, 消息)
"""
proxies = {'http': proxy_url, 'https': proxy_url}
targets = [
('https://www.douyu.com', '斗鱼主站'),
('https://myip.ipip.net', 'IP验证'),
('https://qifu-api.baidubce.com/ip/local/geo/v1/district', '百度IP查询'),
]
for url, label in targets:
try:
response = requests.get(
url, proxies=proxies, timeout=timeout,
headers={'User-Agent': 'Mozilla/5.0'},
)
response.raise_for_status()
return True, f'代理可用 → {label}'
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"代理验证 {label} 失败: {detail}")
continue
return False, '代理验证失败(所有目标不可达)'
def resolve_working_proxy(
api_url: str,
whitelist_uid: str = "",
whitelist_ukey: str = "",
max_attempts: int = 3,
log_func=None,
) -> tuple[Optional[str], str]:
"""
从代理API获取可用代理,自动处理白名单同步。
支持API返回多个代理IP,逐一验证直到找到可用的。
Args:
api_url: 代理API地址
whitelist_uid: 白名单UID(启用白名单时传入)
whitelist_ukey: 白名单UKEY
max_attempts: 最大获取尝试次数(默认3次)
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
last_error = ""
for attempt in range(1, max_attempts + 1):
# 重试之间增加退避延迟,避免代理API返回同一个不可用IP
if attempt > 1:
delay = min(attempt, 3)
log('info', f'等待 {delay}s 后重试...')
time.sleep(delay)
log('info', f'代理预检 {attempt}/{max_attempts}: 正在获取代理')
try:
response = requests.get(api_url, timeout=10)
response.raise_for_status()
text = response.text.strip()
proxy_urls, whitelist_ip = parse_proxy_response(text)
if proxy_urls:
log('info', f'获取到 {len(proxy_urls)} 个代理,逐一验证')
for proxy_url in proxy_urls:
ok, msg = verify_proxy_url(proxy_url)
if ok:
log('success', f'代理预检成功: {proxy_url}')
return proxy_url, msg
log('warning', f'代理 {proxy_url} 不可用: {msg}')
last_error = f'共 {len(proxy_urls)} 个代理均不可用'
log('warning', f'代理预检 {attempt}/{max_attempts}: {last_error}')
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}'
last_error = f'代理API响应无法解析'
log('warning', f'代理预检 {attempt}/{max_attempts}: {last_error}: {text[:80]}')
except Exception as exc:
last_error = f'代理API请求失败: {exc}'
log('warning', f'代理预检 {attempt}/{max_attempts}: {last_error}')
return None, f'代理预检失败({max_attempts}次尝试均失败): {last_error}'