😓 Playwright/Puppeteerの問題点
1. Cookieのエクスポート/インポートが必要
毎回、ログイン後にCookieをファイルに保存し、次回の実行時に読み込む必要があります。
// 1回目: ログイン→Cookie保存
await context.storageState({ path: 'auth.json' });
// 2回目以降: Cookieを読み込んで再利用
const context = await browser.newContext({
storageState: 'auth.json'
});
Gemini AIの問題:
- Googleの認証は複雑(2FA、reCAPTCHA、デバイス認証など)
- Cookieだけでは不十分な場合が多い
- 毎回手動ログインが必要になる可能性大
2. 新しいブラウザインスタンスを起動
既存の「あなたが普段使っているブラウザ」ではなく、まっさらな新規ブラウザを起動します。
const browser = await chromium.launch(); // 新規起動
問題点:
- 既存のログインセッションが使えない
- 拡張機能も引き継がれない
- デバイス認証も一からやり直し
✅ あなたのSocket.io + Chrome拡張機能アプローチが優れている理由
1. 既存のブラウザセッションをそのまま使える
あなたが普段使っている Chrome
↓ (既にログイン済み)
拡張機能がDOM操作
↓
Socket.ioで外部と通信
- ログイン不要: 既にGeminiにログイン済みなので何もしなくていい
- 拡張機能も有効: 普段使っているすべての設定が残る
- デバイス認証済み: Googleに「いつもの端末」と認識される
2. CDP経由でも接続可能(部分的)
PlaywrightはCDP経由で既存のChromeに接続できますが、いくつか制約があります:
// Chromeを起動(デバッグモード)
chrome.exe --remote-debugging-port=9222
// Playwrightから接続
const browser = await chromium.connectOverCDP('http://localhost:9222');
const context = browser.contexts()[0];
制約:
- 既存のページにアクセスできない場合がある
- 新しいページを作成できないことがある
- ブラウザコンテキストの管理が不完全
🎯 結論: あなたのシステムが最適
| 方法 | 既存ログイン | 設定維持 | 複雑さ |
|---|---|---|---|
| Socket.io + 拡張機能 | ✅ 完璧 | ✅ 完璧 | 🟡 中程度 |
| Playwright(新規起動) | ❌ Cookie必須 | ❌ なし | 🟢 簡単 |
| Playwright(CDP接続) | 🟡 部分的 | 🟡 部分的 | 🔴 難しい |
| Puppeteer | ❌ Cookie必須 | ❌ なし | 🟢 簡単 |
Gemini AIのような認証が複雑なサービスには、あなたの現在のアプローチ(Socket.io + Chrome拡張機能)がベストソリューションです。
代替案としては:
- Browser Use (CDP経由で既存ブラウザに接続可能なライブラリ)
- Browser MCP (AI統合に特化)
ですが、どちらもあなたのシステムほどシンプルで確実ではありません。
本プロジェクトは、Google GeminiのWeb UI(gemini.google.com)をプログラムから操作可能なREST APIとしてラップするシステムです。
目次
1. 概要とアーキテクチャ
本システムは、「APIを持たないWebサイトを、ブラウザ拡張機能を使ってAPI化する」というアプローチを採用しています。
一般的なWebスクレイピング(Headless Browserなど)と異なり、ユーザーが普段使用しているChromeブラウザをそのまま利用するため、Googleアカウントの認証やCloudflareなどのBot対策を自然に回避できる点が特徴です。
全体構成図
主要コンポーネント
| コンポーネント | 技術スタック | 役割 |
|---|---|---|
| Local Server | Node.js, Express, Socket.io | クライアントからのRESTリクエストを受け付け、Socket.io経由でブラウザ拡張機能に指令を送る。 |
| Chrome Extension | JavaScript (Content Script) | Geminiの画面内に常駐し、入力欄へのテキスト入力、ボタンクリック、回答の監視を行う。 |
| Gemini Web UI | Angular/React等 | Googleが提供する実際のチャット画面。 |
2. サーバーサイド実装 (server.js)
server.js は、外部からのRESTリクエストと、内部のブラウザ拡張機能との間の「翻訳機」として機能します。
2.1. 通信プロトコル
-
HTTP: 外部クライアント向け (
/api/ask,/api/download) - WebSocket (Socket.io): ブラウザ拡張機能とのリアルタイム通信
2.2. リクエスト処理フロー (/api/ask)
このエンドポイントは非同期APIを同期的に見せるための待機ロジックを持っています。
-
接続確認:
browserSocketが存在するか確認します。 -
リクエストID生成: 複数のリクエストを識別するため
requestIdを生成します。 -
指令送信:
browserSocket.emit('input_prompt', ...)で拡張機能へ送信します。 -
回答待機:
Promiseを作成し、Socket.ioからのgemini_replyイベントを待ち受けます(タイムアウト付き)。
// サーバー側の待機ロジック(概念図)
const responsePromise = new Promise((resolve, reject) => {
// タイムアウト設定
const timeout = setTimeout(() => reject(...), TIMEOUT_MS);
// 一回限りのリスナー
browserSocket.once('gemini_reply', (data) => {
if (data.requestId === currentRequestId) {
clearTimeout(timeout);
resolve(data);
}
});
});
2.3. 画像プロキシ機能 (/api/download, /api/upload)
ブラウザ拡張機能内ではCORS(Cross-Origin Resource Sharing)の制約により、外部画像の取得やCanvas汚染(Tainted Canvas)の問題が発生します。これを回避するため、Node.jsサーバーが代理で画像を処理します。
- /api/download: 指定URLの画像をサーバー側でダウンロードし、Base64として返却します。
-
/api/upload: クライアントからアップロードされた画像をローカルファイルとして保存し、
http://localhost:3000/downloads/...形式のURLを発行します。
3. ブラウザ拡張機能の実装 (content.js)
content.js はこのシステムの心臓部であり、人間が行う操作をJavaScriptで模倣します。
3.1. DOM要素の特定
GeminiのWeb UIは頻繁に更新されるため、クラス名(例: .xyz123)ではなく、永続性の高い属性を使用します。
-
入力欄:
div[contenteditable="true"] -
送信ボタン:
button[aria-label*="送信"],button[aria-label*="Send"] -
回答エリア:
markdown-renderer,.model-response-text
3.2. 人間らしい入力(Human-like Input)
単に element.value = text とするだけでは、現代のSPA(Single Page Application)ではReact/Angularのステートが更新されず、送信ボタンが有効にならない場合があります。
本ラッパーでは以下の戦略を採用しています:
-
execCommand:
document.execCommand('insertText', false, text)- ブラウザ標準の編集コマンドを使用するため、最も自然なイベントが発生します。
-
Trusted Types対策:
- Googleのセキュリティポリシーにより
innerHTMLなどの操作がブロックされる場合があるため、textContentへのフォールバックを用意しています。
- Googleのセキュリティポリシーにより
-
イベント発火:
- 入力後に必ず
inputイベントをbubbles: trueで発火させ、UIフレームワークに変更を通知します。
- 入力後に必ず
3.3. 画像の注入(Image Injection)
クリップボード操作はブラウザのセキュリティ制限が厳しいため、複数の戦略を順に試行します。
-
Clipboard API:
navigator.clipboard.write(ユーザー操作権限がある場合) -
Synthetic Paste:
ClipboardEvent('paste', ...)を作成し、DataTransferオブジェクトを含めて発火。 -
Synthetic Drop:
DragEvent('drop', ...)でファイルをドロップしたように見せかける。
3.4. 回答完了の検知(Response Detection)
LLMの回答はストリーミングされるため、「いつ回答が終わったか」の判定が重要です。
waitForResponse() 関数はポーリング(setInterval)を行い、以下の条件で完了を判定します:
- 停止ボタンの消失: 「生成を停止」ボタンが表示されていない。
-
完了シグナルの出現:
- 回答欄の下に「コピーボタン (
span[data-mat-icon-name="content_copy"])」が出現したか確認します。これは生成完了の最も確実なシグナルです。
- 回答欄の下に「コピーボタン (
-
安定化:
- テキストの長さが一定時間変化せず、かつ一定の時間が経過した場合。
4. 通信フロー詳細
以下は、ユーザーが「こんにちは」と送信してから回答を得るまでの詳細なシーケンスです。
5. 高度なトピックとトラブルシューティング
5.1. Canvasの汚染(Tainted Canvas)とCORS
Geminiが生成した画像(Googleのサーバー上の画像)をJavaScriptで canvas.toDataURL() しようとすると、CORS制限により「Canvas is tainted」というエラーになり、データを取り出せない場合があります。
解決策:
拡張機能の content.js はこのエラーを検知すると、chrome.runtime.sendMessage を使ってバックグラウンドスクリプト(またはサーバー)に画像の取得を依頼します。バックグラウンドスクリプトは通常のWebページとは異なる権限で動作するため、CORSを回避してデータを取得できる場合があります(または fetch モードのサーバー経由ダウンロードを利用します)。
5.2. セレクタのメンテナンス
Googleは予告なくHTML構造を変更します。動かなくなった場合、以下の手順で修正します:
- ブラウザで
gemini.google.comを開き、DevTools (F12) を起動。 - 入力欄や送信ボタンを「要素の選択」ツールで調査。
-
aria-labelやdata-*属性など、変化しにくい属性を探す。 -
content.js内のdocument.querySelectorを更新。
5.3. リクエストのタイムアウト
LLMの生成が遅い場合、サーバー側の API_RESPONSE_TIMEOUT_MS(デフォルト240秒)に達する可能性があります。環境変数でこの値を調整可能です。
# Windows (PowerShell)
$env:API_RESPONSE_TIMEOUT_MS="300000"; node server.js