Garmin 数据采集管道:没有官方 API 时怎么拿自己的数据

在 Garmin 没有个人开发者 API 的情况下,通过第三方库和 SSO 认证获取自己的训练、睡眠、压力等数据。

问题

Garmin 拥有丰富的训练数据(GPS 轨迹、训练负荷、VO2Max、压力等),但不提供面向个人开发者的公开 API

官方合作 API(Garmin Health API / Connect IQ SDK)仅面向企业合作伙伴,个人用户想拿自己的数据?对不起,请用我们的 App。

现实:数据是用户的,但获取方式被厂商垄断。

解决方案:garth 库

库名: garth
类型: Python 第三方库,开源社区维护
原理: 模拟 Garmin 移动端 App 的认证流程,获取 OAuth token 后调用内部 API
风险: 非官方 API,Garmin 可能随时变更接口,无 SLA 保证

认证体系(已踩完的坑)

Garmin 的认证体系比预期复杂得多。最大的坑是:Web 登录和移动端 API 是两套独立的认证体系,不互通。

三条认证路径

路径A_移动端登录(正常路径):
  流程: garth.login(email, password) → 内部调用移动端 SSO API → 获取 OAuth1 + OAuth2 token
  端点: sso.garmin.com/mobile/api/login
  优点: 一行代码搞定
  致命缺陷: 被 Cloudflare 全局限流(429),重试会重置冷却窗口

路径B_Web SSO + Ticket 交换(救命路径):
  流程:
    1. 浏览器打开旧版登录页(/sso/signin,不是 /sso/embed)
    2. 手动登录成功,拿到 CAS service ticket
    3. 用 garth 底层方法将 ticket 交换为 OAuth token
  关键: ticket 有效期只有几秒,必须立刻交换
  适用: 移动端 API 被封时的备用方案

路径C_Token 刷新(日常运行):
  流程: garth.resume(token目录) → 自动用 refresh_token 换新 access_token
  适用: token 未过期时的日常数据拉取
  注意: 只有 refresh_token 也过期时才需要重新走路径 A 或 B

路径 A 的 429 限流(最大的坑)

现象:
  garth.login() 返回 429 Too Many Requests

关键发现:
  - 这是 Cloudflare 全局级限流,不分 IP
  - 换代理、换网络、换设备都没用
  - 每次重试都会重置冷却窗口
  - 冷却时间:1-2 小时(完全不碰才行)

正确做法:
  1. 发现 429 后立刻停止所有重试
  2. 检查是否有其他服务/cron 在重复调用(必须全部停掉)
  3. 等 1-2 小时完全冷却
  4. 只跑一次 garth.login()

错误做法:
  - 循环重试(每次重置冷却窗口,永远解不了封)
  - 换代理重试(全局限流,代理没用)
  - 以为等 10 分钟就够了(不够,至少等 1 小时)

路径 B 的 Ticket 交换细节

第一步_浏览器登录:
  URL: https://sso.garmin.com/sso/signin?service=https://connect.garmin.com/modern/
  注意: 必须是 /sso/signin(旧版),不是 /sso/embed(新版不给 ticket)
  目标: 登录成功后从 URL 或 network 请求中拿到 CAS service ticket

第二步_Ticket 交换:
  用 garth 底层方法:
    1. get_oauth1_token(ticket, client) → 获取 OAuth1 token
    2. exchange(oauth1_token, client) → 换取 OAuth2 token
  
  关键参数:
    ticket 的 service 参数必须匹配: 移动端集成地址(不是 connect.garmin.com)
    ticket 必须立即使用(几秒过期)

第三步_保存:
  garth.save(token目录) → 保存 oauth1_token.json 和 oauth2_token.json

Token 生命周期管理

oauth2_token:
  access_token 有效期: 约 1 小时
  refresh_token 有效期: 约 30 天
  自动刷新: garth.resume() 会自动检查并刷新 access_token

日常运行:
  garth.resume(token目录) → 自动加载并刷新 → 正常调用 API
  不需要每次都 login()
  不触发移动端 API 限流

refresh_token 过期时:
  需要重新走路径 A(如果没被限流)或路径 B(SSO ticket 交换)
  当前 refresh_token 过期日期需要记录并提前预警

Token 文件结构:
  目录下两个文件:
    - oauth1_token.json
    - oauth2_token.json

API 端点

使用方式: garth 高级 API(推荐)或 connectapi 低级调用

高级 API(推荐,稳定性更好):
  睡眠: garth.DailySleep.list(date, count)
    返回: 睡眠得分

  HRV: garth.DailyHRV.list(date, count)
    返回: 昨夜平均 HRV、5 分钟最高 HRV、周平均、状态

  步数: garth.DailySteps.list(date, count)
    返回: 总步数、距离、步数目标

  压力: garth.DailyStress.list(date, count)
    返回: 整体压力等级、各等级持续时间

  训练状态: garth.DailyTrainingStatus.list(date, count)
    返回: 训练状态码、急性/慢性负荷、ACWR 急慢比、体能趋势

低级 API(灵活但不稳定):
  活动列表: garth.connectapi('/activitylist-service/activities/search/activities', params={...})
    返回: 活动名称、类型、时长、距离、心率、配速、VO2Max

已知坑_API路径变化:
  现象: 带用户 GUID 的 URL 返回 403
  原因: Garmin 禁用了部分旧路径
  解法: 使用高级 API 封装,不手动拼 URL
  教训: 非官方 API 没有 changelog,随时可能变

训练状态码对照表

状态码映射:
  0: 无数据
  1: 减量中
  2: 效果良好 (Productive)
  3: 保持中 (Maintaining)
  4: 恢复中 (Recovery)
  5: 无效训练 (Unproductive)
  6: 过度训练 (Overreaching)
  7: 超量恢复 (Peaking)

ACWR 急慢比解读:
  < 0.8: 训练不足(LOW)
  0.8-1.3: 甜区(OPTIMAL)
  1.3-1.5: 偏高,注意疲劳
  > 1.5: 伤病风险升高

压力等级解读

数值范围:
  0-25: 低压力 💚
  26-50: 中等 💛
  51-75: 偏高 🟠
  76-100: 高压力 🔴

数据格式:人机双读快照

格式: 与 Whoop 篇相同的 Markdown + HTML 注释 JSON 方案

机器层标记: "<!--GARMIN_DAILY_JSON ... GARMIN_DAILY_JSON-->"
提取正则: /<!--GARMIN_DAILY_JSON\s*([\s\S]*?)\s*GARMIN_DAILY_JSON-->/

JSON 结构包含:
  - dayKey: 日期
  - sleep: 睡眠得分
  - hrv: HRV 数据
  - steps: 步数
  - stress: 压力
  - training: 训练状态 + ACWR
  - activities: 当天运动列表

已验证的踩坑经验总结

坑1_429是全局的:
  重要程度: ⭐⭐⭐⭐⭐
  本质: Cloudflare 全局限流,不分 IP/设备/代理
  影响: 一旦触发,所有重试都会延长冷却时间
  教训: 先停掉所有可能在调 Garmin API 的服务,再等

坑2_两套认证不互通:
  重要程度: ⭐⭐⭐⭐
  本质: 浏览器 Web SSO 和移动端 OAuth 是独立体系
  影响: 浏览器能登录不代表 API 能用
  教训: 了解清楚认证架构再动手,否则白折腾

坑3_ticket几秒过期:
  重要程度: ⭐⭐⭐
  本质: CAS service ticket 有效期极短
  影响: 人手动操作几乎来不及
  教训: 自动化流程必须在拿到 ticket 后立即交换

坑4_代理配置:
  重要程度: ⭐⭐
  本质: 部分环境代理会干扰 Garmin SSO
  解法: Garmin 域名走直连规则

坑5_API路径无预警变化:
  重要程度: ⭐⭐⭐
  本质: 非官方 API 没有版本管理
  影响: 昨天能用的 URL 今天可能 403
  教训: 优先用库的高级封装而非手动拼 URL

与 Whoop 的对比

对比维度:
  官方 API:
    Whoop: 有,标准 OAuth2,文档清晰
    Garmin: 无(个人开发者),依赖第三方库

  认证复杂度:
    Whoop: 低(标准流程)
    Garmin: 高(移动端限流 + SSO 交换 + token 文件管理)

  实时推送:
    Whoop: 有 Webhook
    Garmin: 无(只能定时拉取)

  API 稳定性:
    Whoop: 高(官方维护)
    Garmin: 低(非官方,随时变)

  维护成本:
    Whoop: 
    Garmin: 高(需要关注 token 过期、API 变更、限流状态)

结论: Garmin 数据价值高但获取成本也高。一旦管道搭好且 token 有效,日常运行是稳定的。
  主要风险是 refresh_token 过期后需要重新走认证流程。

适用场景

  • 需要自动获取 Garmin 训练负荷和 ACWR 用于训练决策
  • 需要将 Garmin 数据与 Whoop(或其他设备)交叉分析
  • 需要长期积累训练数据做趋势分析
  • 能接受非官方 API 的维护成本和风险

半胆浣熊

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

← 返回文章列表