← 文档库
📄 文档learn/量化工程优化/2026-07-29-数据抓取链路故障全景复盘与防复发手册.md
🌱 创建于2026-07-29 02:20
✏️ 修改于2026-07-29 02:20
👁️ 浏览量—
🔗 短链接/~learn/s/2f3f7bd7
🔐 可见性

数据抓取链路故障全景复盘与防复发手册

> 复盘时间:2026-07-29 > 覆盖事故:2026-07-17(8天僵死)、2026-07-27(多进程竞争)、2026-07-28(Session连接静默失效) > 关联文档:2026-05-28-抓取稳定性复盘.md、2026-05-26-数据缓存分离工程优化

---

一、事故时间线(3周内连续3次数据断档)

日期 用户发现 数据滞后 修复耗时
---------------------------------
2026-07-17 晚 ETF数据停在7/15(周四) 2天 2小时(重装py-mini-racer+重启)
2026-07-27 晚 核心ETF停在7/24(周四) 3天(7/25周五+7/27周一均缺失) 1.5小时(清理孤儿进程+重启)
2026-07-28 晚 核心ETF停在7/27(周一) 1天 1小时(重启进程+修复session)

规律:都是晚上发现,白天收盘后的数据没更新。这不是巧合——是系统性问题。

---

二、三次事故的完整根因清单

事故1:2026-07-17 — schedule库8天僵死 + py-mini-racer崩溃

根因链:

事故2:2026-07-27 — 多守护进程并存

根因链:

事故3:2026-07-28 — requests.Session keep-alive静默失效

根因链:

---

三、数据抓取链路架构与失效点全景

┌─────────────────────────────────────────────────────────────┐
│  bash while-true 守护shell (run_fetch.sh)                    │
│  └─ Python fetcher (run_data_fetcher.py)                     │
│     ├─ schedule库 ──→ 08:45全量 / 09:30/12:00/14:00/22:00 T1│
│     │   ⚠️ 失效点1: 连续跑>7天僵死(PID活但不触发任务)         │
│     ├─ 03:00 os._exit(0)自重启                                │
│     ├─ 多实例? ──→ pgrep -f 检测                             │
│     │   ⚠️ 失效点2: 只查PID文件漏杀孤儿bash,导致N个进程并存    │
│     │                                                        │
│     ├─ HTTP请求层 (tushare_api_proxy.py)                      │
│     │   ├─ requests.Session() keep-alive                     │
│     │   │   ⚠️ 失效点3: 跑12h+后连接静默失效,返回空数据不报错   │
│     │   ├─ 多数据源 fallback: tushare → adata → excel/alpha   │
│     │   │   ⚠️ 失效点4: ETF类只有2条路径(tushare+adata),       │
│     │   │            无第3兜底,双挂就全灭                      │
│     │   └─ 日志打印                                          │
│     │       ⚠️ 失效点5: 错误响应不截断,日志几分钟膨胀到500MB+   │
│     │                                                        │
│     ├─ 数据缓存层 (data_cache/*.csv)                          │
│     │   ├─ DataCacheManager.save_incremental去重合并          │
│     │   └─ ⚠️ 多进程同时写CSV → 数据丢失/重复                  │
│     │                                                        │
│     └─ 健康监控                                              │
│         ├─ system_monitor: 只看PID ❌                         │
│         ├─ _sniff_latest_dates(): 探针检查新鲜度(已存在但未    │
│         │  接入告警,只写入fetch_history.json)                  │
│         └─ ⚠️ 失效点6: 没有主动告警,数据断档只能靠用户发现      │
└─────────────────────────────────────────────────────────────┘

---

四、所有已发现的失效模式汇总(Checklist)

以下是数据抓取"晚上没抓到当天数据"的全部可能原因,按出现概率排序:

A. 进程/调度层(最常见,占70%+故障)

# 失效模式 检测方法 修复方案 状态
------------------------------------
A1 schedule库跑>7天僵死,PID活但不触发任务 _sniff_latest_dates()探针滞后>1天 03:00每日os._exit(0)自重启 ✅ 已修复
A2 多守护进程并存→N个Python竞争 pgrep -f run_data_fetcher \ wc -l 应该≤2(1bash+1py) start前用pgrep清理所有残留进程 ✅ 已修复
A3 bash set -e 下 pgrep无匹配返回exit 1导致脚本退出 脚本是否正常运行 所有pgrep加 \\ true ✅ 已修复
A4 03:00自重启时PID文件被子进程覆盖,旧bash变孤儿 ps aux 查bash守护shell数量 cmd_stop kill所有pgrep匹配进程 ✅ 已修复

B. HTTP/网络层(占20%+故障)

# 失效模式 检测方法 修复方案 状态
------------------------------------
B1 requests.Session keep-alive连接12h+后静默失效,API返回空items不报错 新进程调API成功但旧进程拿到空,成功率骤降至<20% Connection: close + HTTPAdapter(max_retries=2) ✅ 已修复
B2 tushare proxy频繁502/timeout(外部不稳定) 日志含"请求失败"/"TIMEOUT" 重试+多源fallback ✅ 已有重试
B3 py-mini-racer native崩溃导致adata全链路不可用 dlsym mr_eval_context: symbol not found pip install --force-reinstall py-mini-racer==0.6.0 ✅ 已记录SOP
B4 Alpha Vantage日限额25次→GLD/QQQ/SPY下午就超限 日志含"日限额"/"rate limit" 降级到excel兜底+每天只拉1次 ✅ 已有excel兜底
B5 夜间tushare连接成功率极低(6~12%),白天48~85% 按小时统计成功率 取消凌晨拉取,08:45盘前拉 ✅ 已修复(只有03:00重启)

C. 数据源/数据层(占<10%故障)

# 失效模式 检测方法 修复方案 状态
------------------------------------
C1 ETF类数据只有tushare+adata 2条路径,双挂全灭 两个源都返回空/超时 需补第3条兜底路径 ⚠️ 待修
C2 无后缀ETF代码(510880/513050等)只走adata单源 失败品种中含无后缀码 自动补.SH/.SZ后缀试tushare ⚠️ 部分修复
C3 adata返回数据含重复行(同一天多条) CSV中同一trade_date多条 drop_duplicates已存在 ✅ 已修复

D. 可观测性/告警层(系统性缺陷)

# 失效模式 检测方法 修复方案 状态
------------------------------------
D1 system_monitor只看PID不看数据新鲜度,僵死/空数据进程检测不到 监控探针 _sniff_latest_dates() 滞后>1交易日 需要主动健康检查+告警推送 ❌ 未修,最大隐患
D2 日志未截断导致500MB+大文件,影响排查效率 ls -lh看日志大小 所有print(response)加[:200]截断 ✅ 已修复
D3 数据断档靠用户发现,无主动告警 — 新鲜度探针滞后>1交易日→推送Server酱 ❌ 未修

---

五、待完成的P0级加固项

P0-1:数据新鲜度主动健康检查+告警(最高优先级)

现状:_sniff_latest_dates() 函数已存在(检查5个探针代码的最新日期),每次拉取后写入 fetch_history.json,但没有任何人读它,也没有任何告警。

必须实现:

P0-2:ETF类数据补第3条兜底路径

现状:ETF/指数类代码 _get_source_priority() 返回 ['tushare','adata'] 2条路径。根据5.1事故铁律,必须有至少3条。

方案:增加 eastmoney(东方财富)作为ETF日线第3数据源,或在tushare+adata都失败时从sina/东财接口拉取。

P0-3:多实例启动时的进程锁

现状:依赖pgrep检测,理论上两个start同时执行可能产生竞态。

方案:在bash启动脚本中用 flock 文件锁,确保永远只有一个守护进程能启动。

---

六、排查SOP(下次数据没更新时按这个顺序查)

# Step 1: 查进程数(应该是2行: 1bash+1python)
pgrep -fl "run_data_fetcher.py"
pgrep -fl "fetch_scheduler.log"
# 如果>2行 → 多进程问题,执行: bash run_fetch.sh stop && bash run_fetch.sh start

# Step 2: 查数据最新日期
cd /Users/aohan/PycharmProjects/adata
.venv/bin/python -c "
import sys; sys.path.insert(0,'.')
from adata.aohan.quant.utils.data_cache import DataCacheManager
for c in ['510300.SH','513100.SH','510050.SH','159915.SZ','399300.SZ']:
    print(c, DataCacheManager.get_last_date(c))
"
# 如果所有日期都<今天 → fetcher挂了,重启
# 如果部分<今天 → 对应品种拉取失败,查日志

# Step 3: 查最近几次拉取成功率
cat adata/aohan/quant/data_cache/fetch_history.json | python3 -c "
import sys,json
h=json.load(sys.stdin)
for r in h[-10:]:
    print(r['time'], r['label'], f\"{r['success']}/{r['total']}\", r.get('fail_codes',[])[:3])
"
# 如果success/total持续<50% → API问题或session失效,重启fetcher

# Step 4: 查日志最近错误
tail -200 logs/fetch_scheduler.log | grep -v "API返回非列表" | tail -30

# Step 5: 重启(万能修复)
bash run_fetch.sh stop && sleep 2 && bash run_fetch.sh start

---

七、日常运维铁律(追加到superpowers.md)

以下铁律已/应写入 .trae/rules/superpowers.md 踩坑实录:

---

八、待办跟踪

▶ 📜 变更历史(1 次提交)

6fd2b712026-07-29 02:20fix(工程链路): 数据抓取稳定性全面加固 — 多进程/择时信号NULL/session失效/日志膨胀
🍃quant 知识库
by 伊甸园和枫

🔊 播放设置

勾选音源后调节配比,总和自动归一化
已选 0 个 · 合计:0%
合成中...
00:00/00:00
1.0x

分享图片

生成中...