主要变更: - 后端代码从根目录迁移到 app/ 目录 - 前端代码从 frontend/ 重命名为 WebUI/ - 更新所有导入路径以适配新结构 - 提取公共 API 响应函数到 app/api/common.py - 精简验证器服务代码 - 更新启动脚本和文档 测试: - 新增完整测试套件 (tests/) - 单元测试: 模型、仓库层 - 集成测试: 覆盖所有 22+ API 端点 - E2E 测试: 4个完整工作流场景 - 添加 pytest 配置和测试运行脚本
16 KiB
ProxyPool 架构重构设计文档
目标:建立一个高度可扩展、分层清晰、易于维护的代理池系统。最关键的目标是让添加新爬虫变得极其简单。
1. 架构总览
采用经典的分层架构:
┌─────────────────────────────────────────┐
│ Frontend (Vue3 + Vite + Element Plus) │
└─────────────┬───────────────────────────┘
│ HTTP/REST
┌─────────────▼───────────────────────────┐
│ API Layer (FastAPI Routers) │ ← 只负责:校验输入、调用 Service、格式化输出
├─────────────────────────────────────────┤
│ Service Layer │ ← 业务逻辑编排:爬取策略、验证调度、导出逻辑
├─────────────────────────────────────────┤
│ Plugin System (Crawlers) │ ← 爬虫插件:实现统一接口,返回原始代理数据
├─────────────────────────────────────────┤
│ Task Queue & Workers │ ← 验证队列:背压控制、Worker 池、削峰填谷
├─────────────────────────────────────────┤
│ Repository Layer │ ← 数据访问:所有 SQL 收敛于此
├─────────────────────────────────────────┤
│ Infrastructure (DB / Config / Log) │ ← 基础设施:连接池、配置、日志
└─────────────────────────────────────────┘
2. 后端核心设计原则
2.1 消灭全局单例,全面使用依赖注入 (DI)
当前 scheduler = ValidationScheduler() 是模块级全局变量,导致测试困难、隐式依赖。
重构后:
- 所有核心组件(DB、Scheduler、PluginManager)都通过 FastAPI
Depends注入 - 使用
contextlib.asynccontextmanager在 lifespan 中初始化并挂载到app.state - 单元测试可以轻易 mock 任何一层
2.2 Repository 模式收敛所有 SQL
所有数据库操作从 api_server.py、scheduler.py 中彻底抽离到 repositories/proxy_repo.py。
好处:
- 换数据库时只改 Repository
- 写单元测试直接 mock Repository
- SQL 语句集中管理,防止散落在各处
2.3 任务队列解耦爬取与验证
当前插件爬取后直接 asyncio.gather(*10000_tasks) 验证,存在内存和并发风险。
重构后引入轻量级内存队列:
ValidationQueue:基于asyncio.QueueValidationWorkerPool:固定数量的 Worker 从队列消费- 爬取结果
put进队列即返回,验证在后台进行 - 天然支持背压(backpressure),防止内存爆炸
3. 插件系统设计(核心)
3.1 设计目标
让添加一个新爬虫只需要做两件事:
- 创建一个类,继承
BaseCrawlerPlugin - 实现
crawl()方法,返回list[ProxyRaw]
3.2 插件接口
from dataclasses import dataclass
from typing import List, AsyncIterator
@dataclass
class ProxyRaw:
ip: str
port: int
protocol: str # http | https | socks4 | socks5
class BaseCrawlerPlugin:
"""所有爬虫插件必须继承的基类"""
name: str = "" # 插件唯一标识
display_name: str = "" # 展示名称
description: str = "" # 描述
enabled: bool = True # 是否默认启用
async def crawl(self) -> List[ProxyRaw]:
"""
爬取代理的核心方法。
可以是纯同步逻辑,也可以包含异步 HTTP 请求。
返回原始代理列表,不要在这里做验证。
"""
raise NotImplementedError
async def health_check(self) -> bool:
"""可选:检查当前插件是否可用(如目标网站是否可访问)"""
return True
3.3 插件注册机制
采用显式注册 + 装饰器模式,抛弃运行时目录扫描。
from core.plugin_system import registry
@registry.register
class MyNewPlugin(BaseCrawlerPlugin):
name = "my_new_plugin"
display_name = "我的新代理源"
async def crawl(self):
return [ProxyRaw("1.2.3.4", 8080, "http")]
优点:
- 类型安全:IDE 可以自动补全、静态检查
- 可控:不会出现意外加载未预期模块的问题
- 测试友好:测试时只注册 mock 插件
同时提供一个兼容入口 registry.auto_discover("plugins"),用于兼容现有习惯。
3.4 插件元数据持久化
插件的 enabled 状态应该持久化到数据库(或 settings JSON),而不是仅存在于内存。
新增 plugin_settings 表:
CREATE TABLE plugin_settings (
plugin_id TEXT PRIMARY KEY,
enabled INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
启动时:
- 加载所有已注册插件
- 从
plugin_settings读取持久化状态 - 合并到插件实例中
4. 任务调度与验证队列
4.1 验证队列设计
class ValidationQueue:
def __init__(self, worker_count: int = 50):
self.queue: asyncio.Queue[ProxyRaw] = asyncio.Queue()
self.worker_count = worker_count
self.workers: list[asyncio.Task] = []
self._running = False
async def start(self):
self._running = True
for _ in range(self.worker_count):
self.workers.append(asyncio.create_task(self._worker_loop()))
async def stop(self):
self._running = False
for _ in self.workers:
self.queue.put_nowait(None) # sentinel
await asyncio.gather(*self.workers, return_exceptions=True)
async def submit(self, proxies: list[ProxyRaw]):
for p in proxies:
await self.queue.put(p)
async def _worker_loop(self):
while True:
item = await self.queue.get()
if item is None:
break
await self._validate_and_save(item)
self.queue.task_done()
4.2 调度器设计
SchedulerService 负责:
- 启动/停止验证队列
- 定时从数据库拉取存量代理,重新投入验证队列
- 协调插件爬取后的验证流程
class SchedulerService:
def __init__(self, queue: ValidationQueue, proxy_repo: ProxyRepository):
self.queue = queue
self.proxy_repo = proxy_repo
self.interval_minutes = 30
self._task: asyncio.Task | None = None
5. 数据库设计
保留 SQLite + aiosqlite,但优化连接管理。
5.1 表结构
-- 代理表
CREATE TABLE proxies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOT NULL,
port INTEGER NOT NULL,
protocol TEXT DEFAULT 'http',
score INTEGER DEFAULT 10,
response_time_ms REAL,
last_check TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(ip, port)
);
-- 插件设置表
CREATE TABLE plugin_settings (
plugin_id TEXT PRIMARY KEY,
enabled INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 系统设置表(JSON 存储)
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
5.2 连接管理
- 使用
asynccontextmanager管理连接生命周期 - 每个 HTTP 请求独立获取连接,请求结束后关闭
- 调度器/队列等长生命周期组件也定期重建连接(如每 1000 次操作)
6. API 设计调整
保持现有 API 路径基本不变,但路由按资源拆分。
6.1 路由拆分
apiv1/
├── __init__.py
├── proxies.py # /api/proxies/*
├── plugins.py # /api/plugins/*
├── scheduler.py # /api/scheduler/*
└── settings.py # /api/settings
6.2 新增/调整的 API
插件相关
GET /api/plugins— 获取插件列表(含持久化状态)PUT /api/plugins/{plugin_id}/toggle— 切换启用状态(持久化到 DB)POST /api/plugins/{plugin_id}/crawl— 触发爬取(异步,返回任务 ID)POST /api/plugins/crawl-all— 批量爬取
关键变更:爬取接口改为异步触发而不是同步等待。因为新爬虫可能爬取数万个代理,同步 HTTP 请求会超时。
返回示例:
{
"code": 200,
"message": "爬取任务已启动",
"data": {
"task_id": "crawl-20250402-001",
"queued": 150
}
}
为了简化前端,第一阶段可以保留同步 API,但内部通过 asyncio.create_task 包装,并设置合理的超时(30 秒)。在真正大规模使用时,再迁移到 WebSocket/SSE 推送进度。
7. 前端架构调整
7.1 新增 Service 层
从 Store 中剥离 API 调用逻辑:
frontend/src/
├── services/
│ ├── proxyService.js # 代理相关 API 调用
│ ├── pluginService.js # 插件相关 API 调用
│ ├── schedulerService.js
│ └── settingService.js
├── stores/
│ ├── proxy.js # 纯状态管理
│ └── plugin.js
7.2 Store 职责收敛
Store 只负责:
- 持有状态(
ref/reactive) - 提供计算属性
- 调用 Service,然后更新状态
7.3 API 适配
由于后端 API 路径保持不变,前端改动主要是代码组织上的调整,URL 和返回结构尽量兼容。
8. 目录结构(重构后)
ProxyPool/
├── main.py # 项目入口
├── requirements.txt # Python 依赖
├── .env.example # 环境变量示例
│
├── app/ # 后端代码
│ ├── api/ # FastAPI 入口和路由
│ │ ├── __init__.py
│ │ ├── main.py # 应用工厂
│ │ ├── lifespan.py # 生命周期管理
│ │ ├── deps.py # 依赖注入
│ │ ├── errors.py # 统一异常
│ │ └── routes/
│ │ ├── __init__.py
│ │ ├── proxies.py
│ │ ├── plugins.py
│ │ ├── scheduler.py
│ │ └── settings.py
│ │
│ ├── core/ # 基础设施
│ │ ├── __init__.py
│ │ ├── config.py # Pydantic Settings
│ │ ├── log.py # 日志
│ │ ├── db.py # 数据库连接池/上下文
│ │ ├── exceptions.py # 业务异常
│ │ ├── plugin_system/ # 插件系统
│ │ │ ├── __init__.py
│ │ │ ├── base.py # BaseCrawlerPlugin
│ │ │ └── registry.py # 插件注册中心
│ │ └── tasks/ # 任务队列
│ │ ├── __init__.py
│ │ └── queue.py # ValidationQueue
│ │
│ ├── models/ # 数据模型
│ │ ├── __init__.py
│ │ ├── schemas.py # Pydantic 模型
│ │ └── domain.py # 领域模型
│ │
│ ├── repositories/ # 数据访问层
│ │ ├── __init__.py
│ │ ├── proxy_repo.py
│ │ ├── settings_repo.py
│ │ └── task_repo.py
│ │
│ ├── services/ # 业务逻辑层
│ │ ├── __init__.py
│ │ ├── proxy_service.py
│ │ ├── plugin_service.py
│ │ ├── scheduler_service.py
│ │ └── validator_service.py
│ │
│ └── plugins/ # 爬虫插件
│ ├── __init__.py
│ ├── base.py # 通用抓取基类
│ ├── fate0.py
│ ├── kuaidaili.py
│ ├── ip3366.py
│ ├── ip89.py
│ ├── speedx.py
│ ├── yundaili.py
│ ├── proxylist_download.py
│ └── proxyscrape.py
│
├── WebUI/ # Vue3 前端
│ ├── src/
│ │ ├── api/ # API 封装
│ │ ├── stores/ # Pinia 状态管理
│ │ ├── views/ # 页面组件
│ │ ├── router/ # 路由配置
│ │ ├── components/ # 通用组件
│ │ └── style.css # 全局样式
│ ├── index.html
│ └── package.json
│
├── tests/ # 测试目录
│ ├── conftest.py
│ ├── unit/
│ └── integration/
│
├── script/ # 启动脚本
├── db/ # 数据存储
├── logs/ # 日志文件
└── DESIGN.md # 本文档
9. 迁移计划
Phase 1: 基础设施(今天完成)
- 重写
core/config.py→ Pydantic Settings - 重写
core/db.py→ 带上下文管理的连接池 - 创建
models/层
Phase 2: Repository + Service(今天完成)
- 创建
repositories/proxy_repo.py - 创建
services/下的业务类 - 迁移现有逻辑
Phase 3: 插件系统(今天完成,核心)
- 创建
core/plugin_system/base.py和registry.py - 设计显式注册机制
- 将所有现有插件迁移到新基类
Phase 4: 任务队列(今天完成)
- 创建
ValidationQueue和WorkerPool - 重写
SchedulerService
Phase 5: API 路由(今天完成)
- 拆分
api_server.py到api/routes/ - 组装新的
api/main.py
Phase 6: 前端调整(今天完成)
- 拆分 Service 层
- 适配 Store
- 保留现有页面,只改代码组织
Phase 7: 清理与验证
- 删除旧的
api_server.py,core/scheduler.py,core/sqlite.py等 - 运行测试,确保所有功能正常
- 提交代码
10. 添加新爬虫的标准流程(目标体验)
假设要添加一个名为 mynewsource 的爬虫:
Step 1: 创建文件 app/plugins/mynewsource.py
from app.core.plugin_system import BaseCrawlerPlugin, ProxyRaw
from app.plugins.base import BaseHTTPPlugin # 可选:如果基于 HTTP 爬取
class MyNewSourcePlugin(BaseHTTPPlugin):
name = "mynewsource"
display_name = "我的新代理源"
description = "从 example.com 爬取免费代理"
def __init__(self):
super().__init__()
self.urls = ["https://example.com/proxies"]
async def crawl(self) -> list[ProxyRaw]:
results = []
for url in self.urls:
html = await self.fetch(url)
# ... 解析 html ...
results.append(ProxyRaw(ip="1.2.3.4", port=8080, protocol="http"))
return results
Step 2: 在 app/plugins/__init__.py 中注册
from .mynewsource import MyNewSourcePlugin
from app.core.plugin_system import registry
registry.register(MyNewSourcePlugin)
Step 3: 重启后端服务,前端自动显示新插件。
无需修改任何路由、服务、数据库表。
文档版本: 1.0 作者: Kimi Code 日期: 2026-04-02