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 的维护成本和风险