【🚨無職発生注意報🚨】ヒトはこうして仕事を奪われる~Browser Use Tutorial~

# はじめに
2025年はAIエージェントの年です。注目されているAIエージェントの一つが『AIが自動で自分のPC画面を操作』するBrowser Useというツールです。

https://github.com/browser-use/browser-use

### Browser Useの面白さ
Browser Useを使うと、AIが自動で自身のPC画面を操作することであらかじめ決めた目的を達成をしてくれます。
簡単な指示を出すだけで、自動でAIが色々操作してくれるのはキャッチーで衝撃的ですよね。

例えば下記のように完全自動でAIが記事を検索して記事の情報を取得してくれます。

![BrowseUseデモ動画のコピー1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3973992/6745f503-4f5e-3a39-4072-459cb46088d7.gif)


簡単な指示でAIが自分で考えて画面操作をしてくれるのは近未来感ありますよね。

しかし、現場でAIを使いこなすには「AIがすごい」のレベルではまだ足りません。
実際に触ってみて何ができるのか？逆に何が苦手なのか？という肌感覚を持つことが非常に重要です。
そこで本記事は、その肌感覚を養うために実際にBrowser Useを自分のPCで実行してみるためのチュートリアルを紹介します。

GitHub公式リポジトリでは英語で解説も少なかったので、日本語での解説や段階を踏んだ実行手順なども含めて記載しています。
Pythonに不慣れの方やサクッと試してみたい方でも取り組みやすい構成になっています。

実際に手を動かしながらBrowser Useとは何なのか？何ができるのか？などを体感し、AIエージェントの面白さを共有できれば幸いです。


### 対象読者
- 業務効率化が好きな方
- AIをビジネス活用したい方
- 生成AIに興味のある全ての方


### 記事の構成
１｜Browser Useとは？
２｜Browser Useの環境構築
３｜事例集
４｜目的別Tips
５｜おまけ


## 筆者の紹介（ニコラ）
東京大学で機械学習の研究、AIスタートアップでMLエンジニアを経験、コンサル会社で業務を担当した後に東大松尾研発のAIスタートアップを創業。
AI技術の研究開発や、大臣への生成AI活用紹介、私立高校全教師向けに生成AI研修を行うなど、研究開発・育成・リスキリングに取り組んでいる。


:::note info
[Xアカウントではビジネスマンが生成AIを使いこなすヒントを発信しています](https://x.com/Nicola_GenAI)。AIを使いこなせるようになりたい方はフォローをお願いします！
また、生成AIを使った仕事をラクにするプロダクト開発もしているので、AI時代を楽しみたい方はお待ちしています。
:::

それでは順番に解説していきます。

# Browser Useチュートリアル
## １｜Browser Useとは？
Browser Useとは、AIが自動で自分の画面を操作するためのAIツールです。
達成したい目的を指示すると、AIが自動で目的達成のための手法を細かなタスクにブレイクダウンします。
そのタスクを順番に実行することで最終的に達成したい目標に辿り着きます。
ブラウザーを操作する権限が与えられたAIエージェントのようなイメージです。

例えば、「食べログでおすすめの焼肉屋を教えてほしい」と指示すると、AIが「食べログを検索→サイトに入って"焼肉屋"でおすすめ順に絞り込む→上位表示されている焼肉屋の情報を取得して表示する」のような操作を自律的に計画して実行までしてくれます。

このような大変面白いAIツールを使いこなすために、下記具体例を交えながら解説していきます。
まずは自分のPCでBrowser Useを利用するためのセットアップから始めます。

## ２｜Browser Useの環境構築
Browser Useを動かすだけなら比較的簡単な準備で済みます。
- Pythonの準備
- コードエディタ（VSCode / Cursor等）の準備
- Pythonの仮想環境の準備
- Browser Useのインストール
- 環境変数の設定

### Pythonの準備
Browser Useの使用にはPython（ver 11.1以上？）が必要です。
バージョンが低い場合などは調整するようにしましょう。
Python公式ページや、ChatGPTにバージョン確認、変更の方法を質問すると基本的に正しいやり方を答えてくれます。

### コードエディタ（VSCode / Cursor等）の準備
普段使用しているエディタやIDEなどでOKです。
下記の記事のIDEの章などご覧ください。

https://qiita.com/Nicola_GenAI/items/04e2babe86291fc4483b#%E3%82%A8%E3%83%87%E3%82%A3%E3%82%BFide

### Pythonの仮想環境の準備
venvで仮想環境を作成しましょう。
Pythonの仮想環境を使うことで、他のプロジェクトやシステムに影響を与えずに必要なパッケージを管理できます。
下記の手順で設定可能です。

#### １｜プロジェクト用のディレクトリを作成
```bash
mkdir browser_use_project
cd browser_use_project
```

#### 仮想環境を作成
macOS / Linuxの場合：
```bash
python3 -m venv venv
```

Windowsの場合：
```bash
python -m venv venv
```

#### 仮想環境を有効化
macOS / Linuxの場合：
```bash
source venv/bin/activate
```

Windowsの場合：
```bash
venv\Scripts\activate
```

仮想環境が有効化されると、ターミナル（またはコマンドプロンプト）の先頭に (venv) と表示されるようになります！

#### 仮想環境の終了
仮想環境（venv）の使用を終了したい場合は、仮想環境を「deactivate」することで、システム全体（もしくは別途設定された環境）の Python に戻ります。

```bash
deactivate
```

### Browser Useのインストール
Browser UseをインストールしてAIに画面操作してもらう準備を完了させましょう。
仮想環境を有効にした状態で、以下のコマンドを実行します。

```bash
pip install browser-use
```

▼ 補足
- pip コマンドは、仮想環境を有効にしている場合、仮想環境内の pip が呼び出されます。
- インストール後、pip list などを実行すると browser-use が表示されるはずです。

### (オプション) Playwright のインストール
`browser-use` はPlaywrightを利用してブラウザ制御を行うことが多いです。Playwrightを使う場合は以下のようにインストールして、ブラウザを自動操作できるようにします。
- playwright install で主要なブラウザ（Chromium、Firefox, WebKit）が一括でダウンロード・セットアップされます。

```bash
pip install playwright
playwright install
```


### 環境変数の設定
.env ファイルで API キーを設定しましょう。
`browser-use`は多くの場合、LLMと連携して動作します。
OpenAI の API を使う例では、環境変数`OPENAI_API_KEY`にAPIキーをセットする必要があります。（その他のLLMモデルを使うときはそれに応じたAPIキーやエンドポイントを設定する必要があります）

#### .envファイルの作成
プロジェクトのルートディレクトリ（今回の例だと`browser_use_project`配下）に、`.env`ファイルを新規作成します。

#### APIキーの設定
使用するLLMのAPIキーを設定しましょう。OpenAI、AnthropicのAPIキーの設定例は下記です。
```
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
```

これでBrowser Useのを実際に動かすための設定が完了しました。正しく設定がされているかチェックしてみましょう！
今回はOpenAI APIを想定しています。モデルは最安の`gpt-4o-mini`を使います。

`browser_use_project/examples`というディレクトリを作成し、その配下で`test-browser-use.py`というファイルを作成しましょう。

下記のコードをコピペして実行してください。このファイルは「"Nicola_GenAI"という名前のXアカウントのプロフィールページを検索して表示する」動作をゴールにしています。
（Nicola_GenAIは、私のアカウントです）

**【API利用料を計算して表示してくれるようにセットする】**
OpenAI APIの使用料を表示したいときは、`langchain-community`をインストール必要があります。仮想環境に入っている状態で、下記をターミナルで実行してください。

```bash
pip install -U langchain-community
```

---

`test-browser-use.py`のコードは下記です。

```python
import asyncio
from langchain_openai import ChatOpenAI
from langchain.callbacks import get_openai_callback
from browser_use import Agent
from browser_use.browser.browser import Browser, BrowserConfig

async def main():
    llm = ChatOpenAI(model='gpt-4o-mini')

    browser = Browser(
        config=BrowserConfig(
            headless=False
        )
    )

    task_text = (
        "Go to google.com, search for 'Qiita AI ニコラ'. "
        "After searching, click on the top result. "
        "Finally, get the page title from that page and return me the title."
    )
    agent = Agent(
        task=task_text,
        llm=llm,
        browser=browser,
    )

    # get_openai_callback() でトークン使用量を計測
    with get_openai_callback() as cb:
        history = await agent.run()
        # コールバックが終了するタイミングで合計トークン数などを取得

    print("== Usage Info ==")
    print(f"Prompt tokens: {cb.prompt_tokens}")
    print(f"Completion tokens: {cb.completion_tokens}")
    print(f"Total tokens: {cb.total_tokens}")
    print(f"Total cost (USD): ${cb.total_cost:.5f}")

    await browser.close()

if __name__ == '__main__':
    asyncio.run(main())

```

コピペが完了したら、`browser_use_project`直下で、`python examples/test-browser-use.py`をターミナルで実行すると新たにブラウザが立ち上がり、処理が始まるはずです。

もしエラーが出た場合はその内容をChatGPTなどに質問すると解決することが多いです。
- .env に事前に OPENAI_API_KEY が設定されている必要があります。設定忘れに注意しましょう。

それでは、様々な事例を試しながらBrowser　Useについて学んでいきましょう！


:::note warn
上記のタスクを1回実行したところ、0.00444ドル（約0.67円）でした。gpt-4oを使った場合は数十円のコストになります。使用するモデルで金額がかなり変わるので利用の際はどのモデルを使っているのか意識しましょう。
:::


## ３｜事例集
ここでは、様々な Browser Useの利用例を紹介します。興味のある事例から試してみると良いでしょう。
`browser_use_project/example`フォルダにそれぞれファイルを作成し、pythonコマンドで実行すると動作します。

:::note alert
下記の事例によってAIエージェントとして使用するモデルが異なる形で設定されています。特に、`gpt-4o-mini`以外の場合はコストが比較的高額になりやすいので、どのモデルを使用するか？はご自身で検討の上ご利用ください。
全てOpenAI APIの`gpt-4o-mini`でも動作可能です。
:::

### 事例1｜Amazon検索を自動で行う
アマゾン検索で特定の商品（laptop）を検索、高評価順に並び替えて、最初に表示される商品の価格を取得する操作を行うプログラムです。
`await agent.run(max_steps=3)`で max_stepsを定義すると、エージェントが実行できる最大ステップ数を定義できます（最大ステップ数を超えてエージェントがタスクを実行しようとすると途中で強制的に処理が終了してしまうのでステップ数の定義は注意が必要です）

コード：amazon_search.py
```python
"""
Simple try of the agent.

@dev You need to add OPENAI_API_KEY to your environment variables.
"""
import os
import sys
import asyncio

# モジュール検索パスを調整して、上位ディレクトリのモジュールを読み込む
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from langchain_openai import ChatOpenAI  # OpenAIのモデルを使う
from browser_use import Agent           # ブラウザ操作用のエージェント

# GPT-4oモデルを使用するChatOpenAIインスタンスを作成
llm = ChatOpenAI(model='gpt-4o')

# タスクとLLMをセットしてエージェントを生成
agent = Agent(
    task='Go to amazon.com, search for laptop, sort by best rating, and give me the price of the first result',
    llm=llm,
)

# 非同期でエージェントを実行
async def main():
    await agent.run(max_steps=3)  # 最大3ステップまで試行
    agent.create_history_gif()    # 操作履歴をGIF化

asyncio.run(main())
```

### 事例2｜CAPTCHA解決にチャレンジ
CAPTCHA（画像認証）のあるページを開き、エージェントが自動で認証を解決しようと試みるサンプルです。
CAPTCHA の仕組みにより成功率は状況によって変わりますが、設定次第で自動テストの一部として取り込むことが可能です。

コード：examples/captcha.py
```python
"""
Simple try of the agent.

@dev You need to add OPENAI_API_KEY to your environment variables.
"""

import os
import sys

# 上位ディレクトリのモジュールを読み込めるようにパスを追加
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import asyncio

from langchain_openai import ChatOpenAI

from browser_use import Agent

# NOTE: captchas are hard. For this example it works. But e.g. for iframes it does not.
# for this example it helps to zoom in.
llm = ChatOpenAI(model='gpt-4o')  # GPT-4oモデルを使用
agent = Agent(
    task='go to https://captcha.com/demos/features/captcha-demo.aspx and solve the captcha',
    llm=llm,
)

async def main():
    # エージェントを実行し、CAPTCHAのあるページへアクセスし、解決を試みる
    await agent.run()
    input('Press Enter to exit')  # 実行後に一時停止して、ユーザーがEnterを押すまで待機

asyncio.run(main())

```

### 事例3｜予約ページの空き日程チェック
特定の予約ページへアクセスし、今月と次月の予約可能日があるかどうかを確認するシナリオです。
予約ページに行く、日付をチェックする、といった一連の動作をエージェントが自動化します。

コード：examples/check_appointment.py
```python
import asyncio
import os

import dotenv
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, SecretStr

from browser_use.agent.service import Agent
from browser_use.controller.service import Controller

dotenv.load_dotenv()  # .envファイルを読み込む

controller = Controller()

class WebpageInfo(BaseModel):
    link: str = 'https://appointment.mfa.gr/en/reservations/aero/ireland-grcon-dub/'

@controller.action('Go to the webpage', param_model=WebpageInfo)
def go_to_webpage(webpage_info: WebpageInfo):
    # WebpageInfoのlinkを返すアクション。エージェントがこの情報を使って予約ページへアクセスする
    return webpage_info.link

async def main():
    # エージェントに与えるタスク：今月と次月の空き日程を調べ、なければ「空きがない」と返す
    task = (
        'Go to the Greece MFA webpage via the link I provided you.'
        'Check the visa appointment dates. If there is no available date in this month, check the next month.'
        'If there is no available date in both months, tell me there is no available date.'
    )

    # ChatOpenAIにgpt-4o-miniモデルを指定し、APIキーは.envから取得
    model = ChatOpenAI(model='gpt-4o-mini', api_key=SecretStr(os.getenv('OPENAI_API_KEY', '')))
    agent = Agent(task, model, controller=controller, use_vision=True)

    # 予約状況を確認するタスクを実行し、結果を受け取る
    result = await agent.run()

if __name__ == '__main__':
    asyncio.run(main())
```

### 事例4｜クリップボード操作を行う
クリップボードにテキストをコピーし、別のページに移動して貼り付ける自動操作のサンプルです。
ここでは「Hello, world!」をクリップボードにコピーし、Google へアクセスした後に貼り付けを実行しています。

コード：examples/clipboard.py

```python
import os
import sys
from pathlib import Path

# ActionResult はエージェントのアクション実行結果を扱うためのクラス
from browser_use.agent.views import ActionResult

# パスを調整して上位ディレクトリからモジュールを読み込めるようにする
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio

import pyperclip  # クリップボードの内容を取得・設定できるライブラリ
from langchain_openai import ChatOpenAI

from browser_use import Agent, Controller
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use.browser.context import BrowserContext

# headless=Falseでブラウザを起動して、画面が見える状態で動きを確認できる
browser = Browser(
    config=BrowserConfig(
        headless=False,
    )
)
controller = Controller()

# クリップボードへのコピーアクション
@controller.registry.action('Copy text to clipboard')
def copy_to_clipboard(text: str):
    pyperclip.copy(text)  # pyperclip.copy でクリップボードに文字列をコピー
    return ActionResult(extracted_content=text)

# クリップボードからのペーストアクション
@controller.registry.action('Paste text from clipboard', requires_browser=True)
async def paste_from_clipboard(browser: BrowserContext):
    text = pyperclip.paste()      # クリップボードの内容を取得
    page = await browser.get_current_page()
    await page.keyboard.type(text) # ページ上でキーボード入力としてテキストをペースト
    return ActionResult(extracted_content=text)

async def main():
    # “Clipboardにコピー → Googleにアクセス → テキストをペースト” というタスクを定義
    task = 'Copy the text "Hello, world!" to the clipboard, then go to google.com and paste the text'
    model = ChatOpenAI(model='gpt-4o')  # GPT-4oモデルを指定して言語モデルを作成

    # エージェントの初期化。コピー＆ペーストのアクションやブラウザの操作を自動で行う
    agent = Agent(
        task=task,
        llm=model,
        controller=controller,
        browser=browser,
    )

    await agent.run()       # エージェントを実行（タスク内容に沿って操作する）
    await browser.close()   # 処理が終わったらブラウザを閉じる

    input('Press Enter to close...')  # コンソール画面を維持する（手動でEnterを押して終了）

if __name__ == '__main__':
    asyncio.run(main())
```

### 事例５｜カスタム出力を使った結果の取得
Hacker News の「Show HN」セクションで1番人気の投稿を探し、その結果を独自フォーマットで取得する事例です。
DoneResult というデータモデルをつくり、最終的に投稿のタイトル・URL・コメント数・投稿後の時間をまとめて表示します。

コード：examples/custom_output.py
```python
"""
Show how to use custom outputs.

@dev You need to add OPENAI_API_KEY to your environment variables.
"""

import os
import sys

# 上位ディレクトリのモジュールを読み込めるようにパスを修正
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import asyncio

from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from pydantic import BaseModel

from browser_use import ActionResult, Agent, Controller

# .envファイルから環境変数を読み込む（OPENAI_API_KEYなど）
load_dotenv()

controller = Controller()

# カスタムの出力フォーマット（Hacker News投稿の情報）
class DoneResult(BaseModel):
    post_title: str
    post_url: str
    num_comments: int
    hours_since_post: int

# タスク終了時に、出力をカスタムJSON形式として返すアクション
@controller.registry.action('Done with task', param_model=DoneResult)
async def done(params: DoneResult):
    # is_done=Trueでエージェント側に「完了」を知らせ、extracted_contentにJSONを載せる
    result = ActionResult(is_done=True, extracted_content=params.model_dump_json())
    return result

async def main():
    task = 'Go to hackernews show hn and give me the number 1 post in the list'
    model = ChatOpenAI(model='gpt-4o')
    agent = Agent(task=task, llm=model, controller=controller)

    # エージェントを実行して履歴を取得
    history = await agent.run()

    # エージェントが最終的に返した結果を取り出す
    result = history.final_result()
    if result:
        # JSON文字列をDoneResult型に変換する
        parsed = DoneResult.model_validate_json(result)
        print('--------------------------------')
        print(f'Title: {parsed.post_title}')
        print(f'URL: {parsed.post_url}')
        print(f'Comments: {parsed.num_comments}')
        print(f'Hours since post: {parsed.hours_since_post}')

if __name__ == '__main__':
    asyncio.run(main())
```

### 事例６｜カスタム System Prompt の利用
デフォルトの System Prompt に独自ルールを追加し、**「どんなタスクでも最初にWikipediaを開く」**という挙動を必ず従わせる例です。
MySystemPrompt で important_rules() をオーバーライドし、新しいルールを追加しています。

コード：examples/custom_system_prompt.py
```python
import json
import os
import sys

# システムプロンプトを含むディレクトリまでパスを通す
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import asyncio

from langchain_openai import ChatOpenAI

from browser_use import Agent, SystemPrompt

# デフォルトのSystemPromptを継承して、自分のルールを追加
class MySystemPrompt(SystemPrompt):
    def important_rules(self) -> str:
        existing_rules = super().important_rules()
        # どんなタスクでも最初にwikipedia.comを開くというルールを追加
        new_rules = 'REMEMBER the most important RULE: ALWAYS open first a new tab and go first to url wikipedia.com no matter the task!!!'
        return f'{existing_rules}\n{new_rules}'

async def main():
    task = "do google search to find images of Elon Musk's wife"
    model = ChatOpenAI(model='gpt-4o')

    # system_prompt_classにMySystemPromptを指定して、エージェントに特別ルールを適用
    agent = Agent(task=task, llm=model, system_prompt_class=MySystemPrompt)

    # 確認用にシステムプロンプトをJSONで出力（ルールが追加されているはず）
    print(
        json.dumps(
            agent.message_manager.system_prompt.model_dump(exclude_unset=True),
            indent=4,
        )
    )

    await agent.run()

if __name__ == '__main__':
    asyncio.run(main())
```


### 事例７｜ファイルアップロードを行う
test_cv.txt ファイルをフォームへ自動アップロードするサンプルです。
DOM要素検索後、set_input_files() を呼び出してファイルパスを指定すると、
実ブラウザでユーザーが手動でアップロードする操作を自動化できます。

コード：examples/file_upload.py
```python
import os
import sys
from pathlib import Path

from browser_use.agent.views import ActionResult

# ディレクトリを上にたどり、モジュール検索パスを追加
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio

from langchain_openai import ChatOpenAI

from browser_use import Agent, Controller
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use.browser.context import BrowserContext

# アップロード対象のファイルパスを定義
CV = Path.cwd() / 'examples/test_cv.txt'
import logging

logger = logging.getLogger(__name__)

# headless=FalseでブラウザをUI付きで起動し、chrome_instance_pathも指定
browser = Browser(
    config=BrowserConfig(
        headless=False,
        chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
    )
)
controller = Controller()

@controller.action('Upload file to element ', requires_browser=True)
async def upload_file(index: int, browser: BrowserContext):
    path = str(CV.absolute())
    # index番目のDOM要素を取得（ブラウザ内の要素を順番で特定）
    dom_el = await browser.get_dom_element_by_index(index)

    if dom_el is None:
        return ActionResult(error=f'No element found at index {index}')

    file_upload_dom_el = dom_el.get_file_upload_element()
    if file_upload_dom_el is None:
        return ActionResult(error=f'No file upload element found at index {index}')

    file_upload_el = await browser.get_locate_element(file_upload_dom_el)
    if file_upload_el is None:
        return ActionResult(error=f'No file upload element found at index {index}')

    try:
        # 実際にフォーム要素へファイルをアップロード
        await file_upload_el.set_input_files(path)
        msg = f'Successfully uploaded file to index {index}'
        logger.info(msg)
        return ActionResult(extracted_content=msg)
    except Exception as e:
        return ActionResult(error=f'Failed to upload file to index {index}')

# ファイルダイアログが表示された場合はEscapeを押して閉じる
@controller.action('Close file dialog', requires_browser=True)
async def close_file_dialog(browser: BrowserContext):
    page = await browser.get_current_page()
    await page.keyboard.press('Escape')

async def main():
    # 指定ページへアクセスし、複数のアップロードフィールドにファイルを送る
    task = (
        'go to https://kzmpmkh2zfk1ojnpxfn1.lite.vusercontent.net/'
        ' and upload to each upload field my file'
    )
    model = ChatOpenAI(model='gpt-4o')
    agent = Agent(
        task=task,
        llm=model,
        controller=controller,
        browser=browser,
    )

    await agent.run()
    await browser.close()

    input('Press Enter to close...')

if __name__ == '__main__':
    asyncio.run(main())
```


### 事例８｜複数企業の求人を探し、ファイルをアップロードして応募する
1. PDF形式のCVを読み込む
2. MLインターンシップを検索し、適切な求人を見つける
3. 結果をCSVに保存
4. 自動応募（フォームへのCVアップロードなど）
という一連の流れを同時並行で行うサンプルです。
コード：examples/find_and_apply_to_jobs.py

```python
"""
Find and apply to jobs.

@dev You need to add OPENAI_API_KEY to your environment variables.

Also you have to install PyPDF2 to read pdf files: pip install PyPDF2
"""

import csv
import os
import re
import sys
from pathlib import Path

# PyPDF2でPDFファイルを読み込み
from PyPDF2 import PdfReader

from browser_use.browser.browser import Browser, BrowserConfig

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio
from typing import List, Optional

from dotenv import load_dotenv
from langchain_openai import AzureChatOpenAI, ChatOpenAI
from pydantic import BaseModel, SecretStr

from browser_use import ActionResult, Agent, Controller
from browser_use.browser.context import BrowserContext

load_dotenv()
import logging

logger = logging.getLogger(__name__)
controller = Controller()
CV = Path.cwd() / 'cv_04_24.pdf'

# 求人情報を格納するデータクラス
class Job(BaseModel):
    title: str
    link: str
    company: str
    fit_score: float
    location: Optional[str] = None
    salary: Optional[str] = None

# CSVファイルへ求人情報を追記
@controller.action('Save jobs to file - with a score how well it fits to my profile', param_model=Job)
def save_jobs(job: Job):
    with open('jobs.csv', 'a', newline='') as f:
        writer = csv.writer(f)
        writer.writerow([job.title, job.company, job.link, job.salary, job.location])
    return 'Saved job to file'

# CSVファイルから求人情報を読み込む
@controller.action('Read jobs from file')
def read_jobs():
    with open('jobs.csv', 'r') as f:
        return f.read()

# CV（PDF）を読み込み、テキストをエージェントのメモリに取り込む
@controller.action('Read my cv for context to fill forms')
def read_cv():
    pdf = PdfReader(CV)
    text = ''
    for page in pdf.pages:
        text += page.extract_text() or ''
    logger.info(f'Read cv with {len(text)} characters')
    return ActionResult(extracted_content=text, include_in_memory=True)

# アップロード要素にCV（PDF）をセットして応募する
@controller.action('Upload cv to element - call this function to upload if element is not found, try with different index of the same upload element', requires_browser=True)
async def upload_cv(index: int, browser: BrowserContext):
    path = str(CV.absolute())
    dom_el = await browser.get_dom_element_by_index(index)
    if dom_el is None:
        return ActionResult(error=f'No element found at index {index}')

    file_upload_dom_el = dom_el.get_file_upload_element()
    if file_upload_dom_el is None:
        return ActionResult(error=f'No file upload element found at index {index}')

    file_upload_el = await browser.get_locate_element(file_upload_dom_el)
    if file_upload_el is None:
        return ActionResult(error=f'No file upload element found at index {index}')

    try:
        await file_upload_el.set_input_files(path)
        msg = f'Successfully uploaded file to index {index}'
        logger.info(msg)
        return ActionResult(extracted_content=msg)
    except Exception as e:
        return ActionResult(error=f'Failed to upload file to index {index}')

# ブラウザの設定（Chromeパスやセキュリティ無効化など）
browser = Browser(
    config=BrowserConfig(
        chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
        disable_security=True,
    )
)

async def main():
    # 事前に.envファイルから環境変数をロード
    load_dotenv()

    # ground_taskで大まかなタスク内容を指定
    ground_task = (
        'You are a professional job finder. '
        '1. Read my cv with read_cv'
        'find ml internships in and save them to a file'
        'search at company:'
    )

    # 複数の企業名を追加して、並列でタスク実行
    tasks = [
        ground_task + '\n' + 'Google',
        # ground_task + '\n' + 'Amazon',
        # ...続々と企業を追加可能
    ]

    # AzureChatOpenAIを利用してエージェントを作成
    model = AzureChatOpenAI(
        model='gpt-4o',
        api_version='2024-10-21',
        azure_endpoint=os.getenv('AZURE_OPENAI_ENDPOINT', ''),
        api_key=SecretStr(os.getenv('AZURE_OPENAI_KEY', '')),
    )

    agents = []
    for task in tasks:
        agent = Agent(task=task, llm=model, controller=controller, browser=browser)
        agents.append(agent)

    # gatherで複数エージェントを並行して実行
    await asyncio.gather(*[agent.run() for agent in agents])

if __name__ == '__main__':
    asyncio.run(main())
```


### 事例９｜複数タブを開いて操作する
複数のタブを開き、特定ページを表示後、最初のタブに戻って処理を止めるサンプルです。
ブラウザ操作を並列ではなく「タブ」で切り替えて管理します。

コード：examples/multi-tab_handling.py
```python
"""
Simple try of the agent.

@dev You need to add OPENAI_API_KEY to your environment variables.
"""
import os
import sys

# モジュールパスを上位に追加して、必要なクラスをインポート可能に
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import asyncio

from langchain_openai import ChatOpenAI

from browser_use import Agent

# ChatOpenAIでGPT-4oモデルを使用
llm = ChatOpenAI(model='gpt-4o')

# エージェントのタスク：エロン・マスク、トランプ、スティーブ・ジョブズのページを
# それぞれ別タブで開き、最後に最初のタブに戻って停止
agent = Agent(
    task='open 3 tabs with elon musk, trump, and steve jobs, then go back to the first and stop',
    llm=llm,
)

async def main():
    # エージェント実行。タスク通りにブラウザを操作してくれる
    await agent.run()

# エントリーポイント：非同期処理で main() を実行
asyncio.run(main())
```

### 事例10｜複数のエージェントが同じブラウザを共有する
1つのブラウザコンテキスト（タブやセッション情報）を複数のAIエージェント間で共有し、連続的・協調的な操作を行うサンプルです。
agent1 が開いたページを agent2 が確認するといった流れが可能になります。
自動テスト・情報収集などで有効？

コード：examples/multiple_agents_same_browser.py

```python
import os
import sys

# ChatOpenAIを使ってGPT-4oモデルを呼び出し
from langchain_openai import ChatOpenAI

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import asyncio

from browser_use import Agent, Browser, Controller

async def main():
    # ブラウザインスタンスを作成（contextを共有できるよう管理）
    browser = Browser()

    # 新しいブラウザコンテキストを作り、with文で管理
    async with await browser.new_context() as context:
        model = ChatOpenAI(model='gpt-4o')

        # エージェント1：Metaの歴史ページを開く + ランダムなWikipedia記事を開く
        agent1 = Agent(
            task='Open 2 tabs with wikipedia articles about the history of the meta and one random wikipedia article.',
            llm=model,
            browser_context=context,
        )

        # エージェント2：既に開いているWikipediaタブのタイトルを取得する
        agent2 = Agent(
            task='Considering all open tabs give me the names of the wikipedia article.',
            llm=model,
            browser_context=context,
        )

        await agent1.run()  # 最初のエージェントがタブを開く
        await agent2.run()  # 次のエージェントがそれらのタブ情報を利用

asyncio.run(main())

```

### 事例１１｜処理完了後にメール通知を送る
タスク完了をトリガーにして、結果をメール送信するサンプルです。
ここでは Done with task アクション内で Gmail API（yagmail）を使用し、タスク終了時にメールを送信します。
yagmailでのメール送信では、Gmailのアプリパスワード設定が必要になります。通常のGmailパスワードは使用できない点に注意。

コード：examples/notification.py

```python
import os
import sys

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import asyncio

from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from pydantic import BaseModel

from browser_use import ActionResult, Agent, Controller

load_dotenv()

controller = Controller()

@controller.registry.action('Done with task ')
async def done(text: str):
    import yagmail

    # Gmailアカウントでのメール送信にはApp Passwordが必要
    # https://support.google.com/accounts/answer/185833
    yag = yagmail.SMTP('your_email@gmail.com', 'your_app_password')
    # メールの内容を作成し送信
    yag.send(
        to='recipient@example.com',
        subject='Test Email',
        contents=f'result\n: {text}',
    )

    return ActionResult(is_done=True, extracted_content='Email sent!')

async def main():
    task = 'go to brower-use.com and then done'
    model = ChatOpenAI(model='gpt-4o')
    agent = Agent(task=task, llm=model, controller=controller)

    await agent.run()

if __name__ == '__main__':
    asyncio.run(main())
```

### 事例１２｜並列エージェントで同時に複数のタスクを実行
複数の検索タスク（天気、暗号通貨価格、NASA画像など）を同時並行で走らせるサンプルです。
asyncio.gather() を使うことで、エージェントが立ち上がるたびにブラウザ操作も並行実行されます。

コード：examples/parallel_agents.py

```python
import os
import sys

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio

from langchain_openai import ChatOpenAI

from browser_use.agent.service import Agent
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use.browser.context import BrowserContextConfig

# ブラウザ設定を指定 (headless=Falseでブラウザ画面を表示)
browser = Browser(
    config=BrowserConfig(
        disable_security=True,
        headless=False,
        new_context_config=BrowserContextConfig(save_recording_path='./tmp/recordings'),
    )
)
llm = ChatOpenAI(model='gpt-4o')

async def main():
    # 複数のタスク（例：天気検索、Reddit確認、Bitcoin価格チェック等）を準備
    tasks = [
        'Search Google for weather in Tokyo',
        'Check Reddit front page title',
        'Look up Bitcoin price on Coinbase',
        'Find NASA image of the day',
		# 'Check top story on CNN',
		# 'Search latest SpaceX launch date',
		# 'Look up population of Paris',
		# 'Find current time in Sydney',
		# 'Check who won last Super Bowl',
		# 'Search trending topics on Twitter',
        # 他にもタスクを追加可
    ]

    # 各タスクに対応するエージェントを作成し、一斉に実行
    agents = [Agent(task=task, llm=llm, browser=browser) for task in tasks]
    await asyncio.gather(*[agent.run() for agent in agents])

    # 追加のタスクを別途実行する例
    agentX = Agent(
        task='Go to apple.com and return the title of the page',
        llm=llm,
        browser=browser,
    )
    await agentX.run()

    await browser.close()

if __name__ == '__main__':
    asyncio.run(main())
```

### 事例１３｜実際のChromeブラウザ（デバッグモード）を動かす
Chromeをデバッグモードで立ち上げ、Google Docs へアクセスして新規文書を作成、内容を入力してPDFとして保存する…といった高度な操作をAIエージェントが行う例です。
🚨 デバッグモードでChromeを開くために、一度現在開いているChromeを全て閉じる必要がある点に注意が必要です。

コード：examples/real_browser.py

```python
import os
import sys
from pathlib import Path

from browser_use.agent.views import ActionResult

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio

from langchain_openai import ChatOpenAI

from browser_use import Agent, Controller
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use.browser.context import BrowserContext

# headless=Falseかつchrome_instance_pathを指定し、デバッグモードでChromeを起動
browser = Browser(
    config=BrowserConfig(
        headless=False,
        # MacのChromeパスを直接指定（OSに合わせて変更）
        chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
    )
)
controller = Controller()

async def main():
    # Docs上で「お礼の手紙」を書いてPDF保存するタスク
    task = f'In docs.google.com write my Papa a quick thank you for everything letter \n - Magnus'
    task += f' and save the document as pdf'

    model = ChatOpenAI(model='gpt-4o')
    agent = Agent(
        task=task,
        llm=model,
        controller=controller,
        browser=browser,
    )

    await agent.run()
    await browser.close()

    input('Press Enter to close...')

if __name__ == '__main__':
    asyncio.run(main())

```

### 事例１４｜結果の履歴やエラー内容を解析する
タスク実行中の履歴（モデルが考えたステップ、エラー、最終URLなど）をまとめて取得し、後から検証する例です。
Web操作ログをテストやデバッグに活用できます。

コード：examples/result_processing.py

```python
import os
import sys
from pprint import pprint

from browser_use.browser.browser import Browser, BrowserConfig
from browser_use.browser.context import (
    BrowserContext,
    BrowserContextConfig,
    BrowserContextWindowSize,
)

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio

from langchain_openai import ChatOpenAI

from browser_use import Agent
from browser_use.agent.views import AgentHistoryList
from browser_use.controller.service import Controller

llm = ChatOpenAI(model='gpt-4o')
browser = Browser(
    config=BrowserConfig(
        headless=False,
        disable_security=True,
        extra_chromium_args=['--window-size=2000,2000'],
    )
)

async def main():
    # 新しいブラウザコンテキストで実行履歴を記録
    async with await browser.new_context(
        config=BrowserContextConfig(
            trace_path='./tmp/result_processing',
            no_viewport=False,
            browser_window_size=BrowserContextWindowSize(width=1280, height=1000),
        )
    ) as browser_context:
        agent = Agent(
            task="go to google.com and type 'OpenAI' click search and give me the first url",
            llm=llm,
            browser_context=browser_context,
        )
        history: AgentHistoryList = await agent.run(max_steps=3)

        print('Final Result:')
        pprint(history.final_result(), indent=4)

        print('\nErrors:')
        pprint(history.errors(), indent=4)

        print('\nModel Outputs:')
        pprint(history.model_actions(), indent=4)

        print('\nThoughts:')
        pprint(history.model_thoughts(), indent=4)

    # 最後にブラウザを閉じる
    await browser.close()

if __name__ == '__main__':
    asyncio.run(main())
```

### 事例１５｜Hugging Faceのモデル情報をファイルに保存する
Hugging Face 上で特定のライセンス（cc-by-sa-4.0）を持つモデルを探し、並び替えをしたうえでトップ5件をファイルに保存する例です。
Save models アクションを利用して、取得したモデル情報を簡易的なテキスト形式でファイルに出力しています。

コード：examples/save_to_file_hugging_face.py
```python
import os
import sys

# sys.pathに追加して、上位ディレクトリのモジュールを読み込めるようにする
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import asyncio
from typing import List, Optional

from langchain_openai import ChatOpenAI
from pydantic import BaseModel

from browser_use.agent.service import Agent
from browser_use.controller.service import Controller

# まず Controller を初期化し、カスタムアクションを登録できるようにする
controller = Controller()

class Model(BaseModel):
    title: str
    url: str
    likes: int
    license: str

class Models(BaseModel):
    models: List[Model]

@controller.action('Save models', param_model=Models)
def save_models(params: Models):
    # 取得したモデル情報を "models.txt" に追記モードで書き込む
    with open('models.txt', 'a') as f:
        for model in params.models:
            # title, url, likes, license を簡易的なフォーマットで書き出す
            f.write(f'{model.title} ({model.url}): {model.likes} likes, {model.license}\n')

# ブラウザ操作の録画例（動画のURL）がコメントされているが、実装への影響はない
# video: https://preview.screen.studio/share/EtOhIk0P
async def main():
    # タスク：「Hugging Faceでライセンスがcc-by-sa-4.0のモデルを見つけ、ライク数の多い順に上位5件を取得し、ファイルに保存」
    task = f'Look up models with a license of cc-by-sa-4.0 and sort by most likes on Hugging face, save top 5 to file.'

    model = ChatOpenAI(model='gpt-4o')
    agent = Agent(task=task, llm=model, controller=controller)

    # エージェントを実行し、ブラウザを操作してモデルを検索・情報を取得、カスタムアクションで保存する
    await agent.run()

if __name__ == '__main__':
    # 非同期関数 main() を実行して処理をスタート
    asyncio.run(main())
```

### 事例１６｜操作履歴のトレースを保存する
ブラウザ操作の**トレース（履歴）**を、指定したフォルダ（./tmp/traces/）に保存します。
どのページへ行ったか、どんなアクションを取ったかを後から確認・分析できるようになる点が特
コード：examples/save_trace.py

```python
import os
import sys

from langchain_openai import ChatOpenAI

# 上位ディレクトリのモジュールにアクセス可能にする
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import asyncio

from browser_use.agent.service import Agent
from browser_use.browser.browser import Browser
from browser_use.browser.context import BrowserContextConfig

# GPT-4oモデルを利用
llm = ChatOpenAI(model='gpt-4o', temperature=0.0)

async def main():
    # Browserインスタンスを作成
    browser = Browser()

    # new_contextでトレースを保存するフォルダを指定
    async with await browser.new_context(
        config=BrowserContextConfig(trace_path='./tmp/traces/')
    ) as context:
        # タスク：「HackerNewsにアクセス→Appleに移動→開いているタブのタイトル全部返して」
        agent = Agent(
            task='Go to hackernews, then go to apple.com and return all titles of open tabs',
            llm=llm,
            browser_context=context,
        )
        # エージェント実行
        await agent.run()

    await browser.close()

# メイン関数を実行
asyncio.run(main())
```

### 事例１７｜ページのスクロール操作
指定したWebページを開いて上下にスクロールするサンプルです。
ページ内の特定テキストを探すために少しずつスクロールしたり、一定ピクセルずつ移動させたりできます。
コード：examples/scrolling_page.py

```python
import os
import sys

from browser_use.browser.browser import Browser, BrowserConfig

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import asyncio

from langchain_openai import ChatOpenAI

from browser_use import Agent

"""
Example: Using the 'Scroll down' action.

This script demonstrates how the agent can navigate to a webpage and scroll down the content.
If no amount is specified, the agent will scroll down by one page height.
"""

llm = ChatOpenAI(model='gpt-4o')

agent = Agent(
	# task="Navigate to 'https://en.wikipedia.org/wiki/Internet' and scroll down by one page - then scroll up by 100 pixels - then scroll down by 100 pixels - then scroll down by 10000 pixels.",

    # ここではWikipediaの「Internet」のページへ行き、目的の文字列までスクロールする
    task="Navigate to 'https://en.wikipedia.org/wiki/Internet' and to the string 'The vast majority of computer'",
    llm=llm,
    browser=Browser(config=BrowserConfig(headless=False)),
)

async def main():
    await agent.run()

if __name__ == '__main__':
    asyncio.run(main())
```

### 事例１６｜出力バリデーションを行う
エージェントの最終出力を、定義したPydanticモデル（DoneResult）でバリデーションし、形式が合わない場合はエラーを発生させるデモです。

コード：examples/validate_output.py
```python
"""
Demostrate output validator.

@dev You need to add OPENAI_API_KEY to your environment variables.
"""

import os
import sys

# 上位ディレクトリのモジュールへアクセス
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import asyncio

from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from pydantic import BaseModel

from browser_use import ActionResult, Agent, Controller

load_dotenv()

controller = Controller()

# 出力フォーマットを定義 (title, comments, hours_since_start)
class DoneResult(BaseModel):
    title: str
    comments: str
    hours_since_start: int

# タスク完了時にこのモデルと合致しない形式ならエラーになる
@controller.registry.action('Done with task', param_model=DoneResult)
async def done(params: DoneResult):
    result = ActionResult(is_done=True, extracted_content=params.model_dump_json())
    print(result)
    # 実験のため、戻り値を意図的に変な文字列にしている
    return 'blablabla'

async def main():
    task = 'Go to hackernews hn and give me the top 1 post'
    model = ChatOpenAI(model='gpt-4o')

    # validate_output=True でバリデーションを有効化
    agent = Agent(task=task, llm=model, controller=controller, validate_output=True)

    # 形式が合わないとエラーが出る
    await agent.run(max_steps=5)

if __name__ == '__main__':
    asyncio.run(main())

```

### 事例１７｜Web Voyagerエージェント（旅行サイトや予約サイト向け）
ヘッドレスブラウザの設定を細かくカスタマイズし、予約サイトやフライト検索サイトをめぐってホテル予約や航空券検索などを行うサンプルです。
- Booking.com や Googleフライトなどページ遷移が多いサイトでも、指定したタスクに従い、複数ステップをこなす想定。
- minimum_wait_page_load_time / maximum_wait_page_load_time などの時間パラメータで、ページ読み込みタイミングを調整可能です。

コード：examples/web_voyager_agent.py

```python
import os
import sys

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import asyncio
import os

from langchain_openai import AzureChatOpenAI
from pydantic import SecretStr

from browser_use.agent.service import Agent
from browser_use.browser.browser import Browser, BrowserConfig, BrowserContextConfig

# ブラウザ設定: セキュリティ無効化、ウィンドウサイズなど細かく指定
browser = Browser(
    config=BrowserConfig(
        headless=False,
        disable_security=True,
        new_context_config=BrowserContextConfig(
            disable_security=True,
            minimum_wait_page_load_time=1,
            maximum_wait_page_load_time=10,
            browser_window_size={'width': 1280, 'height': 1100},
        ),
    )
)

# AzureOpenAIモデルを使用
llm = AzureChatOpenAI(
    model='gpt-4o',
    api_version='2024-10-21',
    azure_endpoint=os.getenv('AZURE_OPENAI_ENDPOINT', ''),
    api_key=SecretStr(os.getenv('AZURE_OPENAI_KEY', '')),
)

# TASK = """
# Find the lowest-priced one-way flight from Cairo to Montreal on February 21, 2025, including the total travel time and number of stops. on https://www.google.com/travel/flights/
# """
# TASK = """
# Browse Coursera, which universities offer Master of Advanced Study in Engineering degrees? Tell me what is the latest application deadline for this degree? on https://www.coursera.org/"""

# 旅行系のタスク例：Booking.comで家族向けのホテルを探す
TASK = """
Find and book a hotel in Paris with suitable accommodations for a family of four (two adults and two children) offering free cancellation for the dates of February 14-21, 2025. on https://www.booking.com/
"""

async def main():
    agent = Agent(
        task=TASK,
        llm=llm,
        browser=browser,
        validate_output=True,  # 出力バリデーションを有効にする
    )
    # max_stepsを大きめにして余裕を持たせる（サイト操作が複雑な場合が多いため）
    history = await agent.run(max_steps=50)
    # 履歴をファイルに保存（操作ログの解析に便利）
    history.save_to_file('./tmp/history.json')

if __name__ == '__main__':
    asyncio.run(main())
```

### 事例１８｜Geminiを使った検索
ChatGoogleGenerativeAI を用いてGemini-2.0などのGoogle独自モデルをエージェントで活用し、Reddit検索や投稿取得を自動化する例です。
LLMのモデルとしてGeminiを採用してBrowser Useを実行するためのコード例です。Gemini APIは無料で利用できるのでタダで実験したい方はこちらを使ってもいいと思います（呼び出し回数制限はあります）
環境設定ファイル`.env`にGemini APIキーの設定を忘れないようにしましょう。
サンプルタスクは、「Redditを検索し、投稿をクリックし、面白いコメントを抽出する」といったものです。

コード：examples/gemini.py
```python
import asyncio
import os

from dotenv import load_dotenv
from langchain_google_genai import ChatGoogleGenerativeAI
from pydantic import SecretStr

from browser_use import Agent

load_dotenv()
# Gemini APIキーを忘れないように設定する
api_key = os.getenv('GEMINI_API_KEY')
if not api_key:
    raise ValueError('GEMINI_API_KEY is not set')

# Geminiモデルを指定
llm = ChatGoogleGenerativeAI(model='gemini-2.0-flash-exp', api_key=SecretStr(api_key))

async def run_search():
    # Redditのr/LocalLLaMAを検索し、「browser use」関連の投稿を探して一番面白いコメントを拾う
    agent = Agent(
        task=(
            'Go to url r/LocalLLaMA subreddit and search for "browser use" in the search bar '
            'and click on the first post and find the funniest comment'
        ),
        llm=llm,
        max_actions_per_step=4,
        tool_call_in_content=False,
    )

    await agent.run(max_steps=25)

if __name__ == '__main__':
    asyncio.run(run_search())
```

### 事例１９｜X（Twitter）に自動投稿するテンプレート
Xの投稿自動化です。
興味ある人も多そうなテーマですね。

- X(Twitter)にログインし、指定したユーザーにメンション付きのツイートを投稿した後、別のツイートにリプライする流れを自動化するテンプレートです。
- TwitterConfig データクラスで、使用するモデル（OpenAI API）、Chromeのパス、ツイート本文、リプライURLなどを指定しています。
- create_twitter_agent() がエージェントを生成し、自然言語でタスクを記述します。
- main() 関数で post_tweet() を呼び出し、エージェントがブラウザ操作を実行します。


コード：examples/post-twitter.py
```python
"""
X Posting Template using browser-use
----------------------------------------

This template allows you to automate posting on X using browser-use.
It supports:
- Posting new tweets
- Tagging users
- Replying to tweets

Add your target user and message in the config section.

target_user="XXXXX"
message="XXXXX"
reply_url="XXXXX"

Any issues, contact me on X @defichemist95
"""

import os
import sys
from typing import Optional
from dataclasses import dataclass
from dotenv import load_dotenv

load_dotenv()  # .envファイルから環境変数を読み込む

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import asyncio
from langchain_openai import ChatOpenAI
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use import Agent, Controller

# ============ Configuration Section ============
@dataclass
class TwitterConfig:
    """Twitterへの投稿に必要な設定をまとめるデータクラス"""

    openai_api_key: str
    chrome_path: str
    target_user: str  # 送信先ユーザー @を省いた文字列
    message: str
    reply_url: str
    headless: bool = False
    model: str = "gpt-4o-mini"
    base_url: str = "https://x.com/home"

# 実際に使う設定を指定
config = TwitterConfig(
    openai_api_key=os.getenv("OPENAI_API_KEY"),
    chrome_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", # Mac環境のChromeパス
    target_user="XXXXX",
    message="XXXXX",
    reply_url="XXXXX",
    headless=False,
)

def create_twitter_agent(config: TwitterConfig) -> Agent:
    # 指定されたモデルやAPIキーを使用して ChatOpenAI を初期化
    llm = ChatOpenAI(model=config.model, api_key=config.openai_api_key)

    # headlessモードやChromeのパスをBrowserConfigに設定
    browser = Browser(
        config=BrowserConfig(
            headless=config.headless,
            chrome_instance_path=config.chrome_path,
        )
    )

    controller = Controller()

    # ユーザー名に@を付け加えた投稿用メッセージを作成
    full_message = f"@{config.target_user} {config.message}"

    # Agentインスタンスを生成し、タスク文（自然言語）の手順をまとめる
    return Agent(
        task=f"""Navigate to Twitter and create a post and reply to a tweet. 
        
        Here are the specific steps:

        1. Go to {config.base_url}. See the text input field at the top of the page that says "What's happening?"
        2. Look for the text input field at the top of the page that says "What's happening?"
        3. Click the input field and type exactly this message:
        "{full_message}"
        4. Find and click the "Post" button (look for attributes: 'button' and 'data-testid="tweetButton"')
        5. Do not click on the '+' button which will add another tweet.
        
        6. Navigate to {config.reply_url}
        7. Before replying, understand the context of the tweet by scrolling down and reading the comments.
        8. Reply to the tweet under 50 characters.
        
        Important:
        - Wait for each element to load before interacting
        - Make sure the message is typed exactly as shown
        - Verify the post button is clickable before clicking
        - Do not click on the '+' button which will add another tweet
        """,
        llm=llm,
        controller=controller,
        browser=browser,
    )

async def post_tweet(agent: Agent):
    try:
        # 最大100ステップまで試行。投稿とリプライを自動実行
        await agent.run(max_steps=100)
        # 操作履歴をGIFファイルにする
        agent.create_history_gif()
        print("Tweet posted successfully!")
    except Exception as e:
        print(f"Error posting tweet: {str(e)}")

def main():
    # 上記の設定値からエージェントを作成
    agent = create_twitter_agent(config)
    # 非同期関数 post_tweet() を実行して投稿
    asyncio.run(post_tweet(agent))

if __name__ == "__main__":
    main()

```


## ４｜目的別Tips
「危険な操作は事前にユーザーの承認を得たい」「メールアドレスなどの情報はユーザーが手入力したい」などの目的に応じた実装コードを記載します。
あくまで簡易的な一例。ベストプラクティスを記載しているわけではないので注意。
既に上記の事例の中で登場したものがほとんどですが、目的別に切り出して整理しておきます。

### 目的１｜Browser Useの操作画面を録画する
ブラウザ操作の様子を録画して保存したい場合。デバッグやプレゼン時に「操作の流れ」を後から振り返る用途に使えます。
Agentを定義する時に`browser`を指定すれば任意のパス(ここでは`./tmp/recordings`)にAIエージェントが実行した画面操作を保存できます。

コード例：
```python
import asyncio
import os
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use.browser.context import BrowserContextConfig
from browser_use import Agent
from langchain_openai import ChatOpenAI

async def main():
    # BrowserContextConfig で録画を保存するパスを指定
    browser = Browser(
        config=BrowserConfig(
            headless=False,
            new_context_config=BrowserContextConfig(save_recording_path='./tmp/my_recordings')
        )
    )

    llm = ChatOpenAI(model='gpt-4o')
    agent = Agent(
        task='Googleで「Browser Useの使い方」を検索し、結果を表示してください。',
        llm=llm,
        browser=browser,
    )

    await agent.run()
    await browser.close()

if __name__ == '__main__':
    asyncio.run(main())

```

### 目的２｜ヘッドレスモードで使用する
ブラウザ画面を表示せず（ヘッドレス）、バックグラウンドで動作させたい場合に利用します。自動テストなどに便利です。

コード例：
```python
import asyncio
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use import Agent
from langchain_openai import ChatOpenAI

async def main():
    # ヘッドレスモードをTrueにするとブラウザを起動せずに処理
    browser = Browser(
        config=BrowserConfig(headless=True)
    )

    llm = ChatOpenAI(model='gpt-4o')
    agent = Agent(
        task='「ニコラ ai twitter」でGoogle検索してください',
        llm=llm,
        browser=browser
    )

    await agent.run()
    await browser.close()

if __name__ == '__main__':
    asyncio.run(main())

```

### 目的３｜カスタムアクションを作成する
独自のアクションを定義し、それをタスクの中から呼び出したい場合に使います。例えば、特定のURLに直接移動するなどの独自処理を行う。
`controller.action`というデコレータ（`@XXX`のようなもの）を使用すれば独自に指定したアクションを設計可能です。
設定したアクションはControllerを初期化してAgentに引数で渡しせば使えます（普通にAgentを設定する時と全く同じ）

コード例：
```python
import asyncio
from pydantic import BaseModel
from browser_use.agent.views import ActionResult
from browser_use.controller.service import Controller
from browser_use.agent.service import Agent
from langchain_openai import ChatOpenAI

controller = Controller()

class SimplePageInfo(BaseModel):
    url: str

@controller.action('Jump to MyPage', param_model=SimplePageInfo)
def jump_to_my_page(page_info: SimplePageInfo):
    """
    指定URLに直接ジャンプするカスタムアクション
    """
    return ActionResult(extracted_content=page_info.url, include_in_memory=True)

async def main():
    llm = ChatOpenAI(model='gpt-4o')
    agent = Agent(
        task="Please jump to my special page: 'https://qiita.com/Nicola_GenAI/items/04e2babe86291fc4483b'",
        llm=llm,
        controller=controller,
    )
    result = await agent.run()
    print(result)

if __name__ == '__main__':
    asyncio.run(main())

```

### 目的４｜システムプロンプトをカスタマイズする
標準のプロンプトに追加ルールを入れて、エージェントの行動を縛りたいときに使います。
クラスの継承をすれば初期設定されているプロンプトにオリジナルの情報を追加できます。
- `SystemPrompt`クラスを継承して`important_rules`メソッドをオーバーライドすればシステムプロンプトを更新できます。
- これをAgentの`system_prompt_class`に引数として渡せばOKです。

※ important_rules以外のメソッドについてもオーバーライドできますが、公式的には推奨していないようです。
私も独自利用しているBrowser Useは現時点`important_rules`以外のシステムプロンプトをいじっていません。

コード例：
```python
import asyncio
import json
from langchain_openai import ChatOpenAI
from browser_use import Agent, SystemPrompt

class CustomSystemPrompt(SystemPrompt):
    def important_rules(self) -> str:
        # 親クラスの既存ルールを取得
        base_rules = super().important_rules()
        # 新たに追加したいルールを追記
        custom_rule = "最優先ルール: いかなるタスクでも Qiita にアクセスして最初の情報を確認すること。"
        return f"{base_rules}\n{custom_rule}"

async def main():
    llm = ChatOpenAI(model='gpt-4o')
    agent = Agent(
        task="Search for 'ニコラ AI' on Google",
        llm=llm,
        system_prompt_class=CustomSystemPrompt,
    )

    # 実際に生成されたシステムプロンプトを確認
    print(json.dumps(
        agent.message_manager.system_prompt.model_dump(exclude_unset=True),
        indent=4,
        ensure_ascii=False
    ))

    await agent.run()

if __name__ == '__main__':
    asyncio.run(main())
```

### 目的５｜CAPTCHAを解く
CAPTCHAが出るページを自動操作したい場合の一例。成功率は環境やCAPTCHAの種類により大きく変動するため注意。
Agentの引数である`task`に指定すればキャプチャ解読に挑戦してくれます。
※ 必ずしも解けるわけではありません。全自動で進めるよりかは、手動入力に切り替えた方がいいと思います。

コード例：
```python
import asyncio
from langchain_openai import ChatOpenAI
from browser_use import Agent

async def main():
    llm = ChatOpenAI(model='gpt-4o')
    agent = Agent(
        task='Open https://captcha.com/demos/features/captcha-demo.aspx and try to solve the captcha',
        llm=llm,
    )
    await agent.run()
    input('Press Enter to exit')

if __name__ == '__main__':
    asyncio.run(main())
```

### 目的６｜出力をカスタマイズする
タスク完了時（または任意の時点）に、LLMが取得した情報を整形して出力したい場合に使います。
デフォルトだと、`agent.run()`を実行したら LLM が吐いた文章がそのまま表示されます。
自由に変更したいときは、`controller.registry.action`デコレータをでタスク完了時のカスタムアクションを作成するといいです。

コード例：
```python
import asyncio
from pydantic import BaseModel
from browser_use.agent.views import ActionResult
from browser_use.controller.service import Controller
from browser_use.agent.service import Agent
from langchain_openai import ChatOpenAI

controller = Controller()

class HNPostInfo(BaseModel):
    title: str
    url: str
    points: int

@controller.registry.action('Finish with HN data', param_model=HNPostInfo)
async def finish(params: HNPostInfo):
    """
    タスク完了時にHNの投稿データをJSONで返すアクション
    """
    return ActionResult(is_done=True, extracted_content=params.model_dump_json())

async def main():
    llm = ChatOpenAI(model='gpt-4o')
    agent = Agent(
        task='Xの"@Nicola_GenAI"というアカウントのプロフィールページに遷移して、アカウント名、Bio、URLを教えて',
        llm=llm,
        controller=controller
    )
    history = await agent.run()
    data_str = history.final_result()

    # 出力をパースして表示
    if data_str:
        post_info = HNPostInfo.model_validate_json(data_str)
        print(f"Title: {post_info.title}\nURL: {post_info.url}\nPoints: {post_info.points}")

if __name__ == '__main__':
    asyncio.run(main())

```

### 目的７｜ブラウザ上のデータをコピペする
あるページからテキストをコピーし、別ページに貼り付ける操作を自動化したい場合に使います。

コード例：
```python
import asyncio
import pyperclip
from browser_use.agent.views import ActionResult
from browser_use.controller.service import Controller
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use.browser.context import BrowserContext
from browser_use import Agent

controller = Controller()

@controller.registry.action('Copy text to clipboard')
def copy_text(text: str):
    """クリップボードに文字列をコピーする"""
    pyperclip.copy(text)
    return ActionResult(extracted_content=text)

@controller.registry.action('Paste text from clipboard', requires_browser=True)
async def paste_text(browser: BrowserContext):
    """クリップボードの文字列をブラウザ上でペースト入力"""
    text = pyperclip.paste()
    page = await browser.get_current_page()
    await page.keyboard.type(text)
    return ActionResult(extracted_content=text)

async def main():
    # headless=Falseで、目視確認しながらコピー＆ペーストの自動化をテスト
    browser = Browser(config=BrowserConfig(headless=False))
    agent = Agent(
        task=(
            "Go to wikipedia.org, copy the first paragraph, "
            "then go to deepl.com and paste the text to see its translation."
        ),
        browser=browser
    )

    await agent.run()
    await browser.close()

if __name__ == '__main__':
    asyncio.run(main())
```

### 目的８｜ユーザーに入力を求める
途中でユーザーの手動入力が必要なケース（パスワード入力や追加の検索ワードなど）に使います。
人間が途中で介在することでタスク全体の成功確度も上昇します。

コード例：
```python
import asyncio
from browser_use.agent.views import ActionResult
from browser_use.controller.service import Controller
from browser_use import Agent
from langchain_openai import ChatOpenAI

controller = Controller()

@controller.action('Wait for user input')
def ask_user(question: str):
    """
    タスクの途中でユーザーの入力を取得する
    """
    answer = input(f"{question}\n>>> ")
    return ActionResult(extracted_content=answer, include_in_memory=True)

async def main():
    llm = ChatOpenAI(model='gpt-4o')
    agent = Agent(
        task="Go to google.com. Then ask the user: 'どんなキーワードで検索しますか？'",
        llm=llm,
        controller=controller,
    )
    await agent.run()

if __name__ == '__main__':
    asyncio.run(main())
```

### 目的９｜既存ブラウザを使用する
Googleドキュメントの使用や、Chrome等のブラウザでのログインが必要な場合など、既にログイン済みのブラウザなどを再利用したい時があります。
`BrowserConfig`に既存のブラウザのパスを指定すれば、Chromeのデバッグモードを起動して、同じユーザープロファイルを使うことができます。
注意
1. 事前に使用するブラウザ（Chrome等）を閉じておく必要があります
2. アカウントにログインした上で処理を実行するため、危険が伴なうケースがあります。使用の際は自己責任でお願いします。

コード例：
```python
import asyncio
import os
from browser_use.browser.browser import Browser, BrowserConfig
from browser_use import Agent
from langchain_openai import ChatOpenAI

async def main():
    browser = Browser(
        config=BrowserConfig(
            headless=False,
            # Chromeをデバッグモードで起動するパス（例: MacOSのChromeパス）
            chrome_instance_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
        )
    )

    llm = ChatOpenAI(model='gpt-4o')
    agent = Agent(
        task='Please open docs.google.com and create a new document titled "Browser Useの実験"',
        llm=llm,
        browser=browser,
    )

    await agent.run()
    await browser.close()

if __name__ == '__main__':
    asyncio.run(main())

```

### 目的１０｜実行ステップの上限を設定する
長すぎるステップを回避したり、API料金を抑えるためにステップ数を制限したい場合に利用します。
`agent.run(max_steps=5)` のように設定できます。AIエージェントがこのステップ数を超えて操作を実行しようとしても強制的に処理が終了するので注意が必要です。

コード例：
```python
import asyncio
from browser_use import Agent
from langchain_openai import ChatOpenAI

async def main():
    llm = ChatOpenAI(model='gpt-4o-mini')
    agent = Agent(
        task='Xを開き、AI系の投稿を探してください',
        llm=llm
    )
    # max_steps=3 でステップを制限
    result = await agent.run(max_steps=3)
    print(result)

if __name__ == '__main__':
    asyncio.run(main())
```

### 目的１１｜既存のアクションを上書きする
既に存在するdoneアクションなどを独自の実装で上書きし、完了時にメール送信するなどの処理を差し込む用途に使います。
`@controller.registry.action`に対して既に設定されいるアクションを書き換えることで利用可能です。
例えば任意のアクションを定義した上で、`done`アクションである`is_done=True`が含まれるActionResultを置き換えることができます。

コード例：
```python
import asyncio
import yagmail
from browser_use.controller.service import Controller
from browser_use.agent.views import ActionResult
from browser_use import Agent
from langchain_openai import ChatOpenAI

controller = Controller()

@controller.registry.action('Done with task')
async def my_custom_done_action(result_text: str):
    """
    タスク完了時にメールで通知を送信する例
    """
    try:
        yag = yagmail.SMTP('your_email@gmail.com', 'app_password')
        yag.send(
            to='recipient@example.com',
            subject='Task Completed',
            contents=f"結果: {result_text}",
        )
		# デフォルトで存在する`si_done=True`が含まれるアクションを上書きする操作
        return ActionResult(is_done=True, extracted_content="Email sent successfully!")
    except Exception as e:
        return ActionResult(error=str(e))

async def main():
    llm = ChatOpenAI(model='gpt-4o')
    agent = Agent(
        task='Go to https://browser-use.com and then done',
        llm=llm,
        controller=controller
    )
    await agent.run()

if __name__ == '__main__':
    asyncio.run(main())
```

## ５｜おまけ
### 実務でBrowser Useを使う時に意識するといいこと
ビジネスマン、クリエイターなど業務で生成AIを使う全ての方向けに意識しておくと良いポイントを記載します。
自身の過去の研究や実務での反省も踏まえて教訓じみたことを記載しています。参考までにご覧ください。
※ 「AIやAIエージェントの仕組みに慣れてみる」という目的であれば積極的にBrowesr Useを使い倒して可能性を探し出して欲しいです！

#### なんでもBrowser Useで解決しようと思わない
「自動でPC画面を自動操縦してくれるシステム」と聞くと、もうほとんどの仕事はAIに任せればいいんじゃないか？と思ってしまうかもしれません。

しかし冷静になって考えてみると「決まったロジック + LLM」で事足りることが多いです。

例えば、「Xに投稿を自動で行う」ことを考えると、XのAPIのみでも事足ります。AIで自分の投稿したい文章っぽいものを自動で投稿したい場合は「LLMが投稿文作成→X APIを使って投稿」という形で簡単に自動化できます。

実行のたびに決まった処理を順番にこなす"ワークフロー"の形であれば、わざわざAIエージェントのような仕組みを導入しないほうがコスト的にも安上がりだったりします。

#### 自動化の前に本当に必要な処理か考える
Browser UseなどAIエージェントのシステムを使って自動化を考える時に、「実は自動化しようとしているステップは『必要がないもの』」だったりします。
機械学習界隈でいう"Garbage in, garbage out"ではないですが、自動化したいプロセスがそもそも必要なプロセスなのか？と一度根本的な部分に立ち返って考えると良いです。


# おわりに
いかがだったでしょうか？
GitHub公式リポジトリは実行例は複数掲載されているものの、具体的な動きや応用はコードを読み込まないと理解が難しいです。
今回の事例解説や用途別の実装例が少しでも役に立てば幸いです。

もしさらに興味を持った方は自身でオリジナルのBrowser Useの活用事例を試してシェアしてほしいです！
変化の激しい生成AIの波を楽しんで乗りこなしていきましょう！

:::note info
[Xアカウントではビジネスマンが生成AIを使いこなすヒントを発信しています](https://x.com/Nicola_GenAI)。AIを使いこなせるようになりたい方はフォローをお願いします！
また、生成AIを使った仕事をラクにするプロダクト開発もしているので、AI時代を楽しみたい方はお待ちしています。
:::

※ 間違いやアップデートで古い情報になっている情報を見つけた場合は、お手数ですがご教示いただけますと幸いです。

**▼ 生成AIに興味を持った方はこちらもおすすめ ▼**

https://qiita.com/Nicola_GenAI/items/04e2babe86291fc4483b