/~learn/s/2f3f7bd7
> 复盘时间:2026-07-29 > 覆盖事故:2026-07-17(8天僵死)、2026-07-27(多进程竞争)、2026-07-28(Session连接静默失效) > 关联文档:2026-05-28-抓取稳定性复盘.md、2026-05-26-数据缓存分离工程优化
---
| 日期 | 用户发现 | 数据滞后 | 修复耗时 |
| ------ | --------- | --------- | --------- |
| 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) |
规律:都是晚上发现,白天收盘后的数据没更新。这不是巧合——是系统性问题。
---
根因链:
schedule v1.2.2 连续运行约8天后僵死(PID存活但 run_pending() 不再触发任何任务),08:45全量拉取静默跳过dlsym mr_eval_context: symbol not found),adata/akshare全链路崩溃['tushare','adata'] 两条路径,两个源同时故障时无第3条回退,95/95全挂
根因链:
run_fetch.sh 的 cmd_start 只检查PID文件中Python子进程PID,不检查bash守护shell本身是否存活./run_fetch.sh start 未先stop时,旧bash守护shell变成"孤儿"(PID文件被新进程覆盖),cmd_stop也杀不到os._exit(0)自重启时,3个孤儿bash各自启动1个Python → 3个fetcher同时跑schedule、同时调API、同时写CSV
根因链:
tushare_api_proxy.py 使用 requests.Session() 做HTTP keep-alive连接复用{code:0, data:{fields:[], items:[]}} 空数据但不抛异常---
┌─────────────────────────────────────────────────────────────┐
│ 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: 没有主动告警,数据断档只能靠用户发现 │
└─────────────────────────────────────────────────────────────┘
---
以下是数据抓取"晚上没抓到当天数据"的全部可能原因,按出现概率排序:
| # | 失效模式 | 检测方法 | 修复方案 | 状态 | ||
| --- | --------- | --------- | --------- | ------ | ||
| 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匹配进程 | ✅ 已修复 |
| # | 失效模式 | 检测方法 | 修复方案 | 状态 |
| --- | --------- | --------- | --------- | ------ |
| 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重启) |
| # | 失效模式 | 检测方法 | 修复方案 | 状态 |
| --- | --------- | --------- | --------- | ------ |
| C1 | ETF类数据只有tushare+adata 2条路径,双挂全灭 | 两个源都返回空/超时 | 需补第3条兜底路径 | ⚠️ 待修 |
| C2 | 无后缀ETF代码(510880/513050等)只走adata单源 | 失败品种中含无后缀码 | 自动补.SH/.SZ后缀试tushare | ⚠️ 部分修复 |
| C3 | adata返回数据含重复行(同一天多条) | CSV中同一trade_date多条 | drop_duplicates已存在 | ✅ 已修复 |
| # | 失效模式 | 检测方法 | 修复方案 | 状态 |
| --- | --------- | --------- | --------- | ------ |
| D1 | system_monitor只看PID不看数据新鲜度,僵死/空数据进程检测不到 | 监控探针 _sniff_latest_dates() 滞后>1交易日 | 需要主动健康检查+告警推送 | ❌ 未修,最大隐患 |
| D2 | 日志未截断导致500MB+大文件,影响排查效率 | ls -lh看日志大小 | 所有print(response)加[:200]截断 | ✅ 已修复 |
| D3 | 数据断档靠用户发现,无主动告警 | — | 新鲜度探针滞后>1交易日→推送Server酱 | ❌ 未修 |
---
现状:_sniff_latest_dates() 函数已存在(检查5个探针代码的最新日期),每次拉取后写入 fetch_history.json,但没有任何人读它,也没有任何告警。
必须实现:
schedule 中加一个每2小时执行一次的健康检查任务(不只是在拉取后检查)os._exit(0) 强制重启(让bash shell自动拉起新进程)
现状:ETF/指数类代码 _get_source_priority() 返回 ['tushare','adata'] 2条路径。根据5.1事故铁律,必须有至少3条。
方案:增加 eastmoney(东方财富)作为ETF日线第3数据源,或在tushare+adata都失败时从sina/东财接口拉取。
现状:依赖pgrep检测,理论上两个start同时执行可能产生竞态。
方案:在bash启动脚本中用 flock 文件锁,确保永远只有一个守护进程能启动。
---
# 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
---
以下铁律已/应写入 .trae/rules/superpowers.md 踩坑实录:
pgrep -f 模式匹配,start前自动清理残留|| true[:200]截断,K线类响应不截断几分钟爆500MB+---