MENU

「ほぼ書かない日記」を作ってみた

日記が続かない。

これを書いている今日まで何度トライしたか覚えていないくらい、ことごとく続かなかった。
3週間が私のベスト記録だったと思います。

ということで今回、技術による解決を試みることにしました。
Notion + BigQuery + GCS でほぼ自動の日記システムを組み、コマンドを1発叩くと、その日の天気・ニュース・体重・歩数・写真までまとめて記録される仕組みにしました。
名付けて「Daybook」。

まだ動かし始めて数日。
続くかどうかは正直まだわからないけれど、設計の段階で「続かない理由」を潰す方針で組んだつもりなので、今までよりは見込みがあるはず、という話です。

目次

1. なぜ作ったか

Notion で日記を書こうとしていた時期がありました。
デイリーテンプレートを置き、毎晩そこに何か書き込むフロー。

3週間で詰まりました。

詰まった理由を後から考えると、日記そのものより周辺作業が重かった、というのが正直なところです。
スマホで撮った写真をMacに転送し、ドラッグして貼り、サイズを整え、天気を思い出して書き、体重も書こうとして HealthPlanet のアプリを開いて確認する。
本題の「今日どんな日だった」に辿り着く前に消耗していた。

逆に言うと、これら全部が自動で埋まっていれば、私が書くのは「今日どんな日だった」の1行で済む。
それなら続けられるのではないか、と。

仕様の前に、まず運用イメージから逆算しました。

夜、寝る前にコマンドを1発叩く。1〜2分で終わる。

このゴール1点だけを死守する設計で全体を組みました。

2. アーキテクチャ ― 3層構成にした理由

最初は Notion だけで完結させようとしましたが、すぐに諦めました。
Notion は閲覧体験は最高ですが、構造化データの扱いと長期保管にはあまり向きません。
逆に BigQuery は素晴らしいデータベースですが、人間が読むには向かない。

それぞれの強みを使い分ける3層構成に落ち着きました。

役割中身
BigQuery真の保存先 (source of truth)構造化された全データ
GCS (Cloud Storage)メディア置き場写真・動画・サムネイル
Notion閲覧用ビューカレンダーとギャラリーで振り返る

データの流れはシンプルで、コマンドを1回叩くと、各データソースから情報が集まり、BigQuery に1行 INSERT、GCS にメディアをアップロード、Notion にページを生成する、という順で書き込まれます。Notion を消しても BigQuery から再生成できる、という関係性です。

ロケーションは BigQuery が US マルチリージョン、GCS が asia-northeast1。
BigQuery が GCS のファイル本体を読むことはなく URL 文字列だけを保持するので、跨リージョンのコストは発生しません。
GCS は家族写真も入るので国内に置いておきたかった、それだけの理由です。

3. 使うときのフロー

3-1. 日記を追加するとき

コマンドを叩くと、対話形式で進みます。
実際の画面を抜粋するとこんな感じ。

$ python diary.py

Diary entry for which date?
  [1] Today     (2026-05-10)
  [2] Yesterday
  [3] Other
> 1

How would you like to add media?
  [1] Take photo with webcam
  [2] Upload file(s) from PC
  [3] Skip / done
  [4] Pick from Google Photos
> 4
(ブラウザで Google Photos の Picker が開く → 写真を1〜数枚選択 → 完了)

Enter today’s comment:
> 朝ラン10km。久しぶりに脚が軽かった。

[weather] OpenWeatherMap から取得… 晴れ時々曇り 22.5°C / 14.1°C
[news] NHK RSS から取得… 3件
[healthplanet] 体重 66.1kg / 体脂肪 19.1%
[garmin] 歩数 12,453歩 / 睡眠 7.2h / HRV 65 / Body Battery 80

[bigquery] Saving… Done
[notion] Creating page… Done

✓ Diary saved.

私が打ち込んだのは、日付選択、メディア選択方法、コメント1行だけ。
所要1〜2分。
残りは全部勝手に集まってくれます。

3-2. 日記を削除するとき

開発中に試しで作ったテストデータを消したい、みたいな用途で使います。

# 確認 (変更なし)
$ python scripts/delete_entry.py 2026-05-08 –dry-run

# 実行
$ python scripts/delete_entry.py 2026-05-08

裏では3つのことが順に起きます。
1) GCS のメディアを削除
2) Notion ページをアーカイブ (ゴミ箱送り、復旧可能)
3) BigQuery の行を削除

順序は意図的です。
GCS と Notion は復旧手段があるので先に消し、真の保存先である BigQuery は最後。
途中で止まっても整合性が壊れにくい順序にしてあります。

4. 自動で集めるデータ

各データは独立した collector モジュールが取りに行きます。
1つ失敗しても他は止まらない、という設計にしました(雑な日もあるので)。

  • 天気・気温: 当日は OpenWeatherMap、過去日は Open-Meteo Archive。OpenWeatherMap の無料プランは過去データを返してくれないので分業です
  • ニュース: NHK NEWS WEB の RSS から top3。当日のみ自動、過去日は手動入力
  • 体重・体脂肪率: HealthPlanet の OAuth API
  • 歩数・睡眠・HRV・Body Battery: Garmin Connect (python-garminconnect)
  • 写真・動画: 入力経路を4つ用意 — webcam で即時撮影、ローカルファイルのパス指定、スキップ、Google Photos Picker でブラウザから選択

5. 工夫した3つのポイント

5-1. Notion ページの「自由編集領域マーカー」

Daybook は同じ日に再実行しても1レコードに統合されるようにしてあります。朝に簡単に記録して、夜にコメントを追記する、みたいな使い方を想定しています。

ただ問題がありました。
Notion ページの本文を毎回まるごと自動生成すると、私が手書きで足したメモが上書きされて消える。
逆に上書きしないと、修正したい自動領域 (天気を取り直したいなど) が更新できない。

解決策は、ページの真ん中に目印のブロックを置くことでした。

[マーカー上: 自動生成領域]
  ・ 天気サマリー
  ・ 写真ブロック (横並び)
  ・ コメント
  ・ 健康データ
  ・ ニュース (折りたたみ)

✏️ ここから下は自由に編集 (Daybook再実行で消えません)  ← マーカー

[マーカー下: ユーザー自由編集領域]
  ・ 手書きの追記、思い出メモ、なんでも

Daybook の再実行時はマーカー上だけを差し替え、マーカー下のブロックはいったん全削除 → 再投稿、という流れで保全しています。
Notion API には「ブロックの差分更新」がないので、いったん全部消して入れ直す方式に落ち着きました。

5-2. 過去日記80件の冪等な移行

実はこの Daybook を作る前、別の Notion DB (T’s Journal という名前) で日記を書こうとしていた時期があり、80件分のデータが残っていました(冒頭で「3週間で詰まった」と書きましたが、それ以外にも何度か小規模なトライをしている)。当然、これは Daybook に移行したい。

migrate_journal.py という移行スクリプトを作りました。
設計の肝は 冪等性※ (べきとうせい)です。※ある操作(演算、処理、APIリクエストなど)を1回行っても複数回行っても、システムの状態や結果が同じになる性質

BigQuery に既にその日付があれば → 全部スキップ
Notion にはあるが BQ にはない    → BQ だけ追記
両方ない                        → 通常処理 (Notion作成 + BQ追記)

実はこの設計が後で効きました。
全件移行を走らせている途中で、開発環境のプロセスマネージャがプロセスを kill してしまい、Notion には57件作成済み、でも BigQuery には1件も入っていない、という不整合状態に陥ったことがあります。

普通なら手動で Notion を全削除してやり直すところですが、上の冪等ロジックのおかげで、もう一度 –execute を流すだけで「Notion にあるけど BQ にない57件は BQ だけ補填、残り18件は通常処理」と勝手に整理されました。所要2分9秒。

「最後まで完走することを期待しない」設計が結果的に運用の楽さに直結する、という良い学びになりました。

5-3. raw_payload で将来のスキーマ拡張に備える

BigQuery の列には「使うと決めた」項目だけ並べてあります。最高気温、最低気温、天気の説明文、体重、歩数、HRV、…。

でも各 API は実際にはもっと多くの情報を返しています。
湿度、体感温度、風速、気圧、UV指数、内臓脂肪、基礎代謝、安静時心拍数、…(返してくれるなら一応もらっておきたい)。

このまま列に出ているデータだけを保存していると、半年後に「やっぱり湿度も振り返りたい」と思ったときに困ります。
OpenWeatherMap の無料プランは過去データを返さないので、過去日付は再取得不可能。
実際 Google Photos の旧 Library API は2025年3月に廃止されました。API 仕様は変わるし、消えます。

そこで API のレスポンス全体を JSON のまま raw_payload列に保管 しておく設計にしました。
「列」と「JSON 倉庫」の二段構え。

即座に使う列:    date, weather_description, temp_high, temp_low, …
取りこぼし保険: raw_payload = { weather: {OWM レスポンス全体}, … }

将来「湿度の列を追加したい」となっても、JSON_VALUE(raw_payload, ‘$.weather.response.daily[0].humidity’) のような SQL で過去レコードも自動で埋められます。再リクエスト不要。(できないこともあるので)

地味な工夫ですが、長期運用前提のシステムにはこういう保険が効くはず、と信じています。

6. Google Photos Picker API の罠

写真の取り込みで一番苦労したのがここです。

iCloud 写真は使っておらず、Google Photos に集約しているので、ここから取り込みたい。
素直に「Google Photos API」で検索すると古い記事が大量にヒットしますが、ほぼ全て使えませんでした。(古い記事の SDK サンプルが軒並み動かない)

2025年3月31日に旧 Library API のスコープが廃止 されていました。
アプリが Google Photos のライブラリ全体をスキャンしたり検索したりする機能は、もう使えません。

代替として登場したのが Photos Picker API です。
考え方が変わっています。

旧モデル: アプリが Photos の中身を直接見て、欲しい写真を取得
新モデル: アプリが「Picker セッション」を作り、ユーザーがブラウザで写真を選び、選ばれた分だけアプリに渡される

ユーザーのプライバシー保護の観点では正しい変更ですが、実装はちょっと回りくどくなりました。
フローはこう。

  1. アプリが POST /sessions でセッションを作る → pickerUri URL が返ってくる
  2. その URL をブラウザで開く (アプリは webbrowser.open() で誘導)
  3. ユーザーが Photos の Picker UI で写真を選び、「完了」を押す
  4. アプリは GET /sessions/{id} を数秒間隔でポーリングし、mediaItemsSet: true を待つ
  5. 完了したら GET /mediaItems で選ばれたアイテムの一覧を取得
  6. 各アイテムの baseUrl に =d (写真) や =dv (動画) を付けてダウンロード
  7. DELETE /sessions/{id} でクリーンアップ

ハマりどころは複数ありました。
OAuth 同意画面の「テストユーザー」に認可するアカウントを追加していないと access_denied で弾かれます。
Picker API の認可スコープは1つだけ (photospicker.mediaitems.readonly) でシンプル。
baseUrl に何もつけずに GET すると小さいプレビュー画像しか落ちてこない、=d をつけて初めて原寸で取れます。

新しい API の解説記事はまだ少ないので、これから取り組む人の参考になればと思って詳しめに書きました。

7. Claude Code との協働

このプロジェクトは Claude Code と一緒に作りました。
役割はだいたいこうです。

私: 何を作るか、なぜ作るか、どう運用したいか、どこを妥協してどこを譲らないか
Claude: そこから逆算した設計の選択肢、実装、テスト、ドキュメント

特に効いたのは「先にゴール (運用イメージ) で合意してから手を動かす」リズムです。
実装が走り出してから「やっぱり違う」となると手戻りが大きいので、各フェーズの最初に簡単なテキストで「やりたいこと」「やらないこと」を明文化してから進める。
これだけで後の判断がほぼ全部勝手に決まります。(これはペアプロというより設計の話ですが)

過去日記80件の移行のときに harness が処理を kill した話 (5-2) も、人間とAIのペアならではの体験でした。
私が状況に気づき、Claude が冪等ロジックの存在を思い出して「現状を確認して再実行で復旧できる」と提案、その通りに動いた。
エラー対応をリアルタイムで会話できるのは、相棒がいる感覚に近いです。

8. これから

書き始めてからまだ1日。
これからどう運用していくかは、正直やってみないと分からない部分が多いです。

ただ、「書く」というハードルを「コマンドを叩く + 1行コメント」まで下げられたのは、過去のトライと比べると一番続きそうな気がしています(楽観)。続かなかったら、続かなかった理由をまた潰す、を繰り返すだけです。

今後の野望としては、以下を順にいじっていきたいなと考えています。

  • データポータル接続 (BigQuery を可視化して月次・年次で振り返る)
  • iOS Shortcuts で iPhone から1タップ起動
  • 月末にまとめサマリーが Slack に届く
  • Whisper で音声コメント入力

ただ、機能を足すたびに「本当にいるか」を疑う癖はつけたい。
Daybook の本質は機能の多さではなく、続けられる設計にあるはずなので。


日記は仕組みで続けられる、という説をこれから検証していきます。
1ヶ月後にこの記事の続編が書けたら、それが何よりの証拠になるはず。

どれか押してもらえると喜びます僕が
  • URLをコピーしました!

コメント

コメントする

目次