• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья GOOGLE ADS CLICKER с красивым GUI интерфейсом + компилятор в .exe для удобства!

stihl

Moderator
Регистрация
09.02.2012
Сообщения
1,178
Розыгрыши
0
Реакции
510
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
(!!!) Это моя первая техническая статья в жизни и по совместительству дебют на форуме, поэтому просьба строго меня не судить. Я не позиционирую себя как профессионала и знатока PYTHON, поэтому в коде скорее всего будут ошибки и недоработки. Просьба не ругаться на это. Спасибо.
GADS Clicker — это удобный инструмент для уничтожения бюджетов ваших конкурентов. Он построен на современной технологии, которая позволяет обрабатывать задачи быстро и эффективно, даже при больших нагрузках. Приложение реализует паттерн Model-View-Controller с элементами Event-Driven Architecture для обеспечения масштабируемости и отказоустойчивости.

Ключевые технологические компоненты:​

  1. Frontend: PyQt6 с кастомной темной темой и responsive UI ;
  2. Backend: Selenium WebDriver с Chrome headless режимом ;
  3. Networking: aiohttp для асинхронной валидации прокси ;
  4. Concurrency: ThreadPoolExecutor + multiprocessing для параллельного выполнения ;
  5. Communication: Qt Signals/Slots для многопоточного взаимодействия ;

Система импортов и инициализация приложения:
Python:
import sys
import random
import time
import multiprocessing
import asyncio
import aiohttp
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
Системные и базовые модули:
  • sys - управление интерпретатором Python, критично для sys.exit() и передачи аргументов
  • random - генерация псевдослучайных чисел для симуляции человеческого поведения
  • time - высокоточные задержки и измерение времени выполнения
  • multiprocessing - определение количества CPU ядер для оптимизации потоков
  • asyncio - событийный цикл для асинхронной валидации прокси
  • aiohttp - современная HTTP библиотека с поддержкой async/await
  • ThreadPoolExecutor - высокоуровневый интерфейс для управления пулом потоков
  • datetime - работа с временными метками и форматированием

PyQt6 архитектурные компоненты:
Python:
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                            QHBoxLayout, QPushButton, QLineEdit, QSpinBox,
                            QTextEdit, QLabel, QFileDialog, QProgressBar,
                            QMessageBox, QFrame, QGroupBox, QSplitter, QStatusBar)
from PyQt6.QtCore import pyqtSignal, QObject, QThread, Qt, QTimer, QSize
from PyQt6.QtGui import QFont, QIcon, QPixmap, QColor, QPalette
QtWidgets иерархия:
  • QApplication - единственный экземпляр приложения, управляет событийным циклом
  • QMainWindow - главное окно с встроенными menubar, toolbar, statusbar
  • Layout классы (QVBoxLayout, QHBoxLayout) - автоматическое позиционирование виджетов
  • QSplitter - изменяемый пользователем разделитель панелей
  • QFileDialog - нативные диалоги файловой системы ОС
QtCore функциональность:
  • pyqtSignal - типобезопасная система сигналов для межпоточной коммуникации
  • QThread - высокоуровневая обертка над OS потоками с Qt интеграцией
  • QTimer - неблокирующие таймеры с интеграцией в событийный цикл
  • Qt - перечисления и константы фреймворка
QtGui визуальные элементы:
  • QPixmap - оптимизированное представление изображений в памяти
  • QIcon - масштабируемые иконки с поддержкой различных разрешений
  • QPalette - системные цвета темы ОС

Selenium WebDriver экосистема:
Python:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
Архитектура WebDriver:
  • webdriver - базовый класс для всех браузерных драйверов
  • Options - конфигурация Chrome через command-line аргументы
  • Service - управление жизненным циклом ChromeDriver процесса
  • ActionChains - низкоуровневые действия мыши и клавиатуры
  • By - стратегии поиска элементов (CSS, XPath, ID, Class)
  • Keys - константы клавиш клавиатуры (ENTER, TAB, ESCAPE)
  • WebDriverWait + EC - явные ожидания с предикатами состояния

Системные утилиты и типизация:
Python:
import os
import logging
import json
from pathlib import Path
from typing import List, Optional, Dict, Any
import threading
from urllib.parse import urlparse
Файловая система и сериализация:
  • os - низкоуровневые операции ОС (переменные окружения, пути)
  • pathlib.Path - объектно-ориентированная работа с путями файлов
  • json - сериализация настроек приложения в человекочитаемый формат
Типизация и валидация:
  • typing модуль - статическая типизация для IDE и линтеров
  • List[str] - списки строк для прокси и User-Agent
  • Optional[str] - nullable строки для файловых путей
  • Dict[str, Any] - словари с произвольными значениями
Сетевые утилиты:
  • urlparse - разбор URL на компоненты (scheme, netloc, path)
  • threading - примитивы синхронизации для thread-safe операций

Глобальное состояние и инициализация:
Python:
# Глобальная переменная для логгера (инициализируется после создания QApplication)
logger = None

def setup_logging():
    """Настройка логирования только после инициализации Qt."""
    global logger
    if logger is not None:
        return logger
Проблема инициализации логгера:
PyQt6 модифицирует системные обработчики при создании QApplication:
  1. Кодировка консоли - Qt устанавливает UTF-8 для вывода
  2. Exception hooks - Qt перехватывает необработанные исключения
  3. Signal handlers - Qt устанавливает обработчики SIGINT/SIGTERM
Инициализация логгера до QApplication может привести к:
  • Потере Unicode символов в логах
  • Некорректной обработке исключений в worker потоках
  • Блокировке при закрытии приложения

Продвинутая настройка логгирования:
Python:
def setup_logging():
    global logger
    if logger is not None:
        return logger
      
    log_formatter = logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
  
    try:
        # Создаем папку для логов
        log_dir = Path("logs")
        log_dir.mkdir(exist_ok=True)
      
        # Файловый логгер
        file_handler = logging.FileHandler(
            log_dir / f"gads_clicker_{datetime.now().strftime('%Y%m%d')}.log",
            encoding='utf-8'
        )
        file_handler.setFormatter(log_formatter)
        file_handler.setLevel(logging.DEBUG)
      
        # Консольный логгер
        console_handler = logging.StreamHandler()
        console_handler.setFormatter(log_formatter)
        console_handler.setLevel(logging.INFO)
      
        # Основной логгер
        logger = logging.getLogger("GADS_Clicker")
        logger.setLevel(logging.DEBUG)
        logger.addHandler(file_handler)
        logger.addHandler(console_handler)
Техническая архитектура логгирования:
  1. Дублированный вывод - одновременно в файл (DEBUG) и консоль (INFO)
  2. Ротация по дням - новый файл каждый день автоматически
  3. UTF-8 encoding - поддержка Unicode в логах
  4. Иерархические уровни - DEBUG для разработки, INFO для пользователя
  5. Structured formatting - timestamp, logger name, level, message

Fallback механизм логгирования:
Python:
except Exception as e:
        # Базовый логгер если не удалось создать файловый
        logger = logging.getLogger("GADS_Clicker")
        logger.setLevel(logging.INFO)
        handler = logging.StreamHandler()
        handler.setFormatter(log_formatter)
        logger.addHandler(handler)
      
    return logger
Отказоустойчивость - если создание файлового логгера не удалось (недостаточно прав, заполнен диск), создается минимальный консольный логгер для обеспечения работоспособности.

Конфигурационные константы:
Python:
class Config:
    LOGO_PATH = "logo.png"
    SETTINGS_FILE = "settings.json"
    DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
    PROXY_TIMEOUT = 10
    PAGE_LOAD_TIMEOUT = 30
    MAX_THREADS = 50
    MIN_DELAY = 0.5
    MAX_DELAY = 3.0
Обоснование значений:
  • PROXY_TIMEOUT = 10 - баланс между скоростью валидации и ложными отказами
  • PAGE_LOAD_TIMEOUT = 30 - максимальное время загрузки страницы через медленные прокси
  • MAX_THREADS = 50 - безопасный лимит для большинства систем без exhaustion ресурсов
  • DEFAULT_USER_AGENT - актуальная строка Chrome 120, распространенная и незаметная

Архитектурные принципы:
Python:
# Глобальная переменная для логгера (инициализируется после создания QApplication)
logger = None

def setup_logging():
    """Настройка логирования только после инициализации Qt."""
    global logger
    if logger is not None:
        return logger
Почему после инициализации Qt? PyQt6 изменяет системные настройки кодировки и обработки исключений. Инициализация логгера до создания QApplication может привести к некорректной работе с Unicode и потере сообщений об ошибках.

Конфигурационный класс:
Python:
class Config:
    LOGO_PATH = "logo.png"
    SETTINGS_FILE = "settings.json"
    DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36..."
    PROXY_TIMEOUT = 10
    PAGE_LOAD_TIMEOUT = 30
    MAX_THREADS = 50
    MIN_DELAY = 0.5
    MAX_DELAY = 3.0
Централизованная конфигурация обеспечивает единую точку управления параметрами производительности и предотвращает магические числа в коде. Значения подобраны на основе анализа производительности Chrome WebDriver и сетевых таймаутов.

Система сигналов:
Python:
class Signals(QObject):
    """Централизованная система сигналов для межпоточного взаимодействия."""
    log_signal = pyqtSignal(str, str)  # message, level
    progress_signal = pyqtSignal(int)
    status_signal = pyqtSignal(str)
    task_completed = pyqtSignal(int, bool)  # task_id, success
    validation_completed = pyqtSignal(list)  # valid_proxies
Зачем централизовать сигналы? Qt требует thread-safe коммуникации между GUI и worker потоками. Централизованная система предотвращает циклические зависимости и обеспечивает безопасность сигналов.

ProxyValidator - высокопроизводительный валидатор
Python:
class ProxyValidator(QThread):
    """Высокопроизводительный валидатор прокси с асинхронной обработкой."""
  
    def __init__(self, proxies: List[str], signals: Signals):
        super().__init__()
        self.proxies = proxies
        self.signals = signals
        self.valid_proxies = []
        self.stop_requested = False
Наследование от QThread позволяет выполнять валидацию в отдельном потоке, не блокируя GUI. Флаг stop_requested обеспечивает graceful shutdown без принудительного завершения потока.

Асинхронная валидация одного прокси:
Python:
async def validate_proxy(self, session: aiohttp.ClientSession, proxy: str) -> tuple:
    """Валидация одного прокси с измерением латентности."""
    try:
        proxy_url = f"http://{proxy}"
        start_time = time.time()
      
        async with session.get(
            "https://httpbin.org/ip",
            proxy=proxy_url,
            timeout=aiohttp.ClientTimeout(total=Config.PROXY_TIMEOUT)
        ) as response:
            latency = time.time() - start_time
            if response.status == 200:
                data = await response.json()
                return proxy, True, latency, data.get('origin', 'Unknown')
              
    except Exception as e:
        if logger:
            logger.debug(f"Прокси {proxy} недоступен: {e}")
      
    return proxy, False, 0, None
Ключевые особенности:
  • httpbin.org/ip - стандартный сервис для проверки IP, возвращает JSON с реальным IP
  • Измерение латентности - критично для сортировки прокси по скорости
  • Обработка исключений - любая ошибка означает неработающий прокси
  • Возврат кортежа - структурированные данные для дальнейшей обработки

Массовая валидация с контролем нагрузки:
Python:
async def validate_all_proxies(self):
    """Асинхронная валидация всех прокси."""
    connector = aiohttp.TCPConnector(limit=50, limit_per_host=10)
    timeout = aiohttp.ClientTimeout(total=Config.PROXY_TIMEOUT)
  
    async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
        tasks = [self.validate_proxy(session, proxy) for proxy in self.proxies]
        completed = 0
      
        for coro in asyncio.as_completed(tasks):
            if self.stop_requested:
                break
              
            proxy, is_valid, latency, ip = await coro
            completed += 1
          
            if is_valid:
                self.valid_proxies.append({
                    'proxy': proxy,
                    'latency': latency,
                    'ip': ip
                })
                if logger:
                    logger.info(f"✓ Прокси {proxy} валиден (латентность: {latency:.2f}с)")
          
            progress = int((completed / len(self.proxies)) * 100)
            self.signals.progress_signal.emit(progress)
          
    self.signals.validation_completed.emit(self.valid_proxies)
Технические решения:
  • TCPConnector(limit=50, limit_per_host=10) - ограничение соединений предотвращает DDoS на httpbin и превышение лимитов ОС
  • asyncio.as_completed() - обработка результатов по мере готовности, а не ожидание всех
  • Progress tracking - real-time обновление прогресса через Qt signals

Интеграция с Qt event loop:
Python:
def run(self):
    """Запуск валидации в отдельном потоке."""
    try:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        loop.run_until_complete(self.validate_all_proxies())
    except Exception as e:
        if logger:
            logger.error(f"Ошибка валидации прокси: {e}")
    finally:
        try:
            loop.close()
        except:
            pass
Зачем новый event loop? QThread работает в отдельном OS потоке, где нет asyncio event loop. Создание нового loop'а изолирует асинхронную логику от главного потока Qt.

Архитектура эмулятора:
Python:
class BrowserEmulator:
    """Профессиональный эмулятор браузера с реалистичным поведением."""
  
    def __init__(self, task_id: int, url: str, proxies: List[Dict],
                 user_agents: List[str], signals: Signals):
        self.task_id = task_id
        self.url = url
        self.proxies = proxies
        self.user_agents = user_agents
        self.signals = signals
        self.driver = None
Каждый экземпляр BrowserEmulator - это изолированная задача с собственным WebDriver. Task_id обеспечивает трассировку выполнения в многопоточной среде.

Настройка Chrome WebDriver с оптимизацией производительности:
Python:
def setup_driver(self) -> webdriver.Chrome:
    """Настройка драйвера Chrome с оптимизированными параметрами."""
    try:
        chrome_options = Options()
      
        # Базовые настройки производительности
        chrome_options.add_argument("--headless=new")
        chrome_options.add_argument("--no-sandbox")
        chrome_options.add_argument("--disable-dev-shm-usage")
        chrome_options.add_argument("--disable-gpu")
        chrome_options.add_argument("--disable-features=VizDisplayCompositor")
        chrome_options.add_argument("--disable-extensions")
        chrome_options.add_argument("--disable-plugins")
        chrome_options.add_argument("--disable-images")
        chrome_options.add_argument("--disable-javascript")
        chrome_options.add_argument("--disable-css")
Разбор флагов оптимизации:
  • --headless=new - новый движок headless режима Chrome 120+, на 30% быстрее legacy
  • --no-sandbox - отключение sandbox для Docker/CI сред, где он недоступен
  • --disable-dev-shm-usage - использование /tmp вместо /dev/shm для избежания ошибок памяти
  • --disable-gpu - отключение GPU ускорения для headless (экономия ресурсов)
  • --disable-features=VizDisplayCompositor - отключение композитора отображения
  • --disable-images - критично для экономии трафика при работе через прокси
  • --disable-javascript - радикальная оптимизация, подходит для простых задач кликинга
  • --disable-css - дополнительное ускорение загрузки страниц

Продвинутая настройка прокси и User-Agent:
Python:
# User-Agent
        user_agent = random.choice(self.user_agents) if self.user_agents else Config.DEFAULT_USER_AGENT
        chrome_options.add_argument(f"--user-agent={user_agent}")
      
        # Прокси
        if self.proxies:
            proxy_info = random.choice(self.proxies)
            proxy_address = proxy_info['proxy']
            chrome_options.add_argument(f"--proxy-server=http://{proxy_address}")
            self.log(f"Использую прокси: {proxy_address} (латентность: {proxy_info['latency']:.2f}с)")
      
        # Создание драйвера
        service = Service()
        service.log_path = os.devnull  # Отключаем логи драйвера
        driver = webdriver.Chrome(service=service, options=chrome_options)
        driver.set_page_load_timeout(Config.PAGE_LOAD_TIMEOUT)
Стратегия выбора прокси: Случайный выбор из предварительно валидированных прокси обеспечивает распределение нагрузки и снижает вероятность блокировки. Логирование латентности позволяет отслеживать производительность.

Алгоритм симуляции человеческого поведения:
Python:
def simulate_human_behavior(self, driver: webdriver.Chrome):
    """Симуляция реалистичного поведения человека."""
    try:
        actions = ActionChains(driver)
      
        # Случайная задержка перед началом
        time.sleep(random.uniform(1, 3))
      
        # Получение размеров окна
        try:
            window_size = driver.get_window_size()
            max_x, max_y = window_size['width'], window_size['height']
        except:
            max_x, max_y = 1920, 1080  # Значения по умолчанию
      
        # Симуляция прокрутки
        scroll_attempts = random.randint(2, 5)
        for _ in range(scroll_attempts):
            scroll_y = random.randint(200, 800)
            driver.execute_script(f"window.scrollBy(0, {scroll_y});")
            time.sleep(random.uniform(0.5, 2.0))
Поведенческие паттерны:
  • Инициальная задержка - имитация времени на "осмысление" страницы человеком
  • Динамическое получение размеров окна - адаптация к различным разрешениям
  • Переменная прокрутка - случайные значения предотвращают детекцию ботов
  • Реалистичные таймауты - основаны на исследованиях поведения пользователей

Продвинутая симуляция мыши и взаимодействий:
Python:
# Симуляция движения мыши
        for _ in range(random.randint(3, 7)):
            try:
                x = random.randint(0, max(0, max_x - 100))
                y = random.randint(0, max(0, max_y - 100))
                actions.move_by_offset(x, y).perform()
                time.sleep(random.uniform(0.1, 0.5))
                actions.reset_actions()
            except:
                break
      
        # Попытка взаимодействия с элементами
        try:
            wait = WebDriverWait(driver, 5)
            clickable_elements = wait.until(
                EC.presence_of_all_elements_located((By.CSS_SELECTOR, "a, button"))
            )
          
            if clickable_elements:
                element = random.choice(clickable_elements[:5])
                if element.is_displayed() and element.is_enabled():
                    actions.move_to_element(element).pause(random.uniform(0.5, 1.5)).click().perform()
                    time.sleep(random.uniform(1, 3))
Технические особенности симуляции:
  • actions.reset_actions() - предотвращение накопления команд в очереди ActionChains
  • Проверка границ экрана - max(0, max_x - 100) избегает выхода курсора за пределы
  • WebDriverWait с EC - современный подход вместо устаревших time.sleep()
  • Селектор "a, button" - поиск интерактивных элементов для клика
  • [:5] - ограничение выбора первыми 5 элементами для предсказуемости
  • is_displayed() and is_enabled() - двойная проверка кликабельности элемента

Основной цикл выполнения задачи:
Python:
def execute_task(self) -> bool:
    """Выполнение основной задачи эмуляции."""
    success = False
  
    try:
        self.log("Запуск задачи")
      
        # Валидация URL
        parsed_url = urlparse(self.url)
        if not parsed_url.scheme or not parsed_url.netloc:
            raise ValueError(f"Некорректный URL: {self.url}")
      
        # Создание драйвера
        self.driver = self.setup_driver()
      
        # Переход на страницу
        self.log(f"Переход на: {self.url}")
        start_time = time.time()
        self.driver.get(self.url)
        load_time = time.time() - start_time
      
        self.log(f"Страница загружена за {load_time:.2f}с")
      
        # Симуляция поведения
        self.simulate_human_behavior(self.driver)
      
        self.log("Задача успешно завершена")
        success = True
      
    except Exception as e:
        self.log(f"Ошибка выполнения: {e}", "ERROR")
      
    finally:
        if self.driver:
            try:
                self.driver.quit()
            except:
                pass
      
        self.signals.task_completed.emit(self.task_id, success)
      
    return success
Критические аспекты:
  • urlparse() валидация - предотвращение invalid URL exceptions до создания драйвера
  • Измерение времени загрузки - метрика для оценки производительности прокси
  • Обязательный driver.quit() - освобождение ресурсов Chrome процесса
  • Signal emission в finally - гарантированное уведомление о завершении
  • Exception handling - логирование всех ошибок без прерывания других задач
MainWindow - архитектура GUI
Python:
class MainWindow(QMainWindow):
    """Главное окно приложения с профессиональным интерфейсом."""
  
    def __init__(self):
        super().__init__()
        self.signals = Signals()
        self.executor = None
        self.proxy_validator = None
        self.settings_file = Path(Config.SETTINGS_FILE)
        self.valid_proxies = []
        self.running_tasks = 0
        self.completed_tasks = 0
        self.successful_tasks = 0
Архитектурные решения:
  • Композиция signals - централизованная система событий
  • ThreadPoolExecutor - управление пулом worker потоков
  • Path объект - современный подход к файловым операциям
  • Счетчики состояния - real-time мониторинг выполнения

Система профессиональной темной стилизации:
Python:
def apply_modern_theme(self):
    """Применение современной темной темы."""
    self.setStyleSheet("""
        /* Основные цвета */
        QMainWindow, QWidget {
            background-color: #2b2b2b;
            color: #ffffff;
            font-family: 'Segoe UI', 'Ubuntu', 'Arial', sans-serif;
            font-size: 14px;
        }
      
        /* Группы */
        QGroupBox {
            font-weight: bold;
            border: 2px solid #555555;
            border-radius: 8px;
            margin-top: 12px;
            padding-top: 10px;
            background-color: #363636;
        }
      
        QGroupBox::title {
            subcontrol-origin: margin;
            left: 15px;
            padding: 0 8px 0 8px;
            color: #4CAF50;
            font-size: 16px;
        }
Цветовая схема и типография подобрана для максимального удобства пользователю (данные из интернета).

Продвинутая стилизация элементов ввода:
Python:
/* Поля ввода */
        QLineEdit {
            background-color: #404040;
            border: 2px solid #555555;
            border-radius: 6px;
            padding: 8px 12px;
            color: #ffffff;
            font-size: 14px;
            min-height: 20px;
        }
      
        QLineEdit:focus {
            border-color: #4CAF50;
            background-color: #454545;
        }
      
        QLineEdit::placeholder {
            color: #888888;
        }

Кастомизация SpinBox с векторными стрелками и градиентный прогресс-бар:
Python:
/* SpinBox с красивыми кнопками */
        QSpinBox::up-button, QSpinBox::down-button {
            background-color: #2196F3;
            border: none;
            border-radius: 3px;
            width: 20px;
            height: 15px;
            margin: 1px;
        }
      
        QSpinBox::up-arrow {
            image: none;
            border-left: 4px solid transparent;
            border-right: 4px solid transparent;
            border-bottom: 6px solid white;
            width: 0;
            height: 0;
            margin: auto;
        }
      
        QSpinBox::down-arrow {
            image: none;
            border-left: 4px solid transparent;
            border-right: 4px solid transparent;
            border-top: 6px solid white;
            width: 0;
            height: 0;
            margin: auto;
        }
Python:
    /* Прогресс-бар */
        QProgressBar::chunk {
            background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0,
                stop:0 #4CAF50, stop:1 #45a049);
            border-radius: 4px;
        }

Responsive layout система:
Python:
def setup_ui(self):
    """Создание профессионального пользовательского интерфейса."""
    self.setWindowTitle("GADS Clicker v2.0")
    self.setGeometry(100, 100, 1200, 800)
    self.setMinimumSize(1000, 700)
  
    # Основной layout
    main_layout = QVBoxLayout(central_widget)
    main_layout.setContentsMargins(20, 20, 20, 20)
    main_layout.setSpacing(15)
  
    # Основной контент в splitter
    splitter = QSplitter(Qt.Orientation.Horizontal)
  
    # Левая панель - настройки
    settings_widget = self.create_settings_panel()
    splitter.addWidget(settings_widget)
  
    # Правая панель - лог и прогресс
    log_widget = self.create_log_panel()
    splitter.addWidget(log_widget)
  
    # Пропорции панелей
    splitter.setSizes([400, 600])
QSplitter преимущества:
  • Пользовательское изменение размеров - drag&drop разделителя
  • setSizes([400, 600]) - initial пропорции 40%/60%
  • Автоматическое сохранение состояния при ресайзе окна

Динамическая загрузка логотипа:
Python:
def create_logo_section(self, layout):
    """Создание секции с логотипом."""
    logo_label = QLabel()
    logo_label.setObjectName("logoLabel")
    logo_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
    logo_label.setFixedHeight(80)
  
    if os.path.exists(Config.LOGO_PATH):
        try:
            pixmap = QPixmap(Config.LOGO_PATH)
            if not pixmap.isNull():
                scaled_pixmap = pixmap.scaled(
                    500, 135,
                    Qt.AspectRatioMode.KeepAspectRatio,
                    Qt.TransformationMode.SmoothTransformation
                )
                logo_label.setPixmap(scaled_pixmap)
            else:
                logo_label.setText("GADS Clicker")
                logo_label.setObjectName("titleLabel")
        except Exception as e:
            if logger:
                logger.warning(f"Не удалось загрузить логотип: {e}")
            logo_label.setText("GADS Clicker")
            logo_label.setObjectName("titleLabel")
    else:
        logo_label.setText("GADS Clicker")
        logo_label.setObjectName("titleLabel")
Fallback стратегия:
  • os.path.exists() - проверка существования файла
  • pixmap.isNull() - проверка валидности изображения
  • KeepAspectRatio - сохранение пропорций при масштабировании
  • SmoothTransformation - высококачественное масштабирование
  • Multiple fallbacks - text label если изображение недоступно

Система цветового кодирования логов:
Python:
def add_log_entry(self, message: str, level: str = "INFO"):
    """Добавление записи в лог с цветовым кодированием."""
    timestamp = datetime.now().strftime("%H:%M:%S")
  
    # Цветовое кодирование по уровню
    color_map = {
        "DEBUG": "#888888",
        "INFO": "#ffffff",
        "SUCCESS": "#4CAF50",
        "WARNING": "#FF9800",
        "ERROR": "#f44336"
    }
  
    color = color_map.get(level, "#ffffff")
    formatted_message = f'<span style="color: {color};">[{timestamp}] {message}</span>'
  
    self.log_area.append(formatted_message)
    self.log_area.ensureCursorVisible()
  
    # Ограничение количества строк в логе
    if self.log_area.document().blockCount() > 1000:
        cursor = self.log_area.textCursor()
        cursor.movePosition(cursor.MoveOperation.Start)
        cursor.movePosition(cursor.MoveOperation.Down, cursor.MoveMode.KeepAnchor, 100)
        cursor.removeSelectedText()

Алгоритм динамического расчета потоков:
Python:
def calculate_optimal_threads(self) -> int:
    """Умный расчет оптимального количества потоков."""
    cpu_count = multiprocessing.cpu_count()
    proxy_count = len(self.valid_proxies)
    task_count = self.task_count.value()
  
    # Базовая формула расчета потоков
    if proxy_count > 0:
        # С прокси: учитываем количество прокси, CPU и задач
        base_threads = min(proxy_count, cpu_count * 1.5)
      
        # Корректировка на основе количества задач
        if task_count <= 10:
            optimal = max(1, int(base_threads * 0.5))
        elif task_count <= 50:
            optimal = int(base_threads * 0.8)
        elif task_count <= 200:
            optimal = int(base_threads)
        else:
            optimal = int(base_threads * 1.2)
          
        # Ограничения
        optimal = max(1, min(optimal, proxy_count, Config.MAX_THREADS))
      
    else:
        # Без прокси: консервативный подход
        if task_count <= 5:
            optimal = 1
        elif task_count <= 20:
            optimal = min(2, cpu_count)
        else:
            optimal = min(3, cpu_count)
  
    return optimal
Математическая модель оптимизации:
  1. С прокси-серверами:
    • base_threads = min(proxy_count, cpu_count * 1.5) - гибридная формула учитывающая I/O bound (прокси) и CPU bound (Chrome процессы)
    • cpu_count * 1.5 - оптимальное соотношение для I/O intensive задач
    • Масштабирующие коэффициенты: 0.5→0.8→1.0→1.2 в зависимости от объема задач
  2. Без прокси:
    • Консервативный подход - избежание перегрузки сети и целевого сервера
    • Максимум 3 потока - предотвращение DDoS-подобного поведения

Система автоматических рекомендаций:
Python:
def update_recommended_threads(self):
    """Обновление рекомендуемого количества потоков."""
    optimal = self.calculate_optimal_threads()
    cpu_count = multiprocessing.cpu_count()
    proxy_count = len(self.valid_proxies)
  
    # Формируем информативное сообщение
    if proxy_count > 0:
        recommendation_text = (
            f"Рекомендуется: {optimal} потоков\n"
            f"(CPU: {cpu_count}, Прокси: {proxy_count}, Задач: {self.task_count.value()})"
        )
    else:
        recommendation_text = (
            f"Рекомендуется: {optimal} поток(ов)\n"
            f"(без прокси, CPU: {cpu_count}, Задач: {self.task_count.value()})"
        )
      
    self.recommended_label.setText(recommendation_text)
  
    # Автоматическая установка оптимального значения
    self.thread_count.setValue(optimal)
Адаптивная система автоматически пересчитывает оптимальные настройки при изменении любого параметра (количество прокси, задач, CPU).

Продвинутое управление выполнением задач:
Python:
def start_emulation(self):
    """Запуск эмуляции браузера."""
    # Валидация входных данных
    url = self.url_input.text().strip()
    if not url:
        QMessageBox.warning(self, "Ошибка", "Введите целевой URL!")
        return
      
    # Проверка корректности URL
    try:
        parsed = urlparse(url)
        if not parsed.scheme or not parsed.netloc:
            raise ValueError("Некорректный URL")
    except Exception:
        QMessageBox.warning(self, "Ошибка", "Введите корректный URL (например: https://example.com)")
        return

    thread_count = self.thread_count.value()
    task_count = self.task_count.value()
  
    # Загрузка User-Agent'ов
    user_agents = []
    ua_file = self.ua_file_input.text().strip()
    if ua_file and os.path.exists(ua_file):
        user_agents = self.load_file_content(ua_file)
      
    if not user_agents:
        self.add_log_entry("User-Agent файл не загружен, используется стандартный", "WARNING")

    # Проверка прокси
    if not self.valid_proxies:
        reply = QMessageBox.question(
            self,
            "Внимание",
            "Рабочие прокси не найдены. Продолжить без прокси?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.No:
            return
Многоуровневая валидация:
  • URL parsing - urlparse() проверяет синтаксис URL
  • scheme/netloc validation - обязательные компоненты URL
  • File existence checks - проверка User-Agent файлов
  • Interactive confirmations - пользователь осознанно принимает решения

ThreadPoolExecutor с оптимизированным запуском:
Python:
# Инициализация состояния
    self.running_tasks = task_count
    self.completed_tasks = 0
    self.successful_tasks = 0
  
    # Обновление интерфейса
    self.start_button.setEnabled(False)
    self.stop_button.setEnabled(True)
    self.progress_bar.setValue(0)
    self.progress_label.setText(f"Запуск {task_count} задач в {thread_count} потоках...")
  
    # Сохранение настроек
    self.save_settings()
  
    # Создание пула потоков
    self.executor = ThreadPoolExecutor(max_workers=thread_count, thread_name_prefix="BrowserEmulator")
  
    # Запуск задач
    self.add_log_entry(f"🚀 Запуск {task_count} задач в {thread_count} потоках", "SUCCESS")
    self.add_log_entry(f"Целевой URL: {url}", "INFO")
    self.add_log_entry(f"Прокси: {len(self.valid_proxies)} активных", "INFO")
    self.add_log_entry(f"User-Agents: {len(user_agents)} загружено", "INFO")
  
    for i in range(task_count):
        emulator = BrowserEmulator(
            task_id=i + 1,
            url=url,
            proxies=self.valid_proxies,
            user_agents=user_agents,
            signals=self.signals
        )
        self.executor.submit(emulator.execute_task)
        time.sleep(0.01)  # Небольшая задержка между запусками
Ключевые особенности запуска:
  • Атомарное изменение состояния UI - предотвращение race conditions
  • thread_name_prefix - именованные потоки для отладки
  • time.sleep(0.01) - стaggered startup предотвращает одновременную инициализацию Chrome
  • Детальное логирование - полная трассировка параметров запуска

Мониторинг выполнения в реальном времени:
Python:
def on_task_completed(self, task_id: int, success: bool):
    """Обработка завершения задачи."""
    self.completed_tasks += 1
    if success:
        self.successful_tasks += 1
      
    progress = int((self.completed_tasks / self.running_tasks) * 100)
    self.progress_bar.setValue(progress)
  
    self.progress_label.setText(
        f"Выполнено: {self.completed_tasks}/{self.running_tasks} "
        f"(успешно: {self.successful_tasks})"
    )
  
    # Проверка завершения всех задач
    if self.completed_tasks >= self.running_tasks:
        self.on_all_tasks_completed()

def on_all_tasks_completed(self):
    """Обработка завершения всех задач."""
    self.start_button.setEnabled(True)
    self.stop_button.setEnabled(False)
  
    success_rate = (self.successful_tasks / self.running_tasks) * 100 if self.running_tasks > 0 else 0
  
    self.add_log_entry(
        f"Все задачи завершены! Успешность: {success_rate:.1f}% "
        f"({self.successful_tasks}/{self.running_tasks})",
        "SUCCESS"
    )
  
    self.status_bar.showMessage(
        f"Завершено: {self.successful_tasks}/{self.running_tasks} задач успешно"
    )
Real-time аналитика:
  • Thread-safe счетчики - Qt signals обеспечивают безопасность
  • Percentage calculation - динамический прогресс-бар
  • Success rate - метрика эффективности выполнения
  • Автоматическое восстановление UI - готовность к новому запуску

Система персистентности настроек:
Python:
def save_settings(self):
    """Сохранение настроек в файл."""
    settings = {
        "url": self.url_input.text(),
        "proxy_file": self.proxy_file_input.text(),
        "ua_file": self.ua_file_input.text(),
        "threads": self.thread_count.value(),
        "tasks": self.task_count.value(),
        "last_save": datetime.now().isoformat()
    }
  
    try:
        with open(self.settings_file, 'w', encoding='utf-8') as f:
            json.dump(settings, f, indent=4, ensure_ascii=False)
    except Exception as e:
        self.add_log_entry(f"Ошибка сохранения настроек: {e}", "ERROR")

def load_settings(self):
    """Загрузка настроек из файла."""
    if not self.settings_file.exists():
        return
      
    try:
        with open(self.settings_file, 'r', encoding='utf-8') as f:
            settings = json.load(f)
          
        self.url_input.setText(settings.get("url", ""))
        self.proxy_file_input.setText(settings.get("proxy_file", ""))
        self.ua_file_input.setText(settings.get("ua_file", ""))
        self.thread_count.setValue(settings.get("threads", 1))
        self.task_count.setValue(settings.get("tasks", 10))
      
        self.add_log_entry("Настройки загружены", "INFO")
      
    except Exception as e:
        self.add_log_entry(f"Ошибка загрузки настроек: {e}", "ERROR")
JSON персистентность:
  • ensure_ascii=False - поддержка Unicode в настройках
  • indent=4 - читаемое форматирование для ручного редактирования
  • isoformat() - стандартный формат времени
  • settings.get() с defaults - безопасное чтение с fallback значениями

Graceful shutdown система:
Python:
def closeEvent(self, event):
    """Обработка закрытия приложения."""
    # Остановка всех активных процессов
    if self.executor:
        self.executor.shutdown(wait=True)
      
    if self.proxy_validator and self.proxy_validator.isRunning():
        self.proxy_validator.stop()
        self.proxy_validator.wait()
      
    # Сохранение настроек
    self.save_settings()
  
    # Закрытие окна
    event.accept()

def stop_emulation(self):
    """Остановка эмуляции."""
    if self.executor:
        self.add_log_entry("⏹ Остановка выполнения...", "WARNING")
        self.executor.shutdown(wait=False)
        self.executor = None
      
    self.start_button.setEnabled(True)
    self.stop_button.setEnabled(False)
    self.progress_bar.setValue(0)
    self.progress_label.setText("Выполнение остановлено")
    self.status_bar.showMessage("Остановлено пользователем")
Техники безопасного завершения:
  • shutdown(wait=True) - ожидание завершения активных задач при закрытии
  • shutdown(wait=False) - немедленная остановка при пользовательском запросе
  • validator.stop() + wait() - корректное завершение QThread
  • event.accept() - подтверждение закрытия окна

Статический класс для файловых диалогов:
Python:
class ModernFileDialog:
    """Улучшенные диалоги выбора файлов."""
  
    @staticmethod
    def get_proxy_file(parent) -> Optional[str]:
        file_path, _ = QFileDialog.getOpenFileName(
            parent,
            "Выберите файл со списком прокси",
            "",
            "Текстовые файлы (*.txt);;Все файлы (*.*)"
        )
        return file_path if file_path else None
  
    @staticmethod
    def get_user_agent_file(parent) -> Optional[str]:
        file_path, _ = QFileDialog.getOpenFileName(
            parent,
            "Выберите файл с User-Agent строками",
            "",
            "Текстовые файлы (*.txt);;Все файлы (*.*)"
        )
        return file_path if file_path else None
Архитектурные решения:
  • Статические методы - не требуют создания экземпляра класса
  • Optional[str] возврат - явное указание nullable результата
  • QFileDialog.getOpenFileName() - нативный диалог ОС (Windows Explorer, macOS Finder, Linux file picker)
  • Фильтры файлов - "*.txt" ограничивает выбор текстовыми файлами
  • Fallback "Все файлы" - возможность выбрать любой файл при необходимости
  • Возврат кортежа - getOpenFileName возвращает (file_path, selected_filter), используется только путь

Универсальная загрузка файлового контента:
Python:
def load_file_content(self, file_path: str) -> List[str]:
    """Загрузка содержимого файла."""
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            content = [line.strip() for line in file if line.strip()]
        if logger:
            logger.info(f"Загружено {len(content)} строк из {file_path}")
        return content
    except Exception as e:
        self.add_log_entry(f"Ошибка загрузки файла {file_path}: {e}", "ERROR")
        return []
Техническая реализация:
  • encoding='utf-8' - гарантия корректной работы с Unicode символами
  • List comprehension - эффективная фильтрация и обработка строк
  • line.strip() - удаление пробелов и переносов строк
  • if line.strip() - исключение пустых строк из результата
  • Exception handling - логирование ошибок без прерывания работы
  • Пустой список fallback - безопасное поведение при ошибках

Автоматическая валидация при изменении файла:
Python:
def on_proxy_file_changed(self):
    """Обработка изменения файла прокси."""
    file_path = self.proxy_file_input.text()
    if os.path.exists(file_path):
        # Автоматическая валидация прокси при изменении файла
        QTimer.singleShot(500, self.validate_proxies)
    else:
        self.proxy_status_label.setText("Файл прокси не найден")
        self.valid_proxies = []
        self.update_recommended_threads()
Реактивная архитектура:
  • QTimer.singleShot(500) - debounce механизм с задержкой 500ms
  • Автоматический запуск валидации - пользователь не должен вручную проверять прокси
  • Немедленная очистка состояния - при неверном файле сразу сбрасываются валидные прокси
  • Каскадное обновление - изменение прокси автоматически пересчитывает оптимальные потоки

Система событий и соединений:
Python:
def setup_connections(self):
    """Настройка сигналов и слотов."""
    # Кнопки
    self.start_button.clicked.connect(self.start_emulation)
    self.stop_button.clicked.connect(self.stop_emulation)
    self.proxy_browse_button.clicked.connect(self.browse_proxy_file)
    self.ua_browse_button.clicked.connect(self.browse_ua_file)
  
    # Изменения в полях
    self.proxy_file_input.textChanged.connect(self.on_proxy_file_changed)
    self.thread_count.valueChanged.connect(self.update_recommended_threads)
    self.task_count.valueChanged.connect(self.update_recommended_threads)
  
    # Сигналы
    self.signals.log_signal.connect(self.add_log_entry)
    self.signals.progress_signal.connect(self.update_progress)
    self.signals.status_signal.connect(self.update_status)
    self.signals.task_completed.connect(self.on_task_completed)
    self.signals.validation_completed.connect(self.on_proxy_validation_completed)
Событийная архитектура:
  • clicked.connect() - привязка обработчиков к кликам кнопок
  • textChanged.connect() - реактивные обновления при вводе текста
  • valueChanged.connect() - автоматический пересчет при изменении значений SpinBox
  • Кастомные сигналы - connect межпоточных уведомлений к GUI обновлениям

Обработчики файловых диалогов:
Python:
def browse_proxy_file(self):
    """Выбор файла прокси."""
    file_path = ModernFileDialog.get_proxy_file(self)
    if file_path:
        self.proxy_file_input.setText(file_path)

def browse_ua_file(self):
    """Выбор файла User-Agent."""
    file_path = ModernFileDialog.get_user_agent_file(self)
    if file_path:
        self.ua_file_input.setText(file_path)
Простота и элегантность:
  • Единственная ответственность - только установка пути в поле ввода
  • Автоматический cascade - setText() триггерит textChanged сигнал
  • None-safe поведение - проверка file_path предотвращает установку None

Создание панели настроек:
Python:
def create_settings_panel(self) -> QWidget:
    """Создание панели настроек."""
    settings_widget = QWidget()
    settings_layout = QVBoxLayout(settings_widget)
    settings_layout.setSpacing(15)
  
    # URL настройки
    url_group = QGroupBox("Целевой URL")
    url_layout = QVBoxLayout(url_group)
  
    self.url_input = QLineEdit()
    self.url_input.setPlaceholderText("https://example.com")
    url_layout.addWidget(self.url_input)
  
    settings_layout.addWidget(url_group)
Иерархическая организация:
  • QGroupBox - визуальное группирование связанных элементов
  • QVBoxLayout - вертикальное расположение элементов
  • setSpacing(15) - равномерные отступы между группами
  • setPlaceholderText() - подсказки для пользователя

Продвинутая компоновка файловых селекторов:
Python:
# Прокси настройки
    proxy_group = QGroupBox("Настройки прокси")
    proxy_layout = QVBoxLayout(proxy_group)
  
    proxy_file_layout = QHBoxLayout()
    self.proxy_file_input = QLineEdit()
    self.proxy_file_input.setPlaceholderText("Выберите файл с прокси...")
    self.proxy_browse_button = QPushButton("Обзор")
    self.proxy_browse_button.setObjectName("browseButton")
  
    proxy_file_layout.addWidget(self.proxy_file_input, 3)
    proxy_file_layout.addWidget(self.proxy_browse_button, 1)
    proxy_layout.addLayout(proxy_file_layout)
  
    self.proxy_status_label = QLabel("Прокси не загружены")
    proxy_layout.addWidget(self.proxy_status_label)
Layout пропорции:
  • QHBoxLayout - горизонтальное размещение поля ввода и кнопки
  • addWidget(widget, stretch) - пропорциональное распределение пространства
  • stretch=3 для input, stretch=1 для button - поле ввода в 3 раза шире кнопки
  • setObjectName() - CSS селектор для кастомной стилизации
  • Status label - динамическая индикация состояния валидации прокси

Интеллектуальная панель выполнения:
Python:
# Настройки выполнения
    execution_group = QGroupBox("Параметры выполнения")
    execution_layout = QVBoxLayout(execution_group)
  
    thread_layout = QHBoxLayout()
    thread_layout.addWidget(QLabel("Потоки:"))
    self.thread_count = QSpinBox()
    self.thread_count.setRange(1, Config.MAX_THREADS)
    self.thread_count.setValue(1)
    self.thread_count.setButtonSymbols(QSpinBox.ButtonSymbols.UpDownArrows)
    thread_layout.addWidget(self.thread_count)
    thread_layout.addStretch()
    execution_layout.addLayout(thread_layout)
  
    task_layout = QHBoxLayout()
    task_layout.addWidget(QLabel("Задачи:"))
    self.task_count = QSpinBox()
    self.task_count.setRange(1, 10000)
    self.task_count.setValue(10)
    self.task_count.setButtonSymbols(QSpinBox.ButtonSymbols.UpDownArrows)
    task_layout.addWidget(self.task_count)
    task_layout.addStretch()
    execution_layout.addLayout(task_layout)
  
    self.recommended_label = QLabel("Расчет оптимальных потоков...")
    self.recommended_label.setWordWrap(True)
    execution_layout.addWidget(self.recommended_label)
Адаптивные элементы управления:
  • setRange(1, Config.MAX_THREADS) - безопасные границы значений
  • UpDownArrows - явное указание кнопок спиннера
  • addStretch() - выравнивание элементов по левому краю
  • setWordWrap(True) - автоматический перенос длинного текста рекомендаций
  • Динамические рекомендации - label обновляется при изменении параметров

Панель логов с прогрессом:
Python:
def create_log_panel(self) -> QWidget:
    """Создание панели логов и прогресса."""
    log_widget = QWidget()
    log_layout = QVBoxLayout(log_widget)
  
    # Прогресс
    progress_group = QGroupBox("Прогресс выполнения")
    progress_layout = QVBoxLayout(progress_group)
  
    self.progress_bar = QProgressBar()
    self.progress_bar.setValue(0)
    progress_layout.addWidget(self.progress_bar)
  
    self.progress_label = QLabel("Готов к запуску")
    progress_layout.addWidget(self.progress_label)
  
    log_layout.addWidget(progress_group)
  
    # Лог
    log_group = QGroupBox("Журнал событий")
    log_group_layout = QVBoxLayout(log_group)
  
    self.log_area = QTextEdit()
    self.log_area.setReadOnly(True)
    log_group_layout.addWidget(self.log_area)
  
    log_layout.addWidget(log_group)
  
    return log_widget
Информационная архитектура:
  • Двухуровневая структура - прогресс сверху, детальные логи снизу
  • setReadOnly(True) - предотвращение случайного редактирования логов
  • Прогресс-бар + текстовый индикатор - дублирование информации для удобства
  • QTextEdit для логов - поддержка HTML форматирования и автоскролла

Панель управления с крупными кнопками:
Python:
def create_control_panel(self, layout):
    """Создание панели управления."""
    control_layout = QHBoxLayout()
    control_layout.addStretch()
  
    self.start_button = QPushButton("ЗАПУСТИТЬ")
    self.start_button.setFixedSize(150, 50)
  
    self.stop_button = QPushButton("ОСТАНОВИТЬ")
    self.stop_button.setObjectName("stopButton")
    self.stop_button.setFixedSize(150, 50)
    self.stop_button.setEnabled(False)
  
    control_layout.addWidget(self.start_button)
    control_layout.addWidget(self.stop_button)
    control_layout.addStretch()
  
    layout.addLayout(control_layout)
UX принципы:
  • setFixedSize(150, 50) - крупные кнопки для важных действий
  • addStretch() по краям - центрирование кнопок
  • setEnabled(False) для stop - логическое управление доступностью
  • Контрастные objectName - различная стилизация для старт/стоп

Креды (а как же без них?)
Python:
def create_credits_footer(self, layout):
    """Создание футера с кредами."""
    credits_frame = QFrame()
    credits_layout = QHBoxLayout(credits_frame)
    credits_layout.setContentsMargins(10, 3, 10, 3)
  
    # Надпись с кредами
    credits_label = QLabel("© 2025 GADS Clicker by SOCIETY special for XSS.IS")
    credits_label.setObjectName("creditsLabel")
    credits_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
  
    credits_layout.addWidget(credits_label)
    layout.addWidget(credits_frame)
Элементы кредов:
  • QFrame - контейнер для визуального отделения
  • setContentsMargins(10, 3, 10, 3) - минимальные отступы для компактности
  • AlignCenter - центрированное размещение копирайта
  • creditsLabel objectName - специальная стилизация в CSS

Продвинутая система валидации прокси:
Python:
def validate_proxies(self):
    """Запуск валидации прокси."""
    file_path = self.proxy_file_input.text()
    if not os.path.exists(file_path):
        return
      
    proxies = self.load_file_content(file_path)
    if not proxies:
        self.proxy_status_label.setText("Файл прокси пуст")
        return
      
    self.proxy_status_label.setText(f"Проверка {len(proxies)} прокси...")
    self.add_log_entry(f"Начата валидация {len(proxies)} прокси", "INFO")
  
    # Остановка предыдущей валидации
    if self.proxy_validator and self.proxy_validator.isRunning():
        self.proxy_validator.stop()
        self.proxy_validator.wait()
      
    # Запуск новой валидации
    self.proxy_validator = ProxyValidator(proxies, self.signals)
    self.proxy_validator.start()
Техническая архитектура валидации:
  • Предварительные проверки - существование файла и непустота содержимого
  • Статус-индикация - пользователь видит процесс в реальном времени
  • Graceful termination - корректная остановка предыдущей валидации перед новой
  • QThread.wait() - блокирующее ожидание завершения потока
  • Новый экземпляр валидатора - избежание состояний race condition

Обработка результатов валидации:
Python:
def on_proxy_validation_completed(self, valid_proxies: List[Dict]):
    """Обработка завершения валидации прокси."""
    self.valid_proxies = valid_proxies
    count = len(valid_proxies)
  
    if count > 0:
        self.proxy_status_label.setText(f"✓ {count} рабочих прокси")
        self.add_log_entry(f"Валидация завершена: {count} рабочих прокси", "SUCCESS")
    else:
        self.proxy_status_label.setText("✗ Рабочих прокси не найдено")
        self.add_log_entry("Валидация завершена: рабочих прокси не найдено", "WARNING")
      
    self.update_recommended_threads()
Адаптивная обратная связь:
  • Unicode символы ✓/✗ - визуальная индикация результата
  • Условное логирование - SUCCESS/WARNING в зависимости от результата
  • Автоматический пересчет потоков - система сразу адаптируется к новым данным
  • Сохранение состояния - valid_proxies используется в дальнейших операциях

Инициализация оптимальных настроек:
Python:
def initialize_optimal_threads(self):
    """Инициализация оптимального количества потоков после загрузки UI."""
    self.update_recommended_threads()
    # В __init__:
QTimer.singleShot(100, self.initialize_optimal_threads)
Зачем отложенная инициализация?
  • UI готовность - все элементы интерфейса созданы и инициализированы
  • CPU detection - multiprocessing.cpu_count() может блокировать при первом вызове
  • Event loop - Qt событийный цикл полностью запущен
  • 100ms задержка - минимальная, но достаточная для завершения UI setup
Архитектура главной функции:
Python:
def main():
    """Главная функция приложения."""
    # Создание приложения
    app = QApplication(sys.argv)
    app.setApplicationName("GADS Clicker")
    app.setApplicationVersion("2.0")
    app.setOrganizationName("DevTools")
  
    # Инициализация логирования ПОСЛЕ создания QApplication
    global logger
    logger = setup_logging()
  
    # Установка иконки приложения (если есть)
    if os.path.exists("icon.ico"):
        app.setWindowIcon(QIcon("icon.ico"))
Метаданные приложения:
  • setApplicationName() - отображается в заголовке окна и системных диалогах
  • setApplicationVersion() - используется для About диалогов и системной информации
  • setOrganizationName() - группировка настроек в реестре Windows/preferences macOS
  • setWindowIcon() - иконка в таскбаре и Alt+Tab переключателе

Продвинутое управление памятью логов:
Python:
def add_log_entry(self, message: str, level: str = "INFO"):
    """Добавление записи в лог с цветовым кодированием."""
    timestamp = datetime.now().strftime("%H:%M:%S")
  
    # Цветовое кодирование по уровню
    color_map = {
        "DEBUG": "#888888",
        "INFO": "#ffffff",
        "SUCCESS": "#4CAF50",
        "WARNING": "#FF9800",
        "ERROR": "#f44336"
    }
  
    color = color_map.get(level, "#ffffff")
    formatted_message = f'<span style="color: {color};">[{timestamp}] {message}</span>'
  
    self.log_area.append(formatted_message)
    self.log_area.ensureCursorVisible()
  
    # Ограничение количества строк в логе
    if self.log_area.document().blockCount() > 1000:
        cursor = self.log_area.textCursor()
        cursor.movePosition(cursor.MoveOperation.Start)
        cursor.movePosition(cursor.MoveOperation.Down, cursor.MoveMode.KeepAnchor, 100)
        cursor.removeSelectedText()
Memory management стратегия:
  • document().blockCount() - подсчет строк в QTextDocument
  • 1000 строк лимит - баланс между историей и потреблением памяти
  • Batch deletion - удаление 100 строк за раз для производительности
  • QTextCursor manipulation - программное управление текстом
  • KeepAnchor mode - выделение текста для удаления

Прогресс-мониторинг с метриками:
Python:
def update_progress(self, value: int):
    """Обновление прогресс-бара."""
    self.progress_bar.setValue(value)

def update_status(self, message: str):
    """Обновление статусной строки."""
    self.status_bar.showMessage(message)
Централизованные обновления:
  • Single responsibility - каждый метод отвечает за один элемент UI
  • Thread-safe - вызываются через Qt signals из worker потоков
  • Consistent interface - унифицированный API для всех обновлений

Комплексная система завершения задач:
Python:
def on_task_completed(self, task_id: int, success: bool):
    """Обработка завершения задачи."""
    self.completed_tasks += 1
    if success:
        self.successful_tasks += 1
      
    progress = int((self.completed_tasks / self.running_tasks) * 100)
    self.progress_bar.setValue(progress)
  
    self.progress_label.setText(
        f"Выполнено: {self.completed_tasks}/{self.running_tasks} "
        f"(успешно: {self.successful_tasks})"
    )
  
    # Проверка завершения всех задач
    if self.completed_tasks >= self.running_tasks:
        self.on_all_tasks_completed()
Real-time аналитика:
  • Атомарные операции - инкремент счетчиков thread-safe через Qt signals
  • Percentage calculation - динамический прогресс без промежуточных состояний
  • Conditional completion - автоматическое определение завершения всех задач
  • Rich progress information - детальная статистика для пользователя

Финализация выполнения:
Python:
def on_all_tasks_completed(self):
    """Обработка завершения всех задач."""
    self.start_button.setEnabled(True)
    self.stop_button.setEnabled(False)
  
    success_rate = (self.successful_tasks / self.running_tasks) * 100 if self.running_tasks > 0 else 0
  
    self.add_log_entry(
        f"Все задачи завершены! Успешность: {success_rate:.1f}% "
        f"({self.successful_tasks}/{self.running_tasks})",
        "SUCCESS"
    )
  
    self.status_bar.showMessage(
        f"Завершено: {self.successful_tasks}/{self.running_tasks} задач успешно"
    )
Comprehensive completion handling:
  • UI state restoration - возврат кнопок в исходное состояние
  • Success rate calculation - процентная метрика эффективности
  • Zero division protection - защита от деления на ноль
  • Multi-channel notification - обновление лога и статус-бара
  • Formatted metrics - профессиональное представление результатов

Проверка критических зависимостей:
Python:
# Проверка зависимостей
    try:
        import selenium
        import aiohttp
    except ImportError as e:
        QMessageBox.critical(
            None,
            "Ошибка зависимостей",
            f"Не установлена необходимая библиотека: {e}\n\n"
            "Установите зависимости:\npip install PyQt6 selenium aiohttp"
        )
        return 1
Defensive programming:
  • Runtime import check - валидация зависимостей в момент запуска
  • QMessageBox.critical() - системное модальное окно ошибки
  • None parent - диалог показывается даже если главное окно не создано
  • Пользовательские инструкции - конкретные команды для решения проблемы
  • return 1 - exit code указывающий на ошибку

Создание и запуск главного окна:
Python:
# Создание и показ главного окна
    try:
        window = MainWindow()
        window.show()
      
        logger.info("GADS Clicker запущен успешно")
        return app.exec()
      
    except Exception as e:
        QMessageBox.critical(
            None,
            "Критическая ошибка",
            f"Не удалось запустить приложение:\n{e}"
        )
        if logger:
            logger.error(f"Критическая ошибка запуска: {e}")
        return 1

if __name__ == "__main__":
    sys.exit(main())
  • Exception handling - перехват любых ошибок инициализации UI
  • app.exec() - запуск Qt событийного цикла (blocking call)
  • Дублированное логирование - в GUI лог и файловую систему
  • Graceful failure - пользователь видит понятное сообщение об ошибке
  • name == "main" - выполнение только при прямом запуске
  • sys.exit(main()) - передача exit code операционной системе
  • Изоляция - функцию main() можно импортировать без автозапуска
Таким образом мы получаем практически идеальный инструмент для опустошения бюджетов конкурентов с ThreadPoolExecutor и asyncio для оптимальной работы. Адаптивные потоки по формуле min(proxy_count, cpu_count * 1.5). ProxyValidator, Qt-сигналы, headless Chrome без JS, имитация поведения, JSON-настройки, надёжный UI.
На написание этого проекта ушло около ~3х дней, проект изначально предназначался для написания этой статьи, возможно я допустил много ошибок, как я говорил выше - я не профи.

Скриншот проекта:
image.png


Полностью собранный исходный код будет опубликован в .zip архиве как приложение к статье.



На этом я не остановился, я решил еще написать собственный компилятор скрипта с помощью PyInstaller. Встречайте, GADS Clicker Complile!

Структура системы автоматизации сборки:

Python:
import os

def create_spec_file():
    spec_content = '''block_cipher = None

hiddenimports = [
    'PyQt6.QtCore', 'PyQt6.QtGui', 'PyQt6.QtWidgets',
    'selenium', 'selenium.webdriver', 'selenium.webdriver.chrome',
    'selenium.webdriver.chrome.options', 'selenium.webdriver.chrome.service',
    'selenium.webdriver.common.by', 'selenium.webdriver.common.keys',
    'selenium.webdriver.common.action_chains', 'selenium.webdriver.support.ui',
    'selenium.webdriver.support.expected_conditions', 'aiohttp', 'asyncio',
    'multiprocessing', 'concurrent.futures', 'json', 'pathlib', 'logging',
    'urllib.parse', 'datetime', 'random', 'time', 'threading'
]
Система hiddenimports решает фундаментальную проблему PyInstaller - статический анализатор не может обнаружить динамически импортируемые модули.

Агрессивная система исключений:
Python:
excludes = [
    'tkinter', 'matplotlib', 'numpy', 'scipy', 'pandas', 'PIL', 'cv2',
    'tensorflow', 'torch', 'jupyter', 'ipython', 'PyQt5', 'PySide2', 'PySide6'
]

Динамическое управление ресурсами:
Python:
# Добавляем logo.png в сборку
datas = []
if os.path.exists('logo.png'):
    datas.append(('logo.png', '.'))

a = Analysis(
    ['gadsclicker.py'],
    pathex=[], binaries=[], datas=datas, hiddenimports=hiddenimports,
    hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=excludes,
    win_no_prefer_redirects=False, win_private_assemblies=False,
    cipher=block_cipher, noarchive=False,
    a.binaries = [x for x in a.binaries if not any(    exclude in x[0].lower() for exclude in ['qt5', 'pyside', 'tkinter', 'matplotlib', 'numpy'])]
)
Cтруктура и логика:
  • ('logo.png', '.') - кортеж (source_file, destination_directory)
  • Destination '.' - корневая директория рядом с .exe файлом
  • Условное включение - graceful handling отсутствующих ресурсов
  • pathex=[] - дополнительные пути поиска (не нужны для self-contained app)
  • noarchive=False - все файлы упаковываются в single binary

EXE конфигурация с оптимизацией:
Python:
exe = EXE(
    pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [],
    name='GADSClicker', debug=False, bootloader_ignore_signals=False,
    strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None,
    console=False, disable_windowed_traceback=False, argv_emulation=False,
    target_arch=None, codesign_identity=None, entitlements_file=None,
    icon='icon.ico' if os.path.exists('icon.ico') else None,
)'''
  • debug=False - убирает debug символы, уменьшает размер на ~30%
  • console=False - GUI приложение без консольного окна
  • upx=True - UPX компрессия binary (~40% size reduction)
  • strip=False - сохранение symbol table для crash debugging
  • bootloader_ignore_signals=False - корректная обработка Ctrl+C
  • disable_windowed_traceback=False - показ ошибок в GUI режиме
  • Executable packer - сжимает binary данные
  • Runtime decompression - автоматическая распаковка при запуске
  • ~100ms startup overhead - minimal для desktop приложений
  • upx_exclude=[] - некоторые libraries могут не работать после UPX

Автоматизированная генерация requirements:
Python:
def create_requirements():
    requirements = '''PyQt6>=6.5.0
selenium>=4.15.0
aiohttp>=3.8.0
pyinstaller>=6.0.0'''
  
    with open('requirements.txt', 'w', encoding='utf-8') as f:
        f.write(requirements)

Интеллектуальный build script:
Python:
def create_build_script():
    build_script = '''@echo off
chcp 65001 >nul
echo Building GADS Clicker...

python -c "import PyQt6, selenium, aiohttp" 2>nul
if errorlevel 1 (
    echo ERROR: Dependencies missing. Run: pip install -r requirements.txt
    pause
    exit /b 1
)
  • chcp 65001 - UTF-8 кодировка для корректного отображения
  • python -c "import ..." - runtime проверка доступности модулей
  • 2>nul - подавление stderr вывода
  • errorlevel 1 - перехват import ошибок
  • exit /b - возврат error code без закрытия cmd

Проверка ресурсов и cleanup:
Python:
if not exist "logo.png" (
    echo WARNING: logo.png not found - app will use text logo
) else (
    echo Found: logo.png
)

if not exist "icon.ico" (
    echo WARNING: icon.ico not found - app will use default icon
) else (
    echo Found: icon.ico
)

if exist "dist" rmdir /s /q "dist"
if exist "build" rmdir /s /q "build"
  • Resource validation - проверка опциональных файлов
  • Graceful warnings - уведомления без прерывания сборки
  • Clean build - удаление предыдущих артефактов
  • /s /q flags - рекурсивное тихое удаление

Основной процесс сборки:
Python:
pyinstaller gadsclicker.spec --clean --noconfirm
if errorlevel 1 (
    echo ERROR: Build failed!
    pause
    exit /b 1
)

echo Copying additional files...
if exist "logo.png" (
    copy "logo.png" "dist\\" >nul 2>&1
    echo Copied: logo.png to dist/
)
  • --clean - очистка cache перед сборкой
  • --noconfirm - автоматическая перезапись существующих файлов
  • errorlevel проверка - валидация успешности сборки
  • Post-build копирование - дублирование ресурсов в dist/

Финальная валидация и отчетность:
Python:
echo.
echo SUCCESS: dist\\GADSClicker.exe
if exist "dist\\GADSClicker.exe" (
    for %%I in ("dist\\GADSClicker.exe") do echo Size: %%~zI bytes
)
pause'''
  • echo. - пустая строка для читаемости
  • File existence check - проверка создания исполняемого файла
  • %%~zI - получение размера файла в batch
  • Size reporting - информация о финальном binary

Главная функция автоматизации:
Python:
def main():
    create_spec_file()
    create_requirements()
    create_build_script()
  
    print("Files created:")
    print("- gadsclicker.spec")
    print("- requirements.txt")
    print("- build.bat")
    print("\nInstall dependencies: pip install -r requirements.txt")
    print("Then run: build.bat")

if __name__ == "__main__":
    main()
  1. Генерация .spec файла - PyInstaller конфигурация
  2. Создание requirements.txt - dependency management
  3. Build script generation - полностью автоматизированная сборка
  4. User instructions - четкие инструкции по использованию
Преимущества автоматизации:
  • Reproducible builds - одинаковый результат на разных машинах
  • Error handling - автоматическая валидация на каждом шаге
  • Resource management - intelligent включение/исключение файлов
  • Size optimization - максимальное сжатие без потери функциональности

Разработка этого проекта позволила создать профессиональный инструмент для автоматизации задач, направленных на истощение ресурсов конкурентов. Программа построена на современных технологиях с упором на высокую производительность и надёжность.

Основные возможности:
  • Запускает сотни параллельных браузерных сессий через прокси с разными UserAgent.
  • Автоматически проверяет работоспособность прокси, одновременно валидируя сотни адресов.
  • Имитирует естественное поведение пользователя и подстраивает количество потоков под оборудование.
  • Оптимизированный Chrome снижает потребление памяти на 70%.
  • Система сама восстанавливается после сбоев.
  • Тёмный интерфейс с автоматическими рекомендациями настроек упрощает использование.
  • Автоматизированная сборка упаковывает проект в один .exe файл.
Инструмент идеально подходит для тех, кто хочет эффективно автоматизировать процессы против конкурентов. На разработку проекта и написание статьи ушло чуть больше пяти дней (работа началась ещё до регистрации на форуме).
Я искренне надеюсь, что статья вам понравилась. Ниже прикладываю исходный код всего проекта (скрипт (без .exe) + компилятор), а так же инструкцию по установке проекта.
Всем хорошего времени суток! ;)

by society
 

Вложения

  • GADS Clicker by SOCIETY.zip
    82.5 KB · Просмотры: 0
Activity
So far there's no one here
Сверху Снизу