Garmin vívoactive 5 の睡眠、Plaud Note Pro の録音、iPhone のヘルスケアの体重。データは毎日たまるのに、アプリがバラバラで見返していませんでした。
そこで、3つを BigQuery に集めて、毎朝「前日のまとめ」をメールで受け取るようにしました。実際に届くメールはこれです。

アプリを3つ開かなくても、前日の睡眠・からだ・録音が1通で見られます。データは BigQuery にあるので、「体重と睡眠」のように、ソースをまたいで SQL でも見られます。
この記事では、私が試したやり方を紹介しようかと思います。Google Cloud(Cloud Run・BigQuery)を少し触ったことがある人向けです。
- 3つのサービスは、データの取り出し方も、動かせる場所も違いました。 クラウドで動かせるものは Cloud Run に、そうでないものは手元の Mac に置いています
- Garmin と Plaud は、取れなかった日を後から取り直せます。 どの処理も、何度動かしても同じ結果になるようにしています。ヘルスケアは、毎朝その時点の最新の値を送る形です
- 毎朝のまとめは、数本の SQL です
かかったお金:Google Cloud の請求は、今のところ0円です(無料枠に収まっています)。
全体の構成

| データ | 取り方 | 動く場所 | 頻度 |
|---|---|---|---|
| iPhone のヘルスケア | iPhone から送る | Cloud Run Service | 毎朝 |
| Plaud | Web 版の内部 API を呼ぶ | Cloud Run Jobs | 毎晩3時 |
| Garmin | ブラウザの中から内部 API を呼ぶ | Mac(launchd) | 90分ごと |
なぜこの形にしたか

iPhone のヘルスケアは「送ってもらう」、ボイスレコーダーは「クラウドから取りに行く」、
スマートウォッチは「Mac のブラウザの中から取りに行く」(外からは門番に止められる)
ヘルスケア:iPhone から送ってもらう
- 制約:ヘルスケアのデータは iPhone の中にしかなく、クラウドから取りに行く API はありません
- 採用:iPhone のショートカットで、Cloud Run の受け口に毎朝送る。アプリの Health Auto Export(REST への自動送信は有料)も考えましたが、体重と体脂肪率だけなので無料のショートカットにしました
- トレードオフ:iPhone が送ってくれないと届きません。翌日の送信で最新の値は入りますが、送れなかった間の測定をすべて取り戻せるわけではありません
Plaud:クラウドから API を呼ぶ
- 制約:公式の API はありません。Web 版が使っている内部の API があり、ログインの Cookie があればブラウザの外からでも呼べました
- 採用:Cloud Run Jobs で毎晩3時に呼ぶ。Mac が止まっていても取れます
- トレードオフ:Cookie は Google のログインで作るので、普段の Chrome から読み取って保存しています。アクセスのない時間で切れるようなので、毎晩の取得とは別に、Mac から6時間ごとに API を1回呼んでセッションを保っています
Garmin:手元の Mac のブラウザの中から呼ぶ
- 制約:試したブラウザの外からの呼び出し(Python の HTTP クライアント、Chrome のふりをするもの)は、どれも 403 になりました。ログインも、パスワードを自動で入れずに自分でする形にしたかったので、ログイン済みのブラウザのプロファイルを持てる場所が必要でした
- 検討:公式の API(Garmin の開発者向けプログラム)は申し込みと承認が必要で、個人で使えるかが読み取れませんでした。Strava などを経由する方法は、睡眠やストレスが取れなくなります
- 採用:Mac で Playwright から Chrome を動かし、ログイン済みのページの中で
fetchする。90分ごとに動かしているのは、2〜3時間ほどアクセスがないとセッションが切れるようだったためです - トレードオフ:Mac が止まっている間は取れません。過去の日付も API から取り直せるので、Mac が戻れば後から埋められます
運用の数字
定期実行を始めた 7/29 から、この記事の 8/25 までの実行記録です。仕組みを作りながら試していた時期の数字で、手で流し直した回も含むので、定期実行だけの成功率ではありません。
| 項目 | Garmin | Plaud | ヘルスケア |
|---|---|---|---|
| 1回で入る行 | 約930行(3分ごとの時系列を含む) | 約25行(文字起こしの区切り) | 2行(測った日だけ) |
| 1回の処理時間 | 約46秒 | 約6秒 | — |
| 失敗した回数 | 126回中62回 | 95回中44回 | — |
Plaud の95回には、最初にまとめて取り直した28日分と、ログインし直したあとの取り直し3回、失敗した夜の自動の再実行(1回)が入っています。夜の数で見ると、7/30〜8/25 の27晩のうち22晩で失敗を記録しています。
失敗のほとんどは、ログインのセッション切れでした(Garmin は62回中61回、Plaud は44回すべて)。どちらも過去の日付を取り直せるので、ログインし直したあとに埋め直しています。原因と直し方(Garmin は90分ごとの収集、Plaud は6時間ごとのセッションを保つ呼び出し)は、ソースごとの記事に書きました。直したあとにどこまで減ったかは、この記事の時点ではまだ数えられていません。
Google Cloud 側の準備

ほとんど Terraform で作っています。手で作るのは、専用のプロジェクトと、Terraform の状態を置くバケットだけです。
gcloud projects create <プロジェクト>
gcloud billing projects link <プロジェクト> --billing-account=<請求先アカウント>
gcloud storage buckets create gs://<状態を置くバケット> --location=us-central1 --uniform-bucket-level-access
Terraform で作っているもの:
- GCS(生データ):30日で Nearline、90日で Coldline に移す
- BigQuery:データ用と実行記録用の2つ。表は日付でパーティション分割
- Cloud Run:ヘルスケアの受け口(Service)と、Plaud のジョブ(Jobs、Cloud Scheduler で毎晩3時)
- Secret Manager:トークン、Plaud の Cookie、Gmail のアプリパスワード
- サービスアカウント:ソースごとに分け、必要な権限だけ付ける
リージョンは us-central1 です。人が待たないバッチだけなので、料金を優先しました。デプロイは GitHub Actions から Workload Identity 連携で行い、鍵のファイルは持っていません。
最初に決めた3つのこと
- 生データを先に残す:JSON や音声は、加工する前に GCS に置く。後から作り直せる
- 何度実行しても同じ結果にする:BigQuery へは
MERGE(同じキーの行は上書き、なければ追加)か、日付ごとの入れ替えで書く。ヘルスケアのキーは(指標名, 計測時刻)なので、同じサンプルを何度送っても1行のままです - お金が暴れないようにする:常駐のサーバーを持たない。クエリに
maximum_bytes_billed(読む量の上限)を付ける。Cloud Run Service は最大1インスタンス
ショートカットの設定は、ヘルスケアの記事に画面付きで書いています。
取り出すところの例
毎朝のまとめは、次のような SQL を数本流して作っています(データセット名は省略)。
直近の体重と体脂肪率(毎日は測らないので、直近30日の一番新しい値)
SELECT metric, value, ts
FROM health_metrics
WHERE metric IN ('weight_body_mass', 'body_fat_percentage')
AND ts >= TIMESTAMP_SUB(@end, INTERVAL 30 DAY) AND ts < @end
QUALIFY ROW_NUMBER() OVER (PARTITION BY metric ORDER BY ts DESC) = 1
BigQuery に集めておくと、ソースをまたいで並べられます。たとえば、iPhone の体重と Garmin の睡眠を、日付でつなぐならこうです。
WITH weight AS (
SELECT DATE(ts, 'Asia/Tokyo') AS date, value AS weight_kg
FROM health_metrics
WHERE metric = 'weight_body_mass'
AND ts >= TIMESTAMP('2026-08-01', 'Asia/Tokyo')
AND ts < TIMESTAMP('2026-08-25', 'Asia/Tokyo')
QUALIFY ROW_NUMBER() OVER (PARTITION BY DATE(ts, 'Asia/Tokyo') ORDER BY ts DESC) = 1
)
SELECT s.date, s.score AS sleep_score, w.weight_kg
FROM garmin_sleep s
LEFT JOIN weight w USING (date)
WHERE s.date BETWEEN '2026-08-01' AND '2026-08-24'
ORDER BY s.date
1日に何回か測った日は、その日の最後の値だけを使います。体重を測らなかった日も、睡眠の行は残ります(weight_kg は空)。
どの SQL も、その表のパーティションの列で絞っています(health_metrics は ts、ほかは date で日ごとに分割)。何年分たまっても、全期間を読まないようにするためです。
結果は Markdown に並べて、Mac から Gmail で自分宛てに送っています。
使っている機器
Garmin vívoactive 5
睡眠・ストレス・Body Battery を取っています。3分ごとの時系列まで取れるので、「眠れなかった日の昼のストレス」のように並べて見られます。電池が長持ちするので、着けっぱなしにできます。
レビューはこちら。
Plaud Note Pro
会議や打ち合わせの録音に使っています。
レビューはこちら。
体重計(Eufy)
ヘルスケアに書き込める体重計なら、メーカーを問わず同じショートカットで送れます。
気をつけていること
- Plaud と Garmin は、公式の API ではありません。 自分のアカウントの自分のデータに限って呼んでいます。仕様が変われば、予告なく動かなくなります
- 録音には、相手の発言や仕事の情報が入ります。 録るときは相手の了承と、勤務先のルールを確かめる必要があります。録音は Plaud のクラウドにもあり、取り出したものの置き場所は自分の GCP のプロジェクトです。このプロジェクトのデータを読めるのは、この仕組みのサービスアカウントと自分だけです
- Cookie やトークンは Secret Manager に置き、コードには書いていません
ソースごとの振り返り
うまくいったこと・いかなかったこと・直し方は、3本に分けて書いています。
- iPhone のヘルスケアをショートカットで Cloud Run に送る:400 が続いたので、受け口の側で受け取れる形を広げた
- Plaud Note Pro のデータを毎晩取り込む仕組みの振り返り:文字起こしの流用はうまくいき、ログインのセッションは数日で切れた
- Garmin Connect のデータ収集が止まり続けた理由:ブラウザの外は 403、Cookie を壊していたのは自分のコードだった
参考
あわせて読みたい
