diff --git a/core/logger.py b/core/logger.py index 56b2a0a..bb7fb79 100644 --- a/core/logger.py +++ b/core/logger.py @@ -7,56 +7,45 @@ from logging.handlers import RotatingFileHandler def setup_logger( - name: str = "bot", + name: str = None, log_level: int = logging.INFO, log_file: str = "logs/bot.log" ) -> logging.Logger: - """ - Configure and return logger instance. - - Args: - name: Logger name - log_level: Logging level - log_file: Path to log file - - Returns: - Configured logger instance - """ - logger = logging.getLogger(name) - logger.setLevel(log_level) - - # Create formatters + root = logging.getLogger() + + # Не добавляем хендлеры повторно + if root.handlers: + return logging.getLogger(name) if name else root + + root.setLevel(log_level) + console_formatter = logging.Formatter( '%(asctime)s | %(levelname)-8s | %(name)s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) - file_formatter = logging.Formatter( '%(asctime)s | %(levelname)-8s | %(name)s | %(funcName)s:%(lineno)d | %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) - - # Console handler + console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(log_level) console_handler.setFormatter(console_formatter) - - # File handler + root.addHandler(console_handler) + if log_file: Path(log_file).parent.mkdir(parents=True, exist_ok=True) file_handler = RotatingFileHandler( log_file, - maxBytes=10*1024*1024, # 10MB + maxBytes=10 * 1024 * 1024, backupCount=5, encoding='utf-8' ) file_handler.setLevel(log_level) file_handler.setFormatter(file_formatter) - logger.addHandler(file_handler) - - logger.addHandler(console_handler) - - return logger + root.addHandler(file_handler) + + return logging.getLogger(name) if name else root logger = setup_logger() \ No newline at end of file diff --git a/services/browser_service.py b/services/browser_service.py index 89388a9..cd9858e 100644 --- a/services/browser_service.py +++ b/services/browser_service.py @@ -20,6 +20,21 @@ from services.socks5_to_http_proxy import Socks5ProxyPool logger = logging.getLogger(__name__) +def _get_proxy_locale(proxy_config: dict) -> str: + """Возвращает locale без script-тега для IP прокси (ru-Cyrl-RU → ru-RU).""" + try: + from camoufox.utils import get_geolocation + ip = proxy_config.get("server", "").replace("http://", "").split(":")[0] + geo = get_geolocation(ip) + lang = geo.locale.language + region = geo.locale.region + if lang and region: + return f"{lang}-{region}" + except Exception: + pass + return "en-US" + + @dataclass class VisitResult: """Результат посещения страницы.""" @@ -58,29 +73,27 @@ class BrowserService: settings.DEFAULT_MAX_READING ) - for attempt in range(3): - proxy = None - use_socks5 = False + proxy = None + use_socks5 = False - try: - # Получаем прокси - if self.proxy_manager.has_proxies(): - proxy = await self.proxy_manager.acquire_proxy(timeout=30) + try: + if self.proxy_manager.has_proxies(): + proxy = await self.proxy_manager.acquire_proxy(timeout=30) - # Определяем конфигурацию - if proxy and proxy.proxy_type in [ProxyType.SOCKS5, ProxyType.SOCKS4]: - proxy_config = await self.socks5_pool.get_proxy_config(proxy) - use_socks5 = True - logger.info(f"Using SOCKS5 tunnel for: {proxy.id}") - elif proxy: - proxy_config = proxy.proxy_config - logger.info(f"Using HTTP proxy: {proxy.id}") - else: - proxy_config = None - logger.info("Direct connection") + if proxy and proxy.proxy_type in [ProxyType.SOCKS5, ProxyType.SOCKS4]: + proxy_config = await self.socks5_pool.get_proxy_config(proxy) + use_socks5 = True + logger.info(f"Using SOCKS5 tunnel for: {proxy.id}") + elif proxy: + proxy_config = proxy.proxy_config + logger.info(f"Using HTTP proxy: {proxy.id}") + else: + proxy_config = None + logger.info("Direct connection") - # Запускаем браузер - if proxy_config: + if proxy_config: + # Сначала пробуем с geoip — fingerprint соответствует региону прокси + try: async with AsyncCamoufox( headless=True, geoip=True, @@ -94,42 +107,54 @@ class BrowserService: proxy.id if proxy else "direct", reading_time ) - else: + except ValueError: + # Camoufox на Linux формирует locale с script-тегом (ru-Cyrl-RU), + # которого нет в базе browserforge — передаём базовую locale без script + try: + geo = _get_proxy_locale(proxy_config) + except Exception: + geo = "ru-RU" + logger.warning(f"geoip script-locale unavailable, retrying with base locale: {geo}") async with AsyncCamoufox( headless=True, - geoip=False, + geoip=True, humanize=True, - locale="ru-RU", - exclude_addons=[DefaultAddons.UBO] + os="windows", + locale=geo, + exclude_addons=[DefaultAddons.UBO], + proxy=proxy_config ) as browser: result = await self._browse_page( - browser, url, "direct", reading_time + browser, url, + proxy.id if proxy else "direct", + reading_time ) + else: + async with AsyncCamoufox( + headless=True, + geoip=False, + humanize=True, + locale="ru-RU", + exclude_addons=[DefaultAddons.UBO] + ) as browser: + result = await self._browse_page( + browser, url, "direct", reading_time + ) - # Освобождаем - if proxy: - await self.proxy_manager.release_proxy(proxy, result.success) - if use_socks5: - await self.socks5_pool.release(proxy) + if proxy: + await self.proxy_manager.release_proxy(proxy, result.success) + if use_socks5: + await self.socks5_pool.release(proxy) - return result + return result - except ValueError as e: - logger.warning(f"Visit attempt {attempt + 1} fingerprint error, retrying with new proxy: {e}") - if proxy: - await self.proxy_manager.release_proxy(proxy, success=False) - if use_socks5: - await self.socks5_pool.release(proxy) - - except Exception as e: - logger.error(f"Visit error: {e}", exc_info=True) - if proxy: - await self.proxy_manager.release_proxy(proxy, success=False) - if use_socks5: - await self.socks5_pool.release(proxy) - return VisitResult(url=url, success=False, error=str(e)) - - return VisitResult(url=url, success=False, error="All attempts failed (geoip fingerprint error)") + except Exception as e: + logger.error(f"Visit error: {e}", exc_info=True) + if proxy: + await self.proxy_manager.release_proxy(proxy, success=False) + if use_socks5: + await self.socks5_pool.release(proxy) + return VisitResult(url=url, success=False, error=str(e)) async def _browse_page( self, browser, url: str, proxy_info: str, reading_time: int