0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Gemini API Wrapper 開発者向け完全ガイド

Last updated at Posted at 2025-12-15

😓 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. 概要とアーキテクチャ
  2. サーバーサイド実装 (server.js)
  3. ブラウザ拡張機能の実装 (content.js)
  4. 通信フロー詳細
  5. 高度なトピックとトラブルシューティング

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を同期的に見せるための待機ロジックを持っています。

  1. 接続確認: browserSocket が存在するか確認します。
  2. リクエストID生成: 複数のリクエストを識別するため requestId を生成します。
  3. 指令送信: browserSocket.emit('input_prompt', ...) で拡張機能へ送信します。
  4. 回答待機: 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のステートが更新されず、送信ボタンが有効にならない場合があります。

本ラッパーでは以下の戦略を採用しています:

  1. execCommand: document.execCommand('insertText', false, text)
    • ブラウザ標準の編集コマンドを使用するため、最も自然なイベントが発生します。
  2. Trusted Types対策:
    • Googleのセキュリティポリシーにより innerHTML などの操作がブロックされる場合があるため、textContent へのフォールバックを用意しています。
  3. イベント発火:
    • 入力後に必ず input イベントを bubbles: true で発火させ、UIフレームワークに変更を通知します。

3.3. 画像の注入(Image Injection)

クリップボード操作はブラウザのセキュリティ制限が厳しいため、複数の戦略を順に試行します。

  1. Clipboard API: navigator.clipboard.write(ユーザー操作権限がある場合)
  2. Synthetic Paste: ClipboardEvent('paste', ...) を作成し、DataTransfer オブジェクトを含めて発火。
  3. Synthetic Drop: DragEvent('drop', ...) でファイルをドロップしたように見せかける。

3.4. 回答完了の検知(Response Detection)

LLMの回答はストリーミングされるため、「いつ回答が終わったか」の判定が重要です。

waitForResponse() 関数はポーリング(setInterval)を行い、以下の条件で完了を判定します:

  1. 停止ボタンの消失: 「生成を停止」ボタンが表示されていない。
  2. 完了シグナルの出現:
    • 回答欄の下に「コピーボタン (span[data-mat-icon-name="content_copy"])」が出現したか確認します。これは生成完了の最も確実なシグナルです。
  3. 安定化:
    • テキストの長さが一定時間変化せず、かつ一定の時間が経過した場合。

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構造を変更します。動かなくなった場合、以下の手順で修正します:

  1. ブラウザで gemini.google.com を開き、DevTools (F12) を起動。
  2. 入力欄や送信ボタンを「要素の選択」ツールで調査。
  3. aria-labeldata-* 属性など、変化しにくい属性を探す。
  4. content.js 内の document.querySelector を更新。

5.3. リクエストのタイムアウト

LLMの生成が遅い場合、サーバー側の API_RESPONSE_TIMEOUT_MS(デフォルト240秒)に達する可能性があります。環境変数でこの値を調整可能です。

# Windows (PowerShell)
$env:API_RESPONSE_TIMEOUT_MS="300000"; node server.js
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?