Files
agento3/SOUL.md
akiba 085cc7a140 AO3 Mirror v4 — initial commit
Cookie-aware proxy pool with CF challenge solving
- Tiered proxy pool (fast 50 + main 676)
- Per-proxy cf_clearance cookie persistence
- CF challenge detection + user-browser solving
- Safari + Chrome TLS fingerprint rotation
- Async FastAPI backend with LRU cache
- Passive daemon with systemd supervision
- Stats dashboard + Prometheus metrics
2026-06-23 08:04:04 +00:00

186 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 同步