第6章 ベストプラクティス ── 実務で効くパターン
実践者の知見から
browser-useのベストプラクティスは、GitHubのIssue・Redditのr/LangChain・XのAIエンジニアスレッドに多数蓄積されている。「うまくいかなかった」事例からの教訓が多い。本章では繰り返し言及されるパターンを整理する。
ベストプラクティス1:タスク指示は「詳細に・構造化して」書く
browser-useで最もよく語られる教訓の一つが「タスク指示の書き方で成功率が大きく変わる」だ。
❌ 悪い例:曖昧・短い
agent = Agent(
task="Amazonで本を買って",
...
)
この指示では:
- どの本か不明
- 「買う」のか「カートに入れる」のか不明
- 在庫切れ・価格変動時にどうするか不明
- 複数の同名書籍があったときにどうするか不明
✅ 良い例:詳細・エッジケース込み
agent = Agent(
task="""
以下の手順でAmazon.co.jpから本を購入してください:
1. Amazon.co.jpにアクセスし、「Python機械学習プログラミング 第4版」を検索してください
2. 検索結果から正確にタイトルが一致する本を選んでください
- 複数の版がある場合は最新版を選ぶ
- Kindle版ではなく紙の本を選ぶ
3. 価格が5,000円以下であることを確認してください
- 5,000円を超える場合は購入をキャンセルし、その旨を報告してください
4. 「今すぐ買う」ボタンをクリックして購入を完了してください
5. 注文確認番号を取得して返してください
注意:ポップアップや割引オファーが表示された場合は閉じてください。
""",
...
)
構造化のポイント:
- 番号付きステップで手順を明示する
- 条件分岐をあらかじめ記述する(「〇〇の場合は△△してください」)
- エッジケースを潰しておく(ポップアップ・モーダル・複数候補)
- 完了の定義を明示する(何を返すか、何をもって成功とするか)
ベストプラクティス2:モデルは用途で選ぶ
「強いモデルを使えばよい」は誤りだ。目的に合ったモデル選択が重要だ。
| 用途 | 推奨モデル | 理由 |
|---|---|---|
| 本番・高精度が必要 | ChatBrowserUse(クラウド) | ブラウザ操作に特化・最速 |
| 本番・汎用性が必要 | Claude Sonnet 4.6 | 精度・コストのバランス |
| 複雑な判断が必要 | Claude Opus 4.6 | 最高精度・高コスト |
| 開発・テスト | Gemini 2.0 Flash | 最速・最安・十分な精度 |
| オフライン・プライバシー | Qwen 2.5:7b(Ollama) | 完全ローカル実行 |
モデルをコードから切り離して環境変数で管理するのが実運用でのベストプラクティスだ。
import os
from langchain_anthropic import ChatAnthropic
from langchain_google_genai import ChatGoogleGenerativeAI
def get_llm():
model_name = os.getenv("BROWSER_USE_MODEL", "claude-sonnet-4-6")
if model_name.startswith("claude"):
return ChatAnthropic(model=model_name)
elif model_name.startswith("gemini"):
return ChatGoogleGenerativeAI(model=model_name)
else:
raise ValueError(f"Unsupported model: {model_name}")
# 開発時:BROWSER_USE_MODEL=gemini-2.0-flash python main.py
# 本番時:BROWSER_USE_MODEL=claude-sonnet-4-6 python main.py
ベストプラクティス3:リソースを必ずクリーンアップする
await browser.close() の呼び忘れは、クラウド版では課金の継続、ローカル版ではメモリリークを引き起こす。
# ❌ 悪い例:エラー時にブラウザが閉じない
browser = Browser()
agent = Agent(task="...", llm=llm, browser=browser)
result = await agent.run()
await browser.close() # エラーが起きると実行されない
# ✅ 良い例:async with を使う(例外時も自動クローズ)
async with Browser() as browser:
agent = Agent(task="...", llm=llm, browser=browser)
result = await agent.run()
# ここで自動的にbrowser.close()が呼ばれる
# ✅ または try/finally で保証する
browser = Browser()
try:
agent = Agent(task="...", llm=llm, browser=browser)
result = await agent.run()
finally:
await browser.close() # 必ず実行される
ベストプラクティス4:max_stepsで暴走を防ぐ
browser-useはデフォルトで最大ステップ数の制限がある。しかし複雑なタスクや意図しないループに入ったとき、ステップが際限なく消費される(=LLMコストが無限に増える)リスクがある。
agent = Agent(
task="...",
llm=llm,
browser=browser,
# 本番環境では明示的に上限を設ける
max_steps=25, # デフォルトより絞る(デフォルト: 100)
timeout=300, # 5分でタイムアウト
)
ステップ数の目安:
シンプルなタスク(1サイト・3〜5操作): max_steps=10
中程度のタスク(複数ページ・判断あり): max_steps=25
複雑なタスク(複数サイト・反復あり): max_steps=50
探索的タスク(調査・リサーチ): max_steps=100
ベストプラクティス5:Human-in-the-Loop を組み込む
高リスクなアクション(購入・フォーム送信・データ変更)の前に人間の確認を挟む。
from browser_use import Agent
from browser_use.agent.views import ActionResult
async def confirm_before_purchase(action_description: str) -> bool:
"""人間に確認を求める(CLI版)"""
response = input(f"\n実行予定: {action_description}\n続行しますか? [y/n]: ")
return response.lower() == "y"
# カスタムアクションとして「購入前確認」を追加
@controller.action("購入確認")
async def purchase_with_confirmation(item_name: str, price: str):
confirmed = await confirm_before_purchase(f"{item_name}({price})を購入する")
if not confirmed:
return ActionResult(extracted_content="ユーザーがキャンセルしました", error="User cancelled")
# 実際の購入処理へ
...
クラウド版SDKでは、セッションを pause() して人間がブラウザを直接操作し、resume() でエージェントが続きを引き継ぐ機能がある(Ch.3参照)。
ベストプラクティス6:専用ブラウザプロファイルを使う
本番のChromeプロファイル(普段使いのGmail・銀行アカウント等が入っている)をbrowser-useに渡すのは危険だ。
# ❌ 危険:本番プロファイルをそのまま使う
config = BrowserConfig(
browser_binary_path="/path/to/chrome",
# デフォルトプロファイル(本番クレデンシャルが入っている)
)
# ✅ 安全:browser-use専用の別プロファイルを作成する
config = BrowserConfig(
browser_binary_path="/path/to/chrome",
new_context_config=BrowserContextConfig(
user_data_dir="/path/to/browser-use-profile", # 専用ディレクトリ
)
)
専用プロファイルの原則:
- browser-use専用のChromeプロファイルを新規作成する
- そのプロファイルには、タスクに必要な最小限のサービスのみログインする
- 本番の銀行・メール・SNSアカウントは含めない
ベストプラクティス7:ハイブリッドパターンでコストを最適化する
高頻度・大量実行のパイプラインでは、全ステップをbrowser-useに任せるとLLMコストが爆発する。
from playwright.async_api import async_playwright
from browser_use import Agent
async def process_large_batch(urls: list[str]):
"""大量URLの処理:構造化部分はPlaywright、複雑部分はbrowser-use"""
results = []
async with async_playwright() as p:
browser = await p.chromium.launch()
for url in urls:
page = await browser.new_page()
try:
# ① 高速・安定な部分はPlaywrightで処理
await page.goto(url)
title = await page.title()
meta_desc = await page.locator('meta[name="description"]').get_attribute("content")
# ② 価格・在庫のような動的・不規則なデータはAIで
# (全URLにやると高コストなので、必要な場合のみ)
if needs_ai_extraction(url):
agent = Agent(
task=f"このページから商品価格と在庫状況を取得: {url}",
llm=cheap_llm, # コスト優先のモデルを使う
)
ai_data = await agent.run()
else:
ai_data = None
results.append({
"url": url,
"title": title,
"description": meta_desc,
"ai_data": ai_data,
})
finally:
await page.close()
await browser.close()
return results
ベストプラクティス8:エラーハンドリングと再試行
browser-useのエージェントはself-healingを内蔵しているが、外部から包む再試行ロジックも有効だ。
import asyncio
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3), # 最大3回試行
wait=wait_exponential(multiplier=1, min=4, max=60), # 指数バックオフ
)
async def run_agent_with_retry(task: str, **kwargs):
async with Browser() as browser:
agent = Agent(
task=task,
llm=llm,
browser=browser,
max_steps=30,
**kwargs,
)
result = await agent.run()
# 失敗を表す文字列が含まれていたらRetryErrorにする
if "エラー" in result or "失敗" in result or "取得できません" in result:
raise ValueError(f"タスク失敗: {result}")
return result
ベストプラクティス一覧
| # | プラクティス | 適用タイミング | 効果 |
|---|---|---|---|
| 1 | タスク指示を詳細・構造化して書く | 常に | 成功率の向上 |
| 2 | モデルを用途で選ぶ | 設計時 | コスト・精度の最適化 |
| 3 | リソースを必ずクリーンアップ | 常に | 課金・メモリリーク防止 |
| 4 | max_stepsで上限を設ける | 本番環境 | コスト暴走防止 |
| 5 | 高リスクアクションにHuman確認を挟む | 書き込み操作 | 意図しない実行防止 |
| 6 | 専用ブラウザプロファイルを使う | 常に | セキュリティリスク低減 |
| 7 | ハイブリッドパターンでコスト最適化 | 高頻度実行 | LLMコスト削減 |
| 8 | 外部再試行ロジックを包む | 本番環境 | 信頼性向上 |