Notionアーカイブのドキュメント移行 — スプレッドシート連動「リンクされた表」の貼り付けを自動化する
Googleドキュメントの API には、スプレッドシートと連動する表を作るリクエストがない。公式リファレンスを何度たどっても、埋め込みオブジェクトを生成する口は出てこない。だからこの一箇所だけは手で貼るしかない、と朝のうちは思っていた。
移行対象がどれだけ残っているかを数えた
Notion アーカイブの中に、資格予備校の税務系科目の教材テキストが3科目分残っているはずだった。ファイル名がページIDなので、名前で検索しても一件も引っかからない。本文の先頭行を検索させたら、3科目とも骨格ごと出てきた。
前日に着手した Google ドキュメントへの変換テストは、記憶のなかでは「途中で止めたもの」だった。成果物を確認させたら、オンライン講座の教材開発ページ199ページ分が全件アップロードから検証まで済んで完走していた。止まっていたのは自分の記憶のほうだった。
手順がスキルになっていなかったので、セッション履歴から読み取らせて notion-to-gdrive スキルにまとめさせた。
Drive にどう積むかを先に決めた
変換したドキュメントは、いまはマイドライブ直下に「ページ名+ユニークな値」のフォルダとして並んでいる。Notion のページ階層をそのまま Drive のフォルダ階層に写すことも考えたが、同名ページが複数あるうえ、ページIDを外すと元のページと突き合わせられなくなる。結局この形しかないと確認して、そのまま積んでいく方針にした。
Notion の表が、ドキュメントでは箇条書きに化けていた
Drive 側のドキュメントを開いて気づいた。Notion ではカテゴリ・講座名・ステータスの列が並んだテーブルビューだった箇所が、ドキュメント側では行ページへのリンクの羅列になっている。
変換スクリプトがデータベースビューを「行リンクの箇条書き」として出していたのが原因だった。原本にはスキーマも各行の値も残っているのだから、表として出せる。直させたら4ページが表に変わった。
ただ、それを開いて数秒で次の問題にぶつかった。列が5つある表を本文に置くと、ページの半分を表が占領する。読み物としてのドキュメントが読めなくなる。
表を本文から追い出して、シートへ逃がした
データベースごとにスプレッドシートを作ってデータを流し込み、ドキュメント本文には「シートを開くリンク」だけを置く構成に変えさせた。インラインデータベースは11個あった。11個ぶんのシートを作り、ドキュメント4件のリンクを差し替えた。
リンクの文字列が「一覧をスプレッドシートで開く」だけでは、どの一覧なのか本文からは分からない。データベース名を差し込む改修も入れた。ここで注意点がひとつ判明する。手で貼った表は、DOCX でドキュメントを再アップロードすると消える。だからリンク文字列の修正は、再アップロードではなく API で該当箇所だけをピンポイント編集する形にした。
手で貼るしかない、と一度は結論した
シートへのリンクは置けた。それでも欲しかったのは、スプレッドシートの中身がドキュメント上にそのまま出て、シートを直せば表も追随する、あの表だった。UI でシートをコピーしてドキュメントに貼ると「スプレッドシートにリンク」を選べる、リンクされた表である。
API にこれを作る手段はない、という回答が返ってきた。UI で手動で貼るしかない、と。
11箇所を手で貼るだけなら、30分もあれば終わる。問題はその先だった。移行はまだ途中で、変換スクリプトを直すたびにドキュメントを作り直す。作り直すたびに、手で貼った11個の表が全部消える。手作業は一度で終わらず、移行が終わるまで何度でも発生する。
最後の手段として、コンピュータ操作ができるという別の AI に、この工程だけ切り出して渡せないかまで考えた。物理的にカーソルを動かせるなら貼れるはずだ、と。
ブラウザ越しに貼る経路を潰していった
その前に、手元の Chrome DevTools MCP でどこまで行けるかを確かめさせた。シートをコピーしてドキュメントに流し込む経路を、順に試していく。
- クリップボード API でシートの中身を書き込む: 入るのはプレーンテキストだけ。連動用のデータが落ちる
- 合成した paste イベントを発火する:
consumed:false。isTrusted:falseなのでドキュメント側のハンドラが反応しない - CDP から Ctrl+V を送る: 効かない
- ドキュメントのメニュー検索(Alt+/)から「貼り付け」を実行する: 通った
途中、ペイロードを 40KB の引数としてツールに渡そうとして手を止めた。大きな引数はセッションを不安定にする。同じオリジンの localStorage を経由させる方式に切り替えて渡した。
メニュー経由で実行すると「表の貼り付け」ダイアログが出て、「スプレッドシートにリンク」が最初から選択されていた。そのまま実行すると表が入り、サイドパネルの「リンクされたオブジェクト」にシート名が登録され、更新ボタンが有効になった。連動している。手動しかないという結論が、ここで一度ひっくり返った。別の AI に渡す案も消えた。
ただし、検証が済んだところで元の状態に復元されてしまい、成功したはずの表が手元から消えた。どのページで成功したのか URL で示してほしい、と頼み直して貼り直させた。動くことを確認したら消す、という進め方は、成果が見えないぶん次の判断ができない。
位置と見た目でもう一度つまずいた
表はドキュメントの先頭に飛び込んだ。カーソルをエディタ内で動かせていないせいだった。左のアウトラインパネルから見出しへカーソルを送り込み、そこから1行ずつ下げてリンク行の末尾まで運ぶ。1行足りなければもう1行下げる。そこで改行して貼ると、狙った位置に入った。
入った表を眺めていて、また引っかかった。罫線がないので表に見えない。実体は5行2列の表として存在していて、線が引かれていないだけだった。罫線とヘッダー背景を付けさせて、199ページに一括適用した。
このとき、199件の一括処理はセッションを切り替えてサブエージェントに投げたほうがいいのではないかと聞いた。不要だと返ってきた。実体は bash スクリプト1本で、199件ループしても目に入る出力は最後の集計3行だけ、ファイルの中身も読み込まない、と。そのまま走らせて失敗ゼロで終わった。
自分が PC を触っていると壊れる
午後、残りを一気に片付けさせようとしたら、2箇所貼ったところで動かなくなった。ダイアログが出ない。作業タブがいつのまにか消えている。クリップボードの中身が別物にすり替わっている。
原因は自分だった。同じ Chrome で調べ物をしていたので、フォーカスもタブもクリップボードも取り合いになっていた。「10〜15分ほど Chrome を触らずに置いてもらえますか」と言われて、ようやく腑に落ちた。
とはいえ、人間が15分席を外すことを前提にした自動化は自動化ではない。時間をいくらかけてもいいから、内部 API まで降りて画面固有の事象に引きずられない経路を探してほしい、と指示を出した。過程も検証記録として残すことを条件にした。
内部 API まで降りた
シートのコピーが載せているカスタム MIME のブロブは 68.5KB あった。Chromium の pickle 形式(uint32 のヘッダに string16 のペア列が続く)をパースさせると、連動用のグリッドのペイロードが取り出せた。
決め手になったのは CDP の Input.dispatchKeyEvent に commands: ["paste"] を渡す経路だった。これはキー入力の再現ではなく、本物の編集コマンドを発火する。
検証の順序が良かった。まずクリップボードをわざと壊れた状態にしておき、そこから commands:["paste"] を撃つ。壊れた中身がそのまま入れば本物のペーストが起きた証拠になる、という切り分けである。結果は背面タブのまま本物のペーストとして通った。プローブに使った文字を消して復元し、次にコピー側を確かめると、こちらも背面の Sheets タブからフル MIME セットが取れた。両側ともフォーカスに依存しなくなった。
残る競合はクリップボードだけだった。自分が何かをコピーした瞬間に上書きされる。ペイロードを保存しておき、貼る直前に書き戻す機構を足したら、これも消えた。
結果、全工程が背面タブで完結するようになった。自分は同じ Chrome で普通に作業を続けたまま、裏で表が貼られていく。席を外す必要がなくなった。
残り8箇所を流して、掃除した
一気通貫にしてからも、細かい石につまずいた。
- 作業タブを自分のタブ整理で閉じてしまう → タブの消失を検知して自己修復する常駐ドライバに作り直させた
.CMDは Node から直接 spawn できない → 実績のある Python ヘルパー経由に切り替えた- 6件中5件成功。落ちた1件は、表自体は入っていてダイアログの検出がタイムアウトしただけだった
- 貼り付け後のスタイル復元が全件失敗 → import 漏れ
- 監査したら重複した表が4件見つかった。貼り付けボタンの合成クリックが2回発火していた。後ろから順に削除
最終的に4ドキュメント11箇所すべてに表が入り、重複0件、連動もパネルで確認できた。描画は自分の目で見た。リンク行のすぐ下に罫線付きの表が並び、セル内のリンクも生きている。
手順を残さないと、明日には同じ壁にぶつかる
ここまでの経路は、どれも「試して駄目だった」の積み重ねの上にある。合成イベントが弾かれることも、メニュー経由なら通ることも、記憶に置いておくと来週には消える。午前中にも同じことを聞いていた。今までのやつはスキルに残してくれていますか、と。
- 確立した貼り付け手順を
notion-to-gdriveスキルの手順書に書き直させた - 一時ディレクトリに置いていたツール一式をリポジトリへ移した。作業用の置き場は消えるので、消えて困るものを置いてはいけない
- 罫線適用のスクリプトと引数仕様もスキルへ追記した
- 残作業の内訳を計画書に書き出させた。11箇所のうち何箇所が終わっていて、次のセッションが何から始めればいいのかが1画面で読める形にした
引き継ぎメモを作らせたとき、自分の認識がひとつずれていた。次のセッションではスプレッドシートへの書き出しから始めるのだと思っていたが、書き出しは11件すべて終わっていて、残っているのは貼り付けだけだった。実際のファイルを確認させたから判明したことで、記憶のまま引き継いでいたら同じ作業を二度やらせていた。
セキュリティレビューに刺された
自動レビューが、データベース名を JavaScript に文字列連結で埋めている点を指摘してきた。ラベルは Notion のデータベース名そのままなので、名前に " や \ が混ざると生成したコードが壊れる。任意のコードが混入しうる状態だった。
JSON.stringify でリテラル化させて解決したが、その修正の過程でコメント内のプレースホルダーが先に置換されるバグが混入し、引用符とバックスラッシュ入りのラベルで往復テストさせて直した。貼り付け済みの11箇所には影響しない。直したのは今後の再実行に使うツール側だけである。
移行のベストプラクティスを聞いておいた
手を動かす一方で、そもそも Notion からの移行に定石があるはずだと思っていた。ChatGPT Pro に調べさせた。25分かけて80以上のサイトを参照して返ってきた結論は、ひとことで言えば「保存物を3つに分けろ」だった。
- 原本: 触らない。エクスポートしたまま保管する
- 検索できる静的な形: 画像も埋め込んだ形で手元に持ち、全文検索できるようにする
- 日常で開く編集可能な形: ドキュメントとスプレッドシート
自分がやっていたのは3つ目だけだった。1つ目は手元にあるが、2つ目が抜けている。
調査の途中で拾ってきた知見もひとつ効いた。リンクされた表は400セルを超えると連携が外れる。今回の11箇所はどれも収まっているが、大きいデータベースを移すときは先に分割が要る。
学び
- API に機能がないことと、実現不可能であることは違う。UI が持っている機能は、UI の内部 API を叩けば届く場合がある
- 「10〜15分触らないでください」と言われた時点が、自動化の設計が甘い合図だった。人間の待機を前提にした手順は、翌日には誰も回さない
- 合成イベントは
isTrusted:falseで弾かれるが、CDP のcommandsは編集コマンドを直接発火するので通る。ブラウザ操作の自動化で行き詰まったら、キー入力の再現ではなくコマンドの発火を探す - 貼り付けボタンの合成クリックが2回発火して重複表を4件作っていた。実行後に全件監査していなかったら、そのまま気づかず引き継いでいた
- 動作確認のために貼ったものを、確認後に消して元通りにされると、成果が手元に残らない。検証は「消して復元」ではなく「残して見せる」で締めてもらったほうが、次の判断が早い
- 残作業を記憶で語ると取り違える。書き出しは全件終わっていたのに、次のセッションでもう一度やらせるところだった
次にやること
- 全文検索できる静的な形(画像埋め込み済みのマークダウン)を手元に作る
- 移行の本線に戻り、残る科目の教材テキストを同じパイプラインに流す
- 400セル超のデータベースがあれば、貼り付け前に分割する