# AO3 Mirror — SOUL v4 ## 项目宗旨 为中国用户恢复 archiveofourown.org 的访问。维护一个高可用、高效、经济的反向代理镜像站。 目标:单台服务器承受 **200+ QPS**,CF 5 秒盾自动求解。 ## 核心指标 | 指标 | 当前值 | 目标 | |------|--------|------| | 本地 QPS(缓存命中) | **720 QPS** | ≥200 | | 平均延迟(缓存命中) | **14-42ms** | ≤100ms | | 代理存活率(AO3/CF) | **~72%** (最佳端口) | ≥70% | | 代理池 | 726 WebShare (最佳端口) | — | | 有效代理 | ~524 (72%) | ≥500 | | Cookie 代理 | — | ≥200 (持有 cf_clearance) | | CF 挑战 | 主动求解 (用户浏览器) | 零感知 | ## 架构总览(v4 — Cookie 感知 + CF 挑战求解) ``` 用户 ──→ Cloudflare CDN ──→ Caddy (443, 60s) ──→ 2× uvicorn workers (8081-8082) [全异步] │ ┌──────┴──────┐ │ Tiered Pool │ │ │ │ Fast (50) │ ← POST/login (8s, 1 retry) │ Main (676) │ ← GET/browse (15s, 2 retries) │ │ │ Cookie 感知 │ ← 每个代理持有自己的 cookie jar │ (cf_clearance│ 成功请求后自动保存 Set-Cookie │ 等持久化) │ 后续请求自动注入 └──────┬──────┘ │ curl_cffi AsyncSession (safari15_5/17_0, chrome123/124 TLS 指纹 + 连接复用) ``` ### v4 关键改进 | 改进 | v3 | v4 | |------|-----|-----| | Cookie 持久化 | ❌ 裸请求 | ✅ 每代理 cookie jar | | CF 挑战检测 | ❌ 当代理死亡 | ✅ 分离检测,不退避 | | CF 挑战求解 | ❌ 放弃 | ✅ 用户浏览器自动求解 | | 代理亲和性 | ❌ 随机 | ✅ Challenge token 保证同 proxy | | TLS 指纹 | chrome123/124 | safari15_5, safari17_0, chrome123/124 | | 统计准确度 | ❌ alive=total (假数据) | ✅ 实时遍历计数 | | Cookie 代理数 | N/A | ✅ 统计面板展示 | | 用户感知 | 502 错误页 | 短暂「检查浏览器」→ 自动恢复 | ## 核心组件 ### 1. 异步代理池 (proxy_pool.py) v4 - `ProxySession` — 每个代理持有自己的 `AsyncSession` + **cookie jar** - Cookie 生命周期:成功请求自动保存 → 过期自动清理 → 后续请求自动注入 - CF 挑战分离:`mark_challenged()` ≠ `mark_failure()` — 不退避 - 加权轮询:响应速度越快的代理,被选中的概率越大 - 指数退避:仅对真失败 (connection error/timeout) → 5s→15s→30s→60s→120s→300s - Cookie-aware 选择:`get_proxy_with_cookies()` 优先返回持有 cf_clearance 的代理 ### 2. 异步抓取器 (ao3_fetcher.py) v4 - `async fetch_url()` — Cookie 感知 + CF 挑战检测 + 智能重试 - `is_cf_challenge()` — 检测 403/503 是否为 CF 挑战页(body markers + Server header) - 智能重试策略: 1. 首次尝试 → 优先用带 cookie 的代理 2. 遇到 CF 挑战 → 换代理(不退避) 3. 遇到真失败 → 退避 + 换代理 - 重试 2 次(fast 路径 1 次) - 请求超时:15s (普通), 8s (交互) ### 3. 后端服务 (app.py) v4 FastAPI,绑定 127.0.0.1。 - **Challenge token 映射**:`_challenge_map[token] → (method, url, headers, body, proxy_host)` - 用户浏览器求解 CF 挑战时,保证后续请求使用同一代理(IP 亲和性) - TTL 120s,过期自动清理 - **挑战页面透传**:所有代理都遇 CF 挑战 → 重写页面 → 发给用户浏览器 - 注入 `_cf_token` cookie 和 meta 标签 - 用户浏览器自动执行 CF JS → 验证通过 → redirect 回镜像 - Worker 捕获 cf_clearance → 保存到代理 cookie jar - Cookie 流转发:AO3 的 Set-Cookie (用户 session + cf_clearance) 域名重写后传给用户 - URL 重写:`archiveofourown.org` → `agento3.miscs.dev` - CORS 全开 - 端点:`/health` `/stats` `/metrics` `/robots.txt` ### 4. 缓存 (cache.py) - LRU 实现,容量 5000 - TTL 按路径差异化(首页30s, 章节120s, 图片600s) - 每个 worker 独立 ### 5. 统计系统 (stats.py) v2 - 内存热路径:计时器 + 桶式计数 (1s 粒度) - SQLite 批量写入:每 60s flush - 统计面板新增:Cookie 代理数 ### 6. URL 重写器 (url_rewriter.py) - HTML/CSS/JS 中 `archiveofourown.org` → `agento3.miscs.dev` - 响应头 Location/Set-Cookie 重写 - CF 挑战页面特殊 URL 处理 ## 代理策略 ### TLS 指纹(按优先级) 1. `safari15_5` — 最高 CF 绕过率 2. `safari17_0` — 次优 3. `chrome123` — 稳定 4. `chrome124` — 稳定 **已弃用**:chrome110, chrome116 (100% 被CF拦截), chrome120, edge120 (低成功率) ### 代理分级 1. **Fast Pool** (50): 最快代理,用于 POST/登录/注册 (8s timeout, 1 retry) 2. **Main Pool** (676): 全部可用代理,用于 GET/浏览 (15s timeout, 2 retries) 3. **Cookie 池**: 持有 cf_clearance 的代理,优先使用 ### 代理扫描器 (scripts/scan_proxies_cffi.py) - curl_cffi 2 种浏览器指纹 (chrome123/124) - 50 并发线程 - 每 30 分钟 cron 自动扫描 - 仅扫描最佳端口范围 13500-14499 (72.6% 成功率) - 结果排序(快→慢) ## 负载均衡 ``` Caddy (agento3.miscs.dev:443) ├── Round Robin → 127.0.0.1:8081 └── Round Robin → 127.0.0.1:8082 ``` ## 自维护框架(被动模式) | 层级 | 类型 | 频率 | 作用 | |------|------|------|------| | daemon.py | systemd service | 30s 轮询 | Worker 被动监督,3 次挂才重启 | | 代理刷新 | Hermes cron | 30min | 重新扫描代理池 | | 统计报告 | Hermes cron | 1h | 聚合指标 + 异常告警 | **不运行**:e2e 测试、Caddy 检查、文件日志、主动健康检查全部 726 代理 ## 关键路径 ``` /home/ubuntu/ ├── proxy.txt # 5000 原始代理 ├── ao3-mirror/ │ ├── app.py # FastAPI v4 (cookie-aware + CF solver) │ ├── proxy_pool.py # 异步代理池 v4 (cookie jar) │ ├── ao3_fetcher.py # 异步抓取器 v4 (CF detection) │ ├── cache.py # LRU 缓存 (5000) │ ├── stats.py # 内存热路径统计 v2 │ ├── url_rewriter.py # URL 重写 │ ├── Caddyfile # Caddy 配置 │ ├── start.sh / stop.sh │ ├── ao3-mirror.service / ao3-daemon.service │ ├── SOUL.md ← 本文档 │ ├── scripts/ │ │ ├── daemon.py # 自维护守护进程 │ │ ├── scan_proxies_cffi.py # TLS 指纹代理扫描 │ │ └── restart_workers.py # Worker 重启工具 │ └── worker-*.log # Per-worker stdout logs /dev/shm/ ├── working_proxies.txt # 有效代理 ├── ao3_stats.db # 统计数据(冷存储) └── ao3/ └── status.json # 守护进程状态 ``` ## 已知限制 - 每个 worker 独立缓存,无共享层 - Cookie jar 是内存存储,worker 重启后丢失 - 用户浏览器求解 CF 挑战需要 120s 内完成(token TTL) - 挑战页面 JS 执行依赖用户浏览器环境 ## 下一步 - [x] Cookie 感知代理层 (v4) - [x] CF 挑战检测与分离 (v4) - [x] 用户浏览器 CF 挑战求解 (v4) - [ ] 共享缓存层 (Redis/memcached) 跨 worker - [ ] Cookie jar 持久化 (重启后恢复) - [ ] 多 proxy provider 源 - [ ] 预加载热门页面到缓存 - [ ] Worker 间 cookie jar 同步