This commit is contained in:
2026-06-08 16:09:13 +08:00
commit 518c6b3c7a
32 changed files with 4156 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
"""应用配置:通过环境变量和 .env 文件加载所有配置项
配置项统一使用 SURUGAYA_ 前缀,例如 SURUGAYA_APP_PORT=31106。
支持 .env 文件自动加载。
"""
from functools import lru_cache
from typing import Literal
from pydantic_settings import BaseSettings, SettingsConfigDict
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
class Settings(BaseSettings):
"""应用全局配置
所有配置项均可通过环境变量覆盖,前缀为 SURUGAYA_。
例如:SURUGAYA_APP_PORT=31106 对应 app_port 配置项。
"""
model_config = SettingsConfigDict(
env_file=str(BASE_DIR / ".env"), # 使用绝对路径
env_file_encoding="utf-8",
env_prefix="SURUGAYA_",
extra="ignore",
)
# ---- 服务基本配置 ----
app_name: str = "Surugaya Scraper Service"
app_env: Literal["dev", "prod", "test"] = "dev"
app_host: str = "0.0.0.0"
app_port: int = 31106
# ---- 日志配置 ----
log_level: str = "DEBUG"
log_to_file: bool | None = None # None 表示根据环境自动决定
log_dir: str = "logs"
log_rotation: str = "10 MB"
log_retention: str = "14 days"
log_compression: str = "zip"
log_format: str = "{time:YYYY-MM-DD HH:mm:ss} {level} {message}"
log_enqueue: bool = True
# ---- 鉴权配置 ----
bearer_token: str = "NilsO8Il8yoT1wazJFs2eeOmlPRfWuSx"
# ---- 浏览器配置 ----
browser_enabled: bool = True
browser_headless: bool | None = None # None 表示根据环境自动决定
browser_keep_open: bool | None = None # None 表示根据环境自动决定
browser_keep_open_on_error: bool | None = None # None 表示根据环境自动决定
browser_channel: str | None = "chrome"
browser_pool_size: int = 3
max_site_concurrency: int = 5 # 最大并发页面数
page_acquire_timeout_seconds: float = 60.0 # 获取页面槽位超时
request_timeout_seconds: float = 120.0 # 页面请求超时
cloudflare_wait_timeout_seconds: float = 60.0 # 等待 Cloudflare 挑战超时
session_ttl_seconds: int = 900 # 会话过期时间(秒)
browser_locale: str = "ja-JP"
browser_timezone_id: str = "Asia/Tokyo"
browser_user_agent: str = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36"
)
browser_stealth_enabled: bool = True # 是否启用浏览器 Stealth 模式
# ---- Redis 配置(可选,未配置时使用进程内存)----
redis_host: str = ""
redis_port: int = 6379
redis_password: str | None = None
redis_db: int = 0
session_namespace: str = "jp_surugaya"
# ---- 代理配置(可选,用于日本 IP)----
proxy_server: str | None = None
proxy_username: str | None = None
proxy_password: str | None = None
# ---- 目标站点 ----
base_url: str = "https://www.suruga-ya.jp"
@property
def browser_headless_effective(self) -> bool:
"""浏览器无头模式:显式配置优先,否则开发环境使用有头模式方便调试"""
if self.browser_headless is not None:
return self.browser_headless
if self.app_env == "dev":
return False
return True
@property
def browser_keep_open_on_error_effective(self) -> bool:
"""出错时是否保留浏览器窗口:仅在开发环境有头模式下保留"""
if self.browser_keep_open_on_error is not None:
return self.browser_keep_open_on_error
return self.app_env == "dev" and not self.browser_headless_effective
@property
def browser_keep_open_effective(self) -> bool:
"""成功后是否保留浏览器窗口:仅在开发环境有头模式下保留"""
if self.browser_keep_open is not None:
return self.browser_keep_open
return self.app_env == "dev" and not self.browser_headless_effective
@property
def log_to_file_effective(self) -> bool:
"""是否写日志文件:显式配置优先,否则生产环境默认写文件"""
if self.log_to_file is not None:
return self.log_to_file
return self.app_env == "prod"
@property
def proxy(self) -> dict[str, str] | None:
"""构建 Playwright 代理配置字典"""
if not self.proxy_server:
return None
proxy: dict[str, str] = {"server": self.proxy_server}
if self.proxy_username:
proxy["username"] = self.proxy_username
if self.proxy_password:
proxy["password"] = self.proxy_password
return proxy
@lru_cache(maxsize=1)
def get_settings() -> Settings:
"""获取全局配置单例"""
return Settings()