Garmin Connect のデータ収集が止まり続けた理由:ブラウザの外は 403、Cookie を壊していたのは自分のコードだった

Garmin Connect のデータ収集が止まり続けた理由:ブラウザの外は 403、Cookie を壊していたのは自分のコードだった

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 で止められているようです。

ブラウザの外からは 403 になる図。ブラウザの中からの fetch は門を通って 200 が返り、データの API に届く。Python から(Cookie あり)と、Chrome のふりをする(Cookie・TLS あり)は、門番に止められて 403
ブラウザの外からは 403 になる図。ブラウザの中からの fetch は門を通って 200 が返り、データの API に届く。Python から(Cookie あり)と、Chrome のふりをする(Cookie・TLS あり)は、門番に止められて 403

そこで、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回の収集では、毎回切れていたわけです。

セッションの寿命の図。1日2回の収集では、約2時間アクセスがないところで切れ、次の収集は失敗する。90分ごとの収集では切れないまま続く。ただし Mac がスリープすると切れる
セッションの寿命の図。1日2回の収集では、約2時間アクセスがないところで切れ、次の収集は失敗する。90分ごとの収集では切れないまま続く。ただし Mac がスリープすると切れる

収集を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 側の画面や防御が変われば、予告なく動かなくなります
  • ログインの操作は自分でしていて、パスワードを自動で入力する処理は作っていません

参考

あわせて読みたい

Garmin Connect のデータ収集が止まり続けた理由:ブラウザの外は 403、Cookie を壊していたのは自分のコードだった

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

エンジニア。Claude Code / Codex / MCP / BigQuery / 自動化 / ガジェット / 3Dプリンター / スマートホーム。楽して生きたい。ゲーム / イヤホン / キーボード / プログラミング / AI

目次