216 lines
6.3 KiB
Python
216 lines
6.3 KiB
Python
|
|
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)
|