Geminiの会話をObsidianに自動保存するツールをPythonで作った話(失敗だらけの開発経緯)
はじめに
Gemini(GoogleのAI)の会話を手動でMarkdown保存し、Obsidianのバックアップ用フォルダに溜めていく作業が面倒だったので、「取得件数だけ指定したらあとは自動でエクスポートしてくれるツール」をPythonで作りました。
やっていることは**「RPAならすぐできるレベル」**なのに、ブラウザの起動の仕方・画像クリック・2画面・ダウンロード先・UIのトグルなどで何度もつまずきました。
この記事では、開発の経緯と失敗、どう直したかをまとめます。同じような自動化(ブラウザ+画像クリック+ファイル移動)を試す方の参考になれば幸いです。
なぜ Obsidian に、なぜ Gemini の会話を?
わたしは日々のメモ・調べもの・読んだことを Obsidian に集約しており、リンクや検索で後から参照できる「ナレッジベース」として使っています。ローカルの Markdown なので長期保存やバックアップも安心です。
vault は Google ドライブで同期しており、PC で編集した内容がスマホの Obsidian にも反映されるようにしているので、スマホ・PC の両方で同じノートを確認できます。この運用方法はかなり使えるので、いずれ記事にまとめる予定です。
その Obsidian の vault を Cursor のワークスペースに含めておくことで、コードを書くときにノートを参照したり、逆にノートからコードへ飛んだりしやすくしています。
そこで Gemini の会話も Obsidian に入れておきたい、というのが動機です。
Gemini ではアイデア出し・コードの下書き・調査メモが混ざった会話が溜まりますが、サービス側にだけ置いておくと履歴が流れていくし、過去の議論を「あのときのあの話」として参照しづらい。
エクスポートした Markdown を Obsidian に置いておけば、他のメモや 02_Literature のようなフォルダと一緒に検索・リンクでき、Cursor で作業するときにも過去の会話をすぐ参照できます。
だから「手動で保存して Obsidian に溜める」を続けていたのですが、それが面倒になって自動化ツールを作った、という流れです。
やりたかったこと
- 入力: 取得するチャット数(例: 10件)、飛ばす固定数(例: 2件)
-
処理: Gemini上で会話一覧を開き、指定件数だけ順に「エクスポート → Markdown」を実行し、ダウンロードされた
_*.mdを指定フォルダ(Obsidianの02_Literatureなど)に移動する - 運用: ユーザーは「ChromeでGeminiを開いておく」だけ。ツールは接続してクリック・キー送信・ファイル移動まで自動で行う
失敗①: ブラウザをツールから起動しようとしてハマった
やったこと
最初は「一切手動で触りたくない」と考え、PlaywrightでChromeを起動し、GeminiのURLまで開く方式にしました。
-
launch_persistent_contextで既存のユーザープロファイル(ログイン済み)を指定して起動 - すると「既存プロファイルでChromeを起動しています…」のままハング
- 別スレッドで起動すると今度は greenlet の "Cannot switch to a different thread" が出る
- 諦めて通常の
chromium.launch()にすると起動はするが、毎回ログインが必要でつらい
気づき
「起動済みのChromeは操作できない」わけではない。
pyautogui は「今フォーカスがあるウィンドウ」にキー送信・画像クリックできる。つまり ユーザーが普段どおりChromeでGeminiを開いておき、ツールは「接続」と「キー・クリック」だけやればよい という設計にすればよかった。
やった修正
- 「ユーザーがChromeでGeminiを開く」 を仕様にした
- ツールは CDP(Chrome DevTools Protocol)で既に起動しているChromeに接続するだけ
- 起動用に chrome_debug.bat(中でPowerShellの
chrome_debug.ps1を呼ぶ)を用意し、--remote-debugging-port=9222と--remote-allow-origins=*でChromeを起動してもらう - ユーザーの手順は「Chromeを全部終了 → chrome_debug.bat → Geminiを開く → gemini-exporter.bat」に一本化
失敗②: CDP接続がずっと「接続できませんでした」
やったこと
- 「Chromeを閉じてからOK」とダイアログを出し、そのあと
connect_over_cdp("http://127.0.0.1:9222")を実行 - 何度やっても 接続タイムアウト
原因
- 既に普通のChromeが起動している状態で chrome_debug.bat を実行していた
- その場合、新しいプロセスは起きず「既存のChromeにウィンドウが1つ増える」だけになり、デバッグポートは立たない
- バッチの
start "" %CHROME% --remote-debugging-port=9222 ...が、Windowsの解釈で引数がChromeに渡っていない可能性もあった
やった修正
-
chrome_debug.bat の先頭で
taskkill /F /IM chrome.exeを実行し、Chromeを確実に終了してから起動 - 起動は PowerShell に任せ、
Start-Process -ArgumentList @("--remote-debugging-port=9222", ...)で引数を確実に渡す(chrome_debug.ps1) - Chrome 109以降では
--remote-allow-origins=*がないとCDPで弾かれることがあるので追加
失敗③: 画像クリックで「pygetwindow がインストールされていません」「pyautogui がインストールされていません」
やったこと
- ウィンドウのアクティブ化に pygetwindow、キー・画像クリックに pyautogui を使用
- ログには「インストールされていません」と出るが、requirements.txt には書いてある
原因
-
gemini-exporter.bat が
call venv\Scripts\activate.batのあとpythonを呼んでおり、環境によっては 別のPython(venv でない) が使われていた - Pillow / pyscreeze / OpenCV が入っておらず、画像検索(
locateOnScreen(..., confidence=0.7))が使えなかった
やった修正
- バッチで venv の python を直接指定:
"%~dp0venv\Scripts\python.exe" -m src.main -
pip install pygetwindow pyautogui Pillow opencv-pythonを実行して依存関係をそろえた
失敗④: 「Gemini」でウィンドウを探したらバッチ窓が前面に出た
やったこと
- タイトルに「Gemini」を含むウィンドウを前面に出す処理を実装
- すると 「ウィンドウをアクティブにしました: gemini-exporter.bat」 と出て、コマンドプロンプトが前面に来てしまう
原因
- ウィンドウタイトルに「gemini」が含まれるため、バッチの窓がヒットしていた(大文字小文字を区別しない検索のため)
やった修正
- 「Gemini」かつ「Chrome」 の両方を含むウィンドウだけを対象にするように変更
- これでブラウザのウィンドウだけがアクティブになる
失敗⑤: 2画面だと画像クリックできなかった
やったこと
- 主モニターでは
locateOnScreenでMarkdownボタンがクリックできるが、サブモニターにChromeを出していると一切検出されない
原因
- Windows では ImageGrab(pyautogui のスクリーンショット)が主モニターしかキャプチャしていない ため、
region=(仮想スクリーン)を渡してもサブモニターが写っていない
やった修正
-
mss で 全モニターを1枚の画像としてキャプチャ(
sct.monitors[0])し、その画像に対してpyautogui.locate()で検索 - 見つかった座標に
mon["left"],mon["top"]のオフセットを足してクリック - 2画面でもサブモニター上のボタンをクリックできるようになった
失敗⑥: 画像クリック後、マウスがボタンの上にあると次回検出できない
やったこと
- 連続で何件もエクスポートするとき、2件目以降で「画面上でMarkdownボタン画像が見つかりません」 になることがあった
原因
- クリック後もマウスがボタンの上にあると、ホバーでボタンの色が変わり、テンプレート画像と一致しなくなる
やった修正
- Markdownボタンをクリックした直後に
pyautogui.moveRel(-300, 0)でマウスを左に300pxずらす - ボタンからマウスが外れ、次回の画像検索が安定する
失敗⑦: Pythonで開いたChromeだとダウンロードが「変な場所」「変な名前」になる
やったこと
- 手動でエクスポートすると「〇〇.md」のように保存されるが、ツール経由だと UUIDのような拡張子なしのファイル名 で、保存場所も分からない という事象が出た
原因
- CDPで接続したChromeでは、ダウンロードのデフォルト保存先が別扱いになっていた
- 拡張機能側の都合で、ファイル名が拡張子なしで渡されるケースがあった
やった修正
- 接続直後に
Page.setDownloadBehaviorでdownloadPathをconfig.jsonのダウンロードフォルダに設定 - 検出対象を「
_で始まる.md」に加え、拡張子なしでUUID形式のファイル名も「新規ダウンロード」として検出し、_<UUID>.mdにリネームしてから移動 - さらに、Chromeが保存後にリネームすることがあるため、検出後に1.5秒待ってから再度フォルダをスキャンし、存在するファイルのうち最新のものを採用するようにした
-
_move_file側でも、指定パスが存在しない場合は「直近15秒以内に更新された_*.md」を探して移動するフォールバックを入れた
失敗⑧: 会話一覧が既に開いているのにハンバーガーメニューを押して隠してしまう
やったこと
- Geminiでは、ハンバーガーメニューを押すとサイドバーが開閉する(トグル)
- 「とにかずメニューを押して会話一覧を出そう」としていたため、既に開いているときに押してしまい、一覧が隠れて次の会話をクリックできなくなる ことがあった
やった修正
-
会話一覧(
a[data-test-id="conversation"])が既に表示されているか を判定するis_chat_list_visible()を追加 - 表示されているときはハンバーガーメニューを押さない
- 判定は2回行い、その間に300ms待つ。初回だけの判定だと「まだ描画されていない」と誤判定することがあるため、二重チェックで「本当に非表示のときだけ」メニューを押すようにした
失敗⑨: ダウンロードを検出したのに「ファイルが見つかりません」で移動できない
やったこと
- 「ダウンロードを検出: _〇〇.md」とログに出るが、その直後に移動処理で FileNotFoundError になる
原因
- Chromeや拡張が、保存後にファイル名をリネームしている(例: 末尾スペースの有無、一時名→正式名)
- 検出した瞬間のパスと、実際にディスクに存在するパスが一致しなくなる
やった修正
- 新規
_*.mdを検出したあと 1.5秒待ち、フォルダを再スキャンして 実際に存在する_*.mdのうち 最も mtime が新しいもの を返すようにした - 移動処理(
_move_file)では、指定パスが存在しない場合に「同じフォルダ内の直近15秒以内に更新された_*.md」を探し、それを移動するフォールバックを追加
現在の運用フロー(まとめ)
- chrome_debug.bat を実行 → Chromeがデバッグポート9222で起動(既存Chromeは終了)
- そのChromeで Gemini のトップページ を開く(ログイン済み)
- gemini-exporter.bat を実行 → 取得件数・スキップ数(前回値がデフォルト)を入力
- ダイアログ「Chromeをデバッグモードで…」でOK → ツールがCDP接続し、会話を順に開いてショートカット送信・Markdownボタン画像クリック・ダウンロード待ち・指定フォルダへ移動を繰り返す
- 監視モードに入り、以後
_で始まる.mdがDownloadsに出現すれば同フォルダへ自動移動
使っている技術
| 用途 | 技術 |
|---|---|
| ブラウザ操作(会話一覧の取得・クリック) | Playwright(CDP接続) |
| ウィンドウの前面化 | pygetwindow |
| ショートカット送信・画像クリック | pyautogui |
| 2画面での画像検索 | mss(全モニターキャプチャ)+ pyautogui.locate |
| 画像マッチの精度 | OpenCV(confidence)、Pillow |
| ファイル監視・移動 | watchdog |
| 設定・前回値 | config.json / state.json |
まとめ
現在は安定稼働しており、手動で会話を保存して Obsidian に溜める面倒な作業が一つ消えた。
- 「全部自動でやりたい」 からブラウザ起動までツール側でやろうとして、起動まわりで何度も失敗した。「ユーザーがChromeでGeminiを開く」 にしたら、CDP接続だけになってシンプルになった。
- 起動済みChromeは pyautogui で操作できる ので、最初から「ユーザーが開く+ツールはキーとクリックだけ」でもよかった。
- 2画面・ダウンロード先・リネーム・UIのトグルなど、想定外の挙動は「検出→待機→再スキャン」「フォールバック」 でだいたい吸収できた。
- 同じような「ブラウザ+画像クリック+ファイル」の自動化をやる場合は、起動はユーザー任せ・接続と操作だけ自動 にすると、トラブルが減りやすいと思います。
失敗だらけでしたが、その分だけ「なぜ動かないか」を切り分ける経験が積めたので、記事として残しておきます。
Discussion