Garmin vívoactive 5 の睡眠・ストレス・Body Battery などを、毎日 BigQuery に集めています(全体の構成はGarmin・Plaud・iPhone のライフログを BigQuery に自動で集めた:Cloud Run と手元の Mac を使い分けた設計)
3つのソースの中で、一番手こずったのが Garmin でした。7月29日〜8月24日の27日分の毎朝のまとめを数えると、Garmin の数字が入っていたのは7日だけです。この記事では、その原因と直し方を紹介しようかと思います。
- 試したブラウザの外からの呼び出しは、どれも 403 でした。 そのため、手元の Mac の Chrome の中から呼んでいます
- Cookie を壊していたのは、自分のコードでした。 Playwright の既定の起動オプションと、保険のつもりの「Cookie を借りる」処理です
- セッションは、2〜3時間ほどアクセスがないと切れるようでした。 90分ごとに収集して、収集そのものを延命にしています
ブラウザの外からは 403 だった
最初は、Plaud と同じように、Chrome の Cookie を読み取って Python から API を呼ぶつもりでした。試した結果です。
| 呼び出し方 | Cookie | CSRF トークン | TLS の偽装 | 結果 |
|---|---|---|---|---|
ブラウザのページ内で fetch |
あり | あり | — | 200 |
| httpx(ブラウザの外) | あり | あり | なし | 403 |
| curl_cffi(Chrome のふりをする) | あり | あり | あり | 403 |
403 の応答は本文が空で、server: cloudflare が付いていました。Garmin のアプリより手前の Cloudflare で止められているようです。

そこで、Playwright で Chrome を動かし、ページの中から fetch する形にしました。ログインは専用の Chrome のプロファイルで自分でする(パスワードは自動で入れない)ので、そのプロファイルがある手元の Mac の launchd で動かしています。
Cookie を壊していたのは、自分のコードだった
8月の後半にかけて、収集が失敗し続けました。原因は4つありました。
1つ目は、Cookie の範囲(ドメイン)を潰していたことです。 Garmin の Cookie には .garmin.com・.connect.garmin.com・connect.garmin.com の3種類があります。普段の Chrome から借りるときに全部 .garmin.com にそろえたら、ページは開けるのに API だけ 403 になりました。範囲を保ったまま移すように直しました。
2つ目は、Playwright の既定の起動オプションです。 Playwright は Chrome を起動するとき、--use-mock-keychain と --password-store=basic を付けます。Mac の Chrome は Cookie をキーチェーンの鍵で暗号化しています。今回の環境では、このオプションを付けて起動すると、普通の Chrome が書いた Cookie が読み込めず、起動後には消えていました。
実際、ログインで作った Cookie 195件が、Playwright を一度動かしただけで消えていました。ignore_default_args でこの2つを外すと、収集が成功し、Cookie も残るようになりました。
ctx = pw.chromium.launch_persistent_context(
str(profile_dir),
channel="chrome", # Chrome for Testing ではなく、普通の Chrome
headless=headless,
ignore_default_args=[
"--enable-automation",
"--use-mock-keychain",
"--password-store=basic",
],
)
3つ目は、「Cookie を借りる」処理そのものです。 失敗したときの保険に、普段の Chrome から Cookie を借りて足していました。この処理が有効期限を落として入れていたので、走るたびに、1年持つはずのログインの Cookie が「ブラウザを閉じたら消える Cookie」に上書きされていました。
しかも、借りた Cookie ではログインは戻りませんでした。Cloudflare の通過を示す Cookie はブラウザごとに紐づいていて、別のプロファイルにコピーしても引き継げないようです。借りる処理はまるごと消しました。
4つ目は、セッションの寿命です。 Cookie を壊さなくなっても、間が空くと切れました。
| 前回の収集からの間隔 | 結果 |
|---|---|
| 2分 / 47分 / 50分 | 成功 |
| 2時間46分 / 4時間 | 失敗 |
手元の Cookie の有効期限は変わっていなかったので、サーバー側に、アクセスの間隔で切れる仕組みがあると考えています。50分では切れず、2時間46分では切れていたので、少なくともこの環境では2〜3時間ほどの空白で切れるようです。1日2回の収集では、毎回切れていたわけです。

収集を90分ごと(1日16回)にして、収集そのものを延命にしました。これで切れなくなったかは、この記事の時点ではまだ確かめているところです。同じ日を何度取っても結果は同じなので、回数を増やしても困りません。
残る弱点は、Mac が90分以上スリープすると切れることです。切れたら、専用のプロファイルでログインし直します。
Fitbit Air への乗り換えも考えた
Google の Fitbit Air に乗り換えることも、一時考えました。Fitbit には公式の API(Google Health API)があり、ドキュメントには、Fitbit と Pixel Watch のすべての端末のデータを返すこと、データの種類に歩数や睡眠があることが書かれています。Fitbit Air の名前はなく、試してもいないので、どこまで取れるかは確かめていません。取れるなら、Mac の Chrome を動かす仕組みはいらなくなります。
ただ、Fitbit Air にはディスプレイがないので、時間を見られません。普通の時計のベルトに付けるアタッチメントを3Dプリンタで作るやり方もありますが、PC でタイピングするときに邪魔になりそうで、まだ検討中です。
使っている時計
Garmin vívoactive 5 は、電池が長持ちして、寝るときも着けっぱなしにしやすい時計です。Body Battery やストレスも3分ごとに記録されます。レビューはこちら。
気をつけていること
- 公式の API ではありません。 Web 版が使っている内部の API を、自分のアカウントの自分のデータに限って呼んでいます。Garmin 側の画面や防御が変われば、予告なく動かなくなります
- ログインの操作は自分でしていて、パスワードを自動で入力する処理は作っていません
参考
- About the Google Health API(Google for Developers)
- Playwright:BrowserType.launchPersistentContext(ignoreDefaultArgs)
あわせて読みたい
