"""
系统配置设置
包含系统级别的基础配置。
"""
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Optional, Tuple
@dataclass
class BrowserConfig:
"""浏览器配置"""
headless: bool = False
timeout: int = 30000 # 30秒
viewport_width: int = 1920
viewport_height: int = 1080
user_agent: Optional[str] = None
locale: str = "zh-CN"
timezone: str = "Asia/Shanghai"
proxy: Optional[str] = None
executable_path: Optional[str] = None
@dataclass
class HumanBehaviorConfig:
"""人类行为配置"""
typing_speed: Tuple[int, int] = (80, 150) # 打字速度范围(毫秒/字符)
pause_probability: float = 0.15 # 随机暂停概率
random_delay_range: Tuple[float, float] = (0.3, 0.8) # 随机延迟范围(秒)
click_delay_range: Tuple[float, float] = (0.1, 0.3) # 点击延迟范围
scroll_delay_range: Tuple[float, float] = (0.2, 0.5) # 滚动延迟范围
page_load_wait: Tuple[float, float] = (1.0, 3.0) # 页面加载等待时间
@dataclass
class ConcurrencyConfig:
"""并发配置"""
max_concurrent_tasks: int = 3
task_retry_count: int = 3
retry_backoff_factor: float = 2.0
task_timeout: int = 300 # 5分钟
@dataclass
class FileConfig:
"""文件配置"""
max_image_size: int = 10 * 1024 * 1024 # 10MB
max_video_size: int = 500 * 1024 * 1024 # 500MB
supported_image_formats: list = None
supported_video_formats: list = None
temp_dir: str = "temp"
upload_timeout: int = 600 # 10分钟
def __post_init__(self):
if self.supported_image_formats is None:
self.supported_image_formats = ["jpg", "jpeg", "png", "webp"]
if self.supported_video_formats is None:
self.supported_video_formats = ["mp4", "mov", "avi", "mkv"]
@dataclass
class LogConfig:
"""日志配置"""
level: str = "INFO"
format: str = (
"{time:YYYY-MM-DD HH:mm:ss} | "
"{level: <8} | "
"{name}:{function}:{line} - "
"{message}"
)
log_dir: str = "logs"
rotation: str = "1 day"
retention: str = "30 days"
compression: str = "zip"
enable_colors: bool = True
@dataclass
class SecurityConfig:
"""安全配置"""
encrypt_cookies: bool = True
cookie_expiry_days: int = 30
max_login_attempts: int = 3
session_timeout: int = 7200 # 2小时
enable_rate_limit: bool = True
rate_limit_per_minute: int = 10
class Settings:
"""系统主配置类"""
def __init__(self):
# 项目基础路径
self.BASE_DIR = Path(__file__).resolve().parent.parent
# 环境变量
self.ENV = os.getenv("SMP_ENV", "development")
self.DEBUG = os.getenv("SMP_DEBUG", "false").lower() == "true"
# 初始化各个配置模块
self.browser = BrowserConfig()
self.human_behavior = HumanBehaviorConfig()
self.concurrency = ConcurrencyConfig()
self.file = FileConfig()
self.log = LogConfig()
self.security = SecurityConfig()
# 加载环境变量配置
self._load_env_config()
# 根据环境调整配置
self._adjust_for_environment()
def _load_env_config(self):
"""加载环境变量配置"""
# 浏览器配置
if os.getenv("SMP_BROWSER_HEADLESS"):
self.browser.headless = os.getenv("SMP_BROWSER_HEADLESS").lower() == "true"
if os.getenv("SMP_BROWSER_TIMEOUT"):
self.browser.timeout = int(os.getenv("SMP_BROWSER_TIMEOUT"))
if os.getenv("SMP_BROWSER_USER_AGENT"):
self.browser.user_agent = os.getenv("SMP_BROWSER_USER_AGENT")
if os.getenv("SMP_BROWSER_PROXY"):
self.browser.proxy = os.getenv("SMP_BROWSER_PROXY")
# 并发配置
if os.getenv("SMP_MAX_CONCURRENT_TASKS"):
self.concurrency.max_concurrent_tasks = int(os.getenv("SMP_MAX_CONCURRENT_TASKS"))
if os.getenv("SMP_TASK_RETRY_COUNT"):
self.concurrency.task_retry_count = int(os.getenv("SMP_TASK_RETRY_COUNT"))
# 文件配置
if os.getenv("SMP_MAX_IMAGE_SIZE"):
self.file.max_image_size = int(os.getenv("SMP_MAX_IMAGE_SIZE"))
if os.getenv("SMP_MAX_VIDEO_SIZE"):
self.file.max_video_size = int(os.getenv("SMP_MAX_VIDEO_SIZE"))
# 日志配置
if os.getenv("SMP_LOG_LEVEL"):
self.log.level = os.getenv("SMP_LOG_LEVEL")
if os.getenv("SMP_LOG_DIR"):
self.log.log_dir = os.getenv("SMP_LOG_DIR")
def _adjust_for_environment(self):
"""根据环境调整配置"""
if self.ENV == "production":
# 生产环境配置
self.browser.headless = True
self.log.level = "WARNING"
self.concurrency.max_concurrent_tasks = 2 # 降低并发
self.human_behavior.pause_probability = 0.25 # 增加暂停频率
elif self.ENV == "testing":
# 测试环境配置
self.browser.headless = True
self.concurrency.max_concurrent_tasks = 1
self.concurrency.task_retry_count = 1
self.log.level = "DEBUG"
@property
def cookies_dir(self) -> Path:
"""Cookie目录路径"""
return self.BASE_DIR / "auth" / "cookies"
@property
def logs_dir(self) -> Path:
"""日志目录路径"""
return self.BASE_DIR / self.log.log_dir
@property
def temp_dir(self) -> Path:
"""临时目录路径"""
return self.BASE_DIR / self.file.temp_dir
@property
def media_dir(self) -> Path:
"""媒体文件目录路径"""
return self.BASE_DIR / "media"
def ensure_directories(self):
"""确保所有必要的目录存在"""
directories = [
self.cookies_dir,
self.logs_dir,
self.temp_dir,
self.media_dir / "images",
self.media_dir / "videos"
]
for directory in directories:
directory.mkdir(parents=True, exist_ok=True)
def to_dict(self) -> dict:
"""转换为字典格式"""
return {
"ENV": self.ENV,
"DEBUG": self.DEBUG,
"BASE_DIR": str(self.BASE_DIR),
"browser": {
"headless": self.browser.headless,
"timeout": self.browser.timeout,
"viewport_width": self.browser.viewport_width,
"viewport_height": self.browser.viewport_height,
"user_agent": self.browser.user_agent,
"proxy": self.browser.proxy
},
"human_behavior": {
"typing_speed": self.human_behavior.typing_speed,
"pause_probability": self.human_behavior.pause_probability,
"random_delay_range": self.human_behavior.random_delay_range
},
"concurrency": {
"max_concurrent_tasks": self.concurrency.max_concurrent_tasks,
"task_retry_count": self.concurrency.task_retry_count,
"retry_backoff_factor": self.concurrency.retry_backoff_factor
},
"file": {
"max_image_size": self.file.max_image_size,
"max_video_size": self.file.max_video_size,
"supported_image_formats": self.file.supported_image_formats,
"supported_video_formats": self.file.supported_video_formats
},
"log": {
"level": self.log.level,
"log_dir": self.log.log_dir,
"rotation": self.log.rotation,
"retention": self.log.retention
}
}
# 全局配置实例
settings = Settings()
# 确保目录存在
settings.ensure_directories()