昨天提到今天要跟 Garmin 打交道,得先過 API 這關。今天就是自己遇到問題的完整紀錄,包含一次卡關和它的解法。
總結一下結論:這條路能行,但它不是這麼直接給你走的。
Garmin 有官方的開發者 API,但要走那條路得先申請、等審核,適合的是要做商業產品的團隊,不太適合一個想撈自己資料的市民跑者專案。
這邊做個小澄清:此專案都是自己帳號裡的資料,不碰別人的、不對外提供、也沒有商業用途。
實務上要拉資料出來,用非官方套件也許是比較快的路,這次選的是 Python 的 garminconnect。理由很直接:它不是走官方通道,而是模擬手機 App 登入 Garmin Connect 的方式去打那些其實是給 App 用的內部 API。要用就必須讓它想辦法看起來像一支真的手機登入,而不能太像一支寫腳本的機器人。
打開這個套件的原始碼,登入這件事被拆成了五段策略,一段一段往下試:
mobile+cffi 手機 App 走 TLS 指紋偽裝
mobile+requests 手機 App 走一般 HTTP
widget+cffi SSO 內嵌表單
portal+cffi 入口網站登入頁
portal+requests 入口網站登入頁(保底)
前面兩段偽裝成手機 App,偽裝失敗才退到網頁表單,網頁表單也不行,最後才退到完全不做偽裝的最陽春方式。光是這五段降級策略存在這件事,就足以說明這是條不好走的路—如果 Garmin 歡迎你這樣撈資料,不會逼一個套件搞出五套備案。
實際跑起來,五段策略一路降級,卡在第三段—widget+cffi:
widget+cffi failed: Widget login: unexpected title 'Set Password'
這個訊息很微妙,因為套件對「密碼錯誤」和「需要 MFA」都各自有明確的判斷分支,但這個兩者都不是。也就是說,Garmin 沒說密碼錯,是回了一個要求設定密碼的頁面。
為什麼會這樣,原因其實在自己身上:這個帳號的密碼早就忘了,跑 CLI 之前才剛動過「忘記密碼」的重設流程,而那個流程並沒有在瀏覽器裡收尾。帳號因此停在一個半完成的狀態,SSO 才回了那個頁面—它根本還沒走到驗密碼那一步。
至於它確切卡在重設流程的哪一步,沒有再深究。能確定的只有一件事:CLI 處理不了這種得在瀏覽器裡走完的流程,得換一個管道才能解決。
解法很直接:先用一般瀏覽器登入 Garmin Connect,把「設定密碼」流程走完,確認自己真的有一組能用帳密登入的密碼。走完這一步,回頭再跑一次 CLI 登入,就順利過了。
CLI 從頭到尾都不會、也不該去處理這種需要瀏覽器互動的關卡—它只會乖乖填帳密表單,遇到這種頁面就是卡住,換個管道才是正解,硬是重試不會有幫助。
登入成功後拿到的是一組 token,值得花一段講清楚它的生命週期,因為這決定了之後排程要不要處理「過期重登」這件事。
Token 是一個 JWT,把它拆開直接看得到簽發與到期時間:
簽發 iat:2026-07-21 00:19:43
到期 exp:2026-07-22 06:03:31
有效期長:107028 秒 ≈ 29.7 小時
access token 大約撐 30 小時,另外還有一組 refresh token,效期長得多,而且套件會在 access token 到期前 15 分鐘自動用 refresh token 換一組新的。
實務上的意思是:只要這支程式偶爾有在跑,就不會被登出;但如果像自己這次一樣,中間停用了三週以上,refresh 的循環沒被觸發過,就得回到最開頭,重新走一次取得憑證的流程。
搞定登入只是第一關。真正拿資料時還會發現一件事—摘要 API 給的資料,跟 Garmin Connect App 裡看到的漂亮圖表,是兩層不同的東西。昨天貼過的那段 JSON:
{
"distance_m": 5000.0,
"pace": "6:17/km",
"avg_hr": 143.0,
"max_hr": 157.0
}
平均值、最大值,距離及配速。想要「每一秒的心率變化」,得往更底層的原始檔案挖—那是明天的主題。
摘要 API 拿得到的資料很有限,真正的細節都鎖在 Garmin 錶同步上去的原始 FIT 檔案裡。那是一個二進位格式,打開來是一堆看不懂的位元組,偶們明天再來拆解!