From 2f18f4a5a1612fbb8a7dda6de356de987b195094 Mon Sep 17 00:00:00 2001 From: Yuriy Yuriev Date: Fri, 22 May 2026 01:22:56 +0700 Subject: [PATCH] fix proxy --- handlers/commands.py | 12 +++++++- managers/proxy_manager.py | 56 +++++++++++++++++++++++++------------ services/browser_service.py | 22 +++++++++++---- 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/handlers/commands.py b/handlers/commands.py index 3be320b..619a4d4 100644 --- a/handlers/commands.py +++ b/handlers/commands.py @@ -255,6 +255,14 @@ class BotInterface: await callback.answer() # === Перезапуск === + @dp.callback_query(F.data == "reload_proxies") + async def cb_reload_proxies(callback: CallbackQuery): + if not await require_admin(callback): + return + count = interface.proxy_manager.reload() + await interface._show_status(callback.message, edit=True) + await callback.answer(f"✅ Прокси перезагружены: {count} шт.") + @dp.callback_query(F.data == "bot_restart_confirm") async def cb_restart_confirm(callback: CallbackQuery): if not await require_admin(callback): @@ -1685,6 +1693,7 @@ class BotInterface: ) builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="🔄 Обновить", callback_data="menu_status")) + builder.row(InlineKeyboardButton(text="🔃 Перезагрузить прокси", callback_data="reload_proxies")) builder.row(InlineKeyboardButton(text="🔁 Перезапустить бота", callback_data="bot_restart_confirm")) builder.row(InlineKeyboardButton(text="🔙 В меню", callback_data="menu_main")) await self._edit_or_send(message, text, builder.as_markup(), edit) @@ -2166,7 +2175,8 @@ class BotInterface: delay = params.get_delay() series_size = params.get_series_size() logger.info( - f"⏳ Next series: {series_size} clicks, " + f"⏳ Next series: {series_size} clicks " + f"(range {params.min_series}-{params.max_series}), " f"delay {delay}s ({delay//60}m {delay%60}s), " f"reading {params.min_reading}-{params.max_reading}s, " f"done {params.successful_visits}/{max_v}" diff --git a/managers/proxy_manager.py b/managers/proxy_manager.py index fd5d1d5..83c7387 100644 --- a/managers/proxy_manager.py +++ b/managers/proxy_manager.py @@ -69,13 +69,13 @@ class Proxy: - http://ip:port:login:password """ line = line.strip() - + # Определяем тип прокси по префиксу proxy_type = default_type if "://" in line: protocol, rest = line.split("://", 1) protocol = protocol.lower() - + if protocol in ["socks5", "socks"]: proxy_type = ProxyType.SOCKS5 elif protocol == "socks4": @@ -84,23 +84,27 @@ class Proxy: proxy_type = ProxyType.HTTP elif protocol == "https": proxy_type = ProxyType.HTTPS - + line = rest - - # Парсим части + + # Поддержка формата login:pass@ip:port + login = password = None + if "@" in line: + creds, line = line.rsplit("@", 1) + if ":" in creds: + login, password = creds.split(":", 1) + + # Парсим ip:port или ip:port:login:pass parts = line.split(":") - + if len(parts) == 2: - return cls(ip=parts[0], port=parts[1], proxy_type=proxy_type) + return cls(ip=parts[0], port=parts[1], proxy_type=proxy_type, + login=login, password=password) elif len(parts) == 4: - return cls( - ip=parts[0], - port=parts[1], - login=parts[2], - password=parts[3], - proxy_type=proxy_type - ) - + return cls(ip=parts[0], port=parts[1], + login=parts[2], password=parts[3], + proxy_type=proxy_type) + return None @@ -294,9 +298,12 @@ class ProxyManager: continue working = self._working_status.get(proxy.id, True) - scheme = proxy.proxy_type.value # socks5, http, etc. - creds = f"{proxy.login}:{proxy.password}@" if proxy.login else "" - new_line = f"{scheme}://{creds}{proxy.ip}:{proxy.port}" + scheme = proxy.proxy_type.value + # Формат совместимый с from_line: scheme://ip:port:login:pass + if proxy.login and proxy.password: + new_line = f"{scheme}://{proxy.ip}:{proxy.port}:{proxy.login}:{proxy.password}" + else: + new_line = f"{scheme}://{proxy.ip}:{proxy.port}" if not working: new_line = f"!{new_line}" @@ -410,6 +417,19 @@ class ProxyManager: loop = asyncio.get_running_loop() await loop.run_in_executor(None, self._save_proxies_file) + def reload(self) -> int: + """Перезагружает прокси из файла. Возвращает количество загруженных.""" + self._proxies.clear() + self._working_status.clear() + self._in_use.clear() + self._usage_count.clear() + self._cooldown_until.clear() + self._used_ids.clear() + self._needs_detection.clear() + self._load_proxies() + logger.info(f"Proxies reloaded: {len(self._proxies)}") + return len(self._proxies) + def get_stats(self) -> dict: """Статистика прокси.""" loop = asyncio.get_running_loop() diff --git a/services/browser_service.py b/services/browser_service.py index 04fd622..b91ffb3 100644 --- a/services/browser_service.py +++ b/services/browser_service.py @@ -188,16 +188,26 @@ class BrowserService: else: return VisitResult(url=url, success=False, error="No proxy available") + # Определяем тип ошибки: ошибка соединения (прокси сломан) vs ошибка загрузки (прокси ok) + is_proxy_dead = result.error and any( + k in result.error for k in ("502", "Bad Gateway", "SOCKS", "NS_ERROR_PROXY") + ) + is_load_error = result.error and not is_proxy_dead and any( + k in result.error for k in ("Timeout", "ERR_", "NS_ERROR_", "Connection") + ) + if proxy: - await self.proxy_manager.release_proxy(proxy, result.success) + # Таймаут/ошибка загрузки — прокси не виноват, освобождаем без штрафа + release_success = True if is_load_error else result.success + await self.proxy_manager.release_proxy(proxy, release_success) if use_socks5: await self.socks5_pool.release(proxy) - # Если таймаут или соединение не прошло — помечаем прокси как нерабочий и меняем - if not result.success and result.error and any( - k in result.error for k in ("Timeout", "ERR_", "Connection", "SOCKS", "NS_ERROR_", "502", "Bad Gateway") - ): - logger.warning(f"Visit attempt {attempt + 1} bad proxy, retrying with new") + if is_proxy_dead: + logger.warning(f"Visit attempt {attempt + 1} proxy connection failed, retrying with new") + continue + elif is_load_error: + logger.warning(f"Visit attempt {attempt + 1} page load error (proxy ok), retrying") continue return result