281 lines
8.8 KiB
Python
281 lines
8.8 KiB
Python
|
|
import json
|
|||
|
|
import random
|
|||
|
|
from datetime import datetime
|
|||
|
|
from typing import List, Dict, Optional
|
|||
|
|
from pathlib import Path
|
|||
|
|
from loguru import logger
|
|||
|
|
from config import Config
|
|||
|
|
|
|||
|
|
|
|||
|
|
class DataManager:
|
|||
|
|
"""数据管理器,负责URL和统计数据的存储与管理"""
|
|||
|
|
|
|||
|
|
def __init__(self, data_file: str = None):
|
|||
|
|
self.data_file = data_file or str(Path(Config.DATA_DIR) / 'urls_data.json')
|
|||
|
|
self._ensure_data_file()
|
|||
|
|
self.data = self._load_data()
|
|||
|
|
|
|||
|
|
def _ensure_data_file(self):
|
|||
|
|
"""确保数据文件存在"""
|
|||
|
|
Config.ensure_dirs()
|
|||
|
|
if not Path(self.data_file).exists():
|
|||
|
|
self._save_data({'urls': {}})
|
|||
|
|
|
|||
|
|
def _load_data(self) -> Dict:
|
|||
|
|
"""加载数据"""
|
|||
|
|
try:
|
|||
|
|
with open(self.data_file, 'r', encoding='utf-8') as f:
|
|||
|
|
return json.load(f)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"加载数据文件失败: {str(e)}")
|
|||
|
|
return {'urls': {}}
|
|||
|
|
|
|||
|
|
def _save_data(self, data: Dict = None):
|
|||
|
|
"""保存数据"""
|
|||
|
|
try:
|
|||
|
|
save_data = data if data is not None else self.data
|
|||
|
|
with open(self.data_file, 'w', encoding='utf-8') as f:
|
|||
|
|
json.dump(save_data, f, ensure_ascii=False, indent=2)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"保存数据文件失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def add_url(self, url: str) -> bool:
|
|||
|
|
"""
|
|||
|
|
添加新URL
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
url: MIP页面链接
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
是否添加成功
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
if url in self.data['urls']:
|
|||
|
|
logger.warning(f"URL已存在: {url}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 生成随机目标点击次数
|
|||
|
|
target_clicks = random.randint(Config.MIN_CLICK_COUNT, Config.MAX_CLICK_COUNT)
|
|||
|
|
|
|||
|
|
self.data['urls'][url] = {
|
|||
|
|
'url': url,
|
|||
|
|
'status': 'active', # active, completed, failed
|
|||
|
|
'target_clicks': target_clicks,
|
|||
|
|
'click_count': 0,
|
|||
|
|
'reply_count': 0,
|
|||
|
|
'created_time': datetime.now().isoformat(),
|
|||
|
|
'last_click_time': None,
|
|||
|
|
'click_history': []
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self._save_data()
|
|||
|
|
logger.info(f"成功添加URL,目标点击次数: {target_clicks}")
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"添加URL失败: {str(e)}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def record_click(self, url: str, has_reply: bool = False):
|
|||
|
|
"""
|
|||
|
|
记录一次点击
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
url: URL
|
|||
|
|
has_reply: 是否获得回复
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
if url not in self.data['urls']:
|
|||
|
|
logger.error(f"URL不存在: {url}")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
url_data = self.data['urls'][url]
|
|||
|
|
url_data['click_count'] += 1
|
|||
|
|
url_data['last_click_time'] = datetime.now().isoformat()
|
|||
|
|
|
|||
|
|
if has_reply:
|
|||
|
|
url_data['reply_count'] += 1
|
|||
|
|
|
|||
|
|
# 记录点击历史
|
|||
|
|
url_data['click_history'].append({
|
|||
|
|
'time': datetime.now().isoformat(),
|
|||
|
|
'has_reply': has_reply
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
self._save_data()
|
|||
|
|
logger.info(f"记录点击成功,总点击次数: {url_data['click_count']}/{url_data['target_clicks']}")
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"记录点击失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def mark_url_completed(self, url: str):
|
|||
|
|
"""
|
|||
|
|
标记URL为已完成
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
url: URL
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
if url not in self.data['urls']:
|
|||
|
|
logger.error(f"URL不存在: {url}")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
self.data['urls'][url]['status'] = 'completed'
|
|||
|
|
self.data['urls'][url]['completed_time'] = datetime.now().isoformat()
|
|||
|
|
self._save_data()
|
|||
|
|
|
|||
|
|
logger.info(f"URL已完成: {url}")
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"标记URL完成失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def mark_url_failed(self, url: str, reason: str = ""):
|
|||
|
|
"""
|
|||
|
|
标记URL为失败
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
url: URL
|
|||
|
|
reason: 失败原因
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
if url not in self.data['urls']:
|
|||
|
|
logger.error(f"URL不存在: {url}")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
self.data['urls'][url]['status'] = 'failed'
|
|||
|
|
self.data['urls'][url]['failed_reason'] = reason
|
|||
|
|
self.data['urls'][url]['failed_time'] = datetime.now().isoformat()
|
|||
|
|
self._save_data()
|
|||
|
|
|
|||
|
|
logger.warning(f"URL标记为失败: {url}, 原因: {reason}")
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"标记URL失败状态失败: {str(e)}")
|
|||
|
|
|
|||
|
|
def get_active_urls(self) -> List[Dict]:
|
|||
|
|
"""
|
|||
|
|
获取所有活跃的URL
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
活跃URL列表
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
active_urls = [
|
|||
|
|
data for data in self.data['urls'].values()
|
|||
|
|
if data['status'] == 'active'
|
|||
|
|
]
|
|||
|
|
return active_urls
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"获取活跃URL失败: {str(e)}")
|
|||
|
|
return []
|
|||
|
|
|
|||
|
|
def get_url_info(self, url: str) -> Optional[Dict]:
|
|||
|
|
"""
|
|||
|
|
获取URL详细信息
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
url: URL
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
URL信息
|
|||
|
|
"""
|
|||
|
|
return self.data['urls'].get(url)
|
|||
|
|
|
|||
|
|
def get_all_urls(self) -> List[Dict]:
|
|||
|
|
"""
|
|||
|
|
获取所有URL
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
所有URL列表
|
|||
|
|
"""
|
|||
|
|
return list(self.data['urls'].values())
|
|||
|
|
|
|||
|
|
def get_statistics(self) -> Dict:
|
|||
|
|
"""
|
|||
|
|
获取统计数据
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
统计数据
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
total_urls = len(self.data['urls'])
|
|||
|
|
active_urls = sum(1 for data in self.data['urls'].values() if data['status'] == 'active')
|
|||
|
|
completed_urls = sum(1 for data in self.data['urls'].values() if data['status'] == 'completed')
|
|||
|
|
failed_urls = sum(1 for data in self.data['urls'].values() if data['status'] == 'failed')
|
|||
|
|
|
|||
|
|
total_clicks = sum(data['click_count'] for data in self.data['urls'].values())
|
|||
|
|
total_replies = sum(data['reply_count'] for data in self.data['urls'].values())
|
|||
|
|
|
|||
|
|
stats = {
|
|||
|
|
'total_urls': total_urls,
|
|||
|
|
'active_urls': active_urls,
|
|||
|
|
'completed_urls': completed_urls,
|
|||
|
|
'failed_urls': failed_urls,
|
|||
|
|
'total_clicks': total_clicks,
|
|||
|
|
'total_replies': total_replies,
|
|||
|
|
'reply_rate': f"{(total_replies / total_clicks * 100) if total_clicks > 0 else 0:.2f}%"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return stats
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"获取统计数据失败: {str(e)}")
|
|||
|
|
return {}
|
|||
|
|
|
|||
|
|
def delete_url(self, url: str) -> bool:
|
|||
|
|
"""
|
|||
|
|
删除URL
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
url: URL
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
是否删除成功
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
if url in self.data['urls']:
|
|||
|
|
del self.data['urls'][url]
|
|||
|
|
self._save_data()
|
|||
|
|
logger.info(f"已删除URL: {url}")
|
|||
|
|
return True
|
|||
|
|
else:
|
|||
|
|
logger.warning(f"URL不存在: {url}")
|
|||
|
|
return False
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"删除URL失败: {str(e)}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def reset_url(self, url: str) -> bool:
|
|||
|
|
"""
|
|||
|
|
重置URL状态(重新开始点击)
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
url: URL
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
是否重置成功
|
|||
|
|
"""
|
|||
|
|
try:
|
|||
|
|
if url not in self.data['urls']:
|
|||
|
|
logger.warning(f"URL不存在: {url}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
# 生成新的随机目标点击次数
|
|||
|
|
target_clicks = random.randint(Config.MIN_CLICK_COUNT, Config.MAX_CLICK_COUNT)
|
|||
|
|
|
|||
|
|
self.data['urls'][url]['status'] = 'active'
|
|||
|
|
self.data['urls'][url]['target_clicks'] = target_clicks
|
|||
|
|
self.data['urls'][url]['click_count'] = 0
|
|||
|
|
self.data['urls'][url]['reply_count'] = 0
|
|||
|
|
self.data['urls'][url]['last_click_time'] = None
|
|||
|
|
self.data['urls'][url]['click_history'] = []
|
|||
|
|
self.data['urls'][url]['reset_time'] = datetime.now().isoformat()
|
|||
|
|
|
|||
|
|
self._save_data()
|
|||
|
|
logger.info(f"已重置URL: {url}")
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"重置URL失败: {str(e)}")
|
|||
|
|
return False
|