From 9b246c5cf09928b2583212eed8dec07951a110fa Mon Sep 17 00:00:00 2001 From: Yuriy Yuriev Date: Mon, 18 May 2026 23:14:43 +0700 Subject: [PATCH] fix proxy detect --- managers/proxy_manager.py | 42 ++++++++++++++++++++++++ services/browser_service.py | 64 ++++++++++++++++++++++++++----------- 2 files changed, 87 insertions(+), 19 deletions(-) diff --git a/managers/proxy_manager.py b/managers/proxy_manager.py index ac8e881..0382763 100644 --- a/managers/proxy_manager.py +++ b/managers/proxy_manager.py @@ -104,6 +104,48 @@ class Proxy: return None +async def detect_proxy_type( + ip: str, + port: str, + login: Optional[str] = None, + password: Optional[str] = None, + timeout: float = 5.0, + test_url: str = "http://api.ipify.org" +) -> Optional[ProxyType]: + """ + Определяет тип прокси подключением. + Пробует SOCKS5 → SOCKS4 → HTTP. Возвращает первый рабочий или None. + """ + import aiohttp + + candidates = [ + (ProxyType.SOCKS5, f"socks5://{ip}:{port}"), + (ProxyType.SOCKS4, f"socks4://{ip}:{port}"), + (ProxyType.HTTP, f"http://{ip}:{port}"), + ] + + auth = aiohttp.BasicAuth(login, password) if login and password else None + + for proxy_type, proxy_url in candidates: + try: + connector = aiohttp.TCPConnector(ssl=False) + async with aiohttp.ClientSession(connector=connector) as session: + async with session.get( + test_url, + proxy=proxy_url, + proxy_auth=auth, + timeout=aiohttp.ClientTimeout(total=timeout), + ) as resp: + if resp.status == 200: + logger.info(f"Proxy {ip}:{port} detected as {proxy_type.value}") + return proxy_type + except Exception: + pass + + logger.warning(f"Proxy {ip}:{port} — type not detected") + return None + + class ProxyManager: """ Менеджер прокси с поддержкой SOCKS5 и HTTP. diff --git a/services/browser_service.py b/services/browser_service.py index 057b9d3..ba4fecb 100644 --- a/services/browser_service.py +++ b/services/browser_service.py @@ -12,6 +12,7 @@ from dataclasses import dataclass from camoufox import DefaultAddons from camoufox.async_api import AsyncCamoufox +from camoufox.exceptions import InvalidIP from config.settings import settings from managers.proxy_manager import ProxyManager, Proxy, ProxyType @@ -20,23 +21,43 @@ from services.socks5_to_http_proxy import Socks5ProxyPool logger = logging.getLogger(__name__) -def _patch_camoufox(): - """Патчит camoufox.utils.generate_fingerprint чтобы убирать screen constraint при ошибке.""" - import camoufox.utils as _cu - _orig = _cu.generate_fingerprint +_socks5_real_ip: Optional[str] = None - def _patched(**config): + +def _patch_camoufox(): + """Патчит camoufox чтобы: + 1. Убирать screen constraint из generate_fingerprint при ошибке + 2. Возвращать реальный IP SOCKS5 прокси вместо попытки запроса через туннель + """ + import camoufox.utils as _cu + import camoufox.ip as _ci + + # Патч 1: generate_fingerprint — убираем screen при ошибке + _orig_gf = _cu.generate_fingerprint + + def _patched_gf(**config): try: - return _orig(**config) + return _orig_gf(**config) except ValueError: config.pop("screen", None) try: - return _orig(**config) + return _orig_gf(**config) except ValueError: minimal = {k: v for k, v in config.items() if k == "locale"} - return _orig(**minimal) + return _orig_gf(**minimal) - _cu.generate_fingerprint = _patched + _cu.generate_fingerprint = _patched_gf + + # Патч 2: public_ip — возвращаем реальный IP прокси вместо HTTP-запроса через туннель + _orig_ip = _ci.public_ip + + def _patched_ip(proxy_string: str) -> str: + global _socks5_real_ip + if _socks5_real_ip and "127.0.0.1" in str(proxy_string): + return _socks5_real_ip + return _orig_ip(proxy_string) + + _ci.public_ip = _patched_ip _patch_camoufox() @@ -101,20 +122,25 @@ class BrowserService: if self.proxy_manager.has_proxies(): proxy = await self.proxy_manager.acquire_proxy(timeout=30) - socks5_locale = None + global _socks5_real_ip + proxy_locale = None + if proxy and proxy.proxy_type in [ProxyType.SOCKS5, ProxyType.SOCKS4]: proxy_config = await self.socks5_pool.get_proxy_config(proxy) use_socks5 = True - # При SOCKS5 camoufox видит 127.0.0.1 → geoip берёт IP сервера → ru-Cyrl-RU - # Делаем lookup по реальному IP прокси сами и передаём чистый locale real_ip = proxy.id.split("://")[-1].split(":")[0] - socks5_locale = _locale_for_ip(real_ip) - logger.info(f"Using SOCKS5 tunnel for: {proxy.id} locale={socks5_locale}") + _socks5_real_ip = real_ip + proxy_locale = _locale_for_ip(real_ip) + logger.info(f"Using SOCKS5 tunnel for: {proxy.id} locale={proxy_locale}") elif proxy: proxy_config = proxy.proxy_config - logger.info(f"Using HTTP proxy: {proxy.id}") + real_ip = proxy.id.split("://")[-1].split(":")[0] + _socks5_real_ip = real_ip + proxy_locale = _locale_for_ip(real_ip) + logger.info(f"Using HTTP proxy: {proxy.id} locale={proxy_locale}") else: proxy_config = None + _socks5_real_ip = None logger.info("Direct connection") if proxy_config: @@ -125,8 +151,8 @@ class BrowserService: exclude_addons=[DefaultAddons.UBO], proxy=proxy_config, ) - if socks5_locale: - camoufox_kwargs["locale"] = socks5_locale + if proxy_locale: + camoufox_kwargs["locale"] = proxy_locale async with AsyncCamoufox(**camoufox_kwargs) as browser: result = await self._browse_page( browser, url, @@ -152,8 +178,8 @@ class BrowserService: return result - except ValueError as e: - logger.warning(f"Visit attempt {attempt + 1} fingerprint error, retrying with new proxy: {e}") + except (ValueError, InvalidIP) as e: + logger.warning(f"Visit attempt {attempt + 1} proxy error, retrying: {e}") if proxy: await self.proxy_manager.release_proxy(proxy, success=False) if use_socks5: