import random import time from datetime import datetime, timedelta from typing import List, Dict from threading import Thread, Lock from loguru import logger from adspower_client import AdsPowerClient from ad_automation import MIPAdAutomation from data_manager import DataManager from config import Config class ClickScheduler: """点击任务调度器""" def __init__(self): self.adspower_client = AdsPowerClient() self.data_manager = DataManager() self.running = False self.lock = Lock() def add_url(self, url: str) -> bool: """ 添加待点击的URL Args: url: MIP页面链接 Returns: 是否添加成功 """ return self.data_manager.add_url(url) def add_urls(self, urls: List[str]) -> int: """ 批量添加URL Args: urls: URL列表 Returns: 成功添加的数量 """ count = 0 for url in urls: if self.add_url(url): count += 1 return count def start_scheduler(self): """启动调度器""" if self.running: logger.warning("调度器已在运行中") return self.running = True logger.info("启动点击调度器") # 启动调度线程 thread = Thread(target=self._schedule_loop, daemon=True) thread.start() def stop_scheduler(self): """停止调度器""" self.running = False logger.info("停止点击调度器") def _schedule_loop(self): """调度循环""" while self.running: try: # 检查当前时间是否在工作时间内 if not self._is_work_time(): logger.debug("当前不在工作时间内,等待...") time.sleep(60) continue # 获取待处理的URL url = self._get_next_url() if url: logger.info(f"开始处理URL: {url}") self._process_url(url) else: logger.debug("暂无待处理的URL,等待...") time.sleep(30) except Exception as e: logger.error(f"调度循环异常: {str(e)}") time.sleep(10) def _is_work_time(self) -> bool: """ 检查当前是否在工作时间内 Returns: 是否在工作时间 """ now = datetime.now() current_hour = now.hour return Config.WORK_START_HOUR <= current_hour < Config.WORK_END_HOUR def _get_next_url(self) -> str: """ 获取下一个需要处理的URL Returns: URL或None """ with self.lock: # 获取所有活跃的URL urls = self.data_manager.get_active_urls() for url_data in urls: url = url_data['url'] # 检查是否已达到随机点击次数上限 click_count = url_data.get('click_count', 0) target_clicks = url_data.get('target_clicks', 0) if click_count >= target_clicks: # 标记为已完成 self.data_manager.mark_url_completed(url) continue # 检查距离上次点击是否超过间隔时间 last_click_time = url_data.get('last_click_time') if last_click_time: last_click = datetime.fromisoformat(last_click_time) time_diff = datetime.now() - last_click if time_diff.total_seconds() < Config.CLICK_INTERVAL_MINUTES * 60: continue return url return None def _process_url(self, url: str): """ 处理单个URL的点击任务 Args: url: 待处理的URL """ page = None try: # 启动 AdsPower 浏览器 browser_info = self.adspower_client.start_browser() if not browser_info: logger.error("启动 AdsPower 浏览器失败") return # 通过 CDP 连接到浏览器 browser = self.adspower_client.connect_browser(browser_info) if not browser: logger.error("连接浏览器失败") return # 获取页面 page = self.adspower_client.get_page(browser) if not page: logger.error("获取页面失败") return # 执行广告点击操作 automation = MIPAdAutomation(page) click_success, has_reply = automation.check_and_click_ad(url) # 更新数据统计 with self.lock: if click_success: self.data_manager.record_click(url, has_reply) logger.info(f"URL点击成功,获得回复: {has_reply}") else: logger.warning(f"URL点击失败: {url}") # 随机延迟 delay = random.randint(10, 30) time.sleep(delay) except Exception as e: logger.error(f"处理URL异常: {str(e)}") finally: # 停止浏览器(会自动清理 Playwright 资源) try: self.adspower_client.stop_browser() except Exception as e: logger.error(f"停止浏览器异常: {str(e)}") def get_statistics(self) -> Dict: """ 获取统计数据 Returns: 统计数据 """ return self.data_manager.get_statistics() def get_url_detail(self, url: str) -> Dict: """ 获取URL详细信息 Args: url: URL Returns: URL详细信息 """ return self.data_manager.get_url_info(url)