双设备健康数据管道:从分散到统一的自动化采集方案

如何将两个穿戴设备(Whoop + Garmin)的健康数据统一采集、结构化存储,并用于跨设备交叉分析。

问题

用户同时佩戴 Whoop(恢复/睡眠)和 Garmin(训练/GPS),两个设备各有擅长但数据完全隔离。

每天需要回答的核心问题是”今天该练什么”,但答案藏在两个设备的数据交叉点上——单看任何一个 App 都不够。

根因:设备厂商不开放数据互通,用户只能在各自 App 里看各自的图表。

核心概念

双设备互补模型

whoop:
  擅长: 恢复评估、睡眠质量、自主神经状态
  核心指标:
    - 恢复分 (0-100, ≥67 为绿区)
    - HRV RMSSD (自主神经状态)
    - 睡眠表现 (实际 vs 目标)
    - 睡眠债 (累计不足)
  API: 官方开发者 API,标准 OAuth2,文档清晰

garmin:
  擅长: 训练负荷管理、GPS 轨迹、运动表现
  核心指标:
    - 训练状态 (Productive/Maintaining/Detraining/Overreaching)
    - ACWR 急慢比 (甜区 0.8-1.3)
    - 压力分 (全天压力水平)
    - VO2Max 趋势
  API: 无官方个人开发者 API,依赖第三方库模拟移动端认证

关键差异:Whoop 告诉你”身体准备好了吗”,Garmin 告诉你”最近练够了吗”。两者组合才能做出合理的训练决策。

数据格式:人机双读快照

每日快照采用 Markdown + 隐藏 JSON 的混合格式:

格式设计原则:
  人类层: Markdown 正文,直接阅读
  机器层: HTML 注释内嵌 JSON,正则提取
  存储位置: GitHub 仓库,按设备/日期组织
  版本管理: git log 追溯历史变化

为什么不用纯 JSON:纯 JSON 在 GitHub 上不可读。纯 Markdown 又不方便程序解析。HTML 注释是两全的方案——渲染时不可见,解析时一行正则搞定。

方法:跨设备训练决策

决策矩阵

Whoop 恢复分Garmin ACWR判断建议
≥67 (绿区)< 0.8恢复好 + 练少了上强度,增加训练量
≥67 (绿区)0.8-1.3状态健康按计划执行
≥67 (绿区)> 1.3恢复好但负荷偏高按计划但关注疲劳信号
34-66 (黄区)任意恢复中等降强度,有氧为主
< 34 (红区)任意疲劳/过度训练休息或轻度恢复跑

预警规则

趋势预警:
  - condition: HRV 连续 3 天下降 > 10%
    signal: 可能过度训练
    action: 建议减量 1-2 天

  - condition: 睡眠债累计 > 5 小时 (滚动 7 天)
    signal: 睡眠不足积累
    action: 优先补觉,降低训练强度

  - condition: ACWR > 1.5
    signal: 急性负荷过高
    action: 伤病风险升高,强制减量

  - condition: 恢复分连续 3 天红区
    signal: 系统性疲劳
    action: 完全休息,排查压力源

架构:数据管道

数据流

实时路径:
  Whoop 手环 → Whoop Cloud → Webhook 推送 → 处理脚本 → 微信通知 + 落盘

定时路径 (每天 21:00):
  Whoop API → 拉取当天数据 → 每日快照
  Garmin Connect → 第三方库拉取 → 每日快照
  合并两设备简报 → 微信推送
  快照文件 → GitHub 同步

周报路径 (每周一 08:30):
  读取近 7 天快照 → 生成趋势汇总表 → 微信推送

调度策略

为什么用 Agent 级调度而非系统 crontab:
  - Token 过期时 Agent 可自主刷新重试
  - API 路径变化时 Agent 可自主切换
  - 失败时 Agent 可判断原因并决定是重试还是告警
  - 不需要人类半夜起来改脚本

已验证的踩坑经验

Garmin 认证

坑1_全局限流:
  现象: garth.login() 返回 429
  原因: Cloudflare 全局级限流,不分 IP
  解法: 停止所有重试,等 1-2 小时自然冷却
  教训: 每次重试都会重置冷却窗口

坑2_认证体系不互通:
  现象: 浏览器能登录 Garmin Connect,但 cookie 无法转成 API token
  原因: Web SSO (CAS) 和移动端 OAuth 是两套独立体系
  解法: 用旧版 Web SSO 拿 CAS ticket → 用库底层方法 exchange 为 OAuth token
  关键: ticket 有效期只有几秒,必须立即交换

坑3_API路径变化:
  现象: 之前能用的 URL 突然返回 403
  原因: 带用户 GUID 的路径被禁,不带的正常
  解法: 使用库的高级封装方法,不手动拼 URL
  教训: 非官方 API 随时可能变,没有 changelog

Token 管理

Whoop:
  - access_token 可被服务端提前吊销(即使本地未过期)
  - 遇到 401 必须强制刷新而非信任本地过期时间
  - created_at 时区是 UTC,文档未说明

Garmin:
  - refresh_token 约 30 天有效
  - 正常使用走 refresh_oauth2(),不触发登录限流
  - 只有 refresh_token 也过期时才需要重新走 SSO 流程

适用场景

这套方案适用于任何需要跨设备健康数据整合的场景:

  • 马拉松训练决策(本文场景)
  • 慢性疲劳监控
  • 睡眠优化实验(改变习惯 → 观测数据变化)
  • 运动处方调整(配合教练使用)

核心思路是通用的:把分散在各个 App 里的数据拿回来,用自己的逻辑分析

半胆浣熊

文科生,不会代码,但很幸运 —— 赶上了 AI 的年代。
这里是我的实战学习笔记。

← 返回文章列表