""" Browser Use REST API Server Простой HTTP API для управления браузером из n8n """ import os import asyncio from typing import Optional from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from browser_use import Agent, Browser, BrowserProfile, ChatOpenAI from browser_use.agent.views import AgentHistoryList import uvicorn from fastapi.responses import HTMLResponse # Настройки Remote Debugging REMOTE_DEBUGGING_PORT = int(os.getenv("REMOTE_DEBUGGING_PORT", "9222")) REMOTE_DEBUGGING_ENABLED = os.getenv("REMOTE_DEBUGGING_ENABLED", "false").lower() == "true" # Модель по умолчанию из переменной окружения DEFAULT_MODEL = os.getenv("LLM_MODEL_NAME", "openai/gpt-4o-mini") from contextlib import asynccontextmanager @asynccontextmanager async def lifespan(app: FastAPI): # Startup logic import sys from importlib import metadata print(f"Python version: {sys.version}") # Автоматический запуск Chrome при старте, если включен Remote Debugging if REMOTE_DEBUGGING_ENABLED: print("Remote Debugging enabled, starting Chrome on startup...") # Выведем IP адреса для отладки import socket try: hostname = socket.gethostname() ip_address = socket.gethostbyname(hostname) print(f"Container IP: {ip_address}") except: pass # Запускаем Chrome asyncio.create_task(run_browser_task("startup", "about:blank", 1, None)) yield # Shutdown logic (if needed) app = FastAPI(title="Browser Use API", version="1.0.0", lifespan=lifespan) # Модели запросов class TaskRequest(BaseModel): task: str max_steps: Optional[int] = 20 model: Optional[str] = None class TaskResponse(BaseModel): task_id: str status: str result: Optional[str] = None error: Optional[str] = None # Хранилище задач tasks = {} @app.post("/api/tasks", response_model=TaskResponse) async def create_task(request: TaskRequest): """Создать новую задачу браузера""" import uuid task_id = str(uuid.uuid4())[:8] tasks[task_id] = { "status": "pending", "task": request.task, "result": None } # Запускаем задачу в фоне asyncio.create_task(run_browser_task(task_id, request.task, request.max_steps, request.model)) return TaskResponse( task_id=task_id, status="pending" ) async def run_browser_task(task_id: str, task: str, max_steps: int, model: str): """Выполнение задачи браузера""" browser = None try: if task_id in tasks: tasks[task_id]["status"] = "running" # Настройка LLM через OpenRouter api_key = os.getenv("OPENROUTER_API_KEY", "") if not api_key: if task_id != "startup": raise ValueError("OPENROUTER_API_KEY not set") api_key = "dummy" # Читаем модель из переменной окружения в момент выполнения actual_model = model if model else os.getenv("LLM_MODEL_NAME", "openai/gpt-4o-mini") # Используем ChatOpenAI с base_url для OpenRouter llm = ChatOpenAI( model=actual_model, base_url="https://openrouter.ai/api/v1", api_key=api_key ) # Настройки браузера # Если включен remote debugging - запускаем Chrome вручную и подключаемся через CDP if REMOTE_DEBUGGING_ENABLED: import subprocess import glob import urllib.request import sys cdp_url = f"http://127.0.0.1:{REMOTE_DEBUGGING_PORT}" # 1. Проверяем, не запущен ли уже Chrome с CDP print(f"[{task_id}] Checking if Chrome is already running with CDP at {cdp_url}...") chrome_already_running = False try: response = urllib.request.urlopen(f"{cdp_url}/json/version", timeout=2) if response.status == 200: print(f"[{task_id}] Chrome is already running, connecting to existing instance.") chrome_already_running = True except Exception: print(f"[{task_id}] Chrome not detected at {cdp_url}. Starting new instance...") if not chrome_already_running: # Список возможных путей к Chrome chrome_exe = None possible_paths = [ "/usr/bin/google-chrome", "/usr/bin/chromium", "/usr/bin/chromium-browser", "/root/.cache/ms-playwright/chromium-*/chrome-linux64/chrome", "/ms-playwright/*/chrome-linux/chrome", ] for path_pattern in possible_paths: matches = glob.glob(path_pattern) if matches: chrome_exe = matches[0] break if not chrome_exe: import shutil chrome_exe = shutil.which("google-chrome") or shutil.which("chromium") or shutil.which("chromium-browser") if not chrome_exe: # В образе Playwright путь обычно такой: chrome_exe = "/usr/bin/google-chrome" print(f"[{task_id}] Using Chrome executable: {chrome_exe}") # Используем постоянную директорию для сохранения сессий (логинов) user_data_dir = "/app/browser-data/main_profile" os.makedirs(user_data_dir, exist_ok=True) # Запускаем Chrome с remote debugging chrome_cmd = [ chrome_exe, f"--remote-debugging-port={REMOTE_DEBUGGING_PORT}", "--remote-debugging-address=0.0.0.0", f"--user-data-dir={user_data_dir}", "--remote-allow-origins=*", "--headless=new", "--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--no-first-run", "--no-default-browser-check", "--disable-extensions", "about:blank" ] print(f"[{task_id}] Executing command: {' '.join(chrome_cmd)}") # Запускаем процесс log_file = open(f"/tmp/chrome_{task_id}.log", "w") process = subprocess.Popen( chrome_cmd, stdout=log_file, stderr=log_file, start_new_session=True ) # Ждем пока Chrome запустится и CDP будет готов cdp_ready = False for i in range(30): # Ждем до 30 секунд await asyncio.sleep(1) # Проверка порта import socket for host in ['127.0.0.1', '0.0.0.0', ip_address]: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: if s.connect_ex((host, REMOTE_DEBUGGING_PORT)) == 0: print(f"[{task_id}] Port {REMOTE_DEBUGGING_PORT} is OPEN on {host}!") else: print(f"[{task_id}] Port {REMOTE_DEBUGGING_PORT} is CLOSED on {host}!") try: response = urllib.request.urlopen(f"{cdp_url}/json/version", timeout=2) if response.status == 200: cdp_ready = True print(f"[{task_id}] CDP is ready at {cdp_url}") break except Exception: if i % 5 == 0: print(f"[{task_id}] Waiting for CDP at {cdp_url}... (attempt {i})") if not cdp_ready: with open(f"/tmp/chrome_{task_id}.log", "r") as f: print(f"[{task_id}] Chrome Logs:\n{f.read()}") if task_id != "startup": raise RuntimeError(f"Failed to start Chrome or CDP not ready after 30s at {cdp_url}") # Подключаемся через CDP browser = Browser(cdp_url=cdp_url) else: # Обычный запуск browser_profile = BrowserProfile( user_data_dir="/app/browser-data/main_profile", profile_directory="Default", headless=True, keep_alive=True, ) browser = Browser(browser_profile=browser_profile) # Если это был просто запуск при старте - выходим if task_id == "startup": print("[startup] Chrome started successfully and ready for connections.") return # Создание агента agent = Agent( task=task, llm=llm, browser=browser, ) # Выполнение задачи history: AgentHistoryList = await agent.run(max_steps=max_steps) # Получение результата result = history.final_result() if task_id in tasks: tasks[task_id]["status"] = "completed" tasks[task_id]["result"] = result except Exception as e: print(f"[{task_id}] Error: {e}") if task_id in tasks: tasks[task_id]["status"] = "failed" tasks[task_id]["error"] = str(e) finally: # Не закрываем браузер pass @app.get("/api/tasks/{task_id}", response_model=TaskResponse) async def get_task(task_id: str): """Получить статус/результат задачи""" if task_id not in tasks: raise HTTPException(status_code=404, detail="Task not found") task = tasks[task_id] return TaskResponse( task_id=task_id, status=task["status"], result=task.get("result"), error=task.get("error") ) @app.get("/api/tasks") async def list_tasks(): """Получить список всех задач""" return { "tasks": [ { "task_id": task_id, "status": task["status"], "task": task.get("task", ""), "result": task.get("result"), "error": task.get("error") } for task_id, task in tasks.items() ] } @app.get("/api/health") async def health_check(): """Проверка здоровья сервера""" return { "status": "healthy", "service": "browser-use-api", "version": "1.0.0" } # Web UI - Главная страница WEB_UI_HTML = """ Browser Use API

Browser Use API

REST API для автоматизации браузера с помощью AI

Быстрые ссылки

Открыть Dashboard

API Endpoints

Пример использования

curl -X POST http://localhost:8383/api/tasks \\
  -H "Content-Type: application/json" \\
  -d '{"task": "Перейди на example.com", "max_steps": 20}'
""" DASHBOARD_HTML = """ Browser Use Dashboard

Browser Use Dashboard

Создать задачу

Задачи

""" @app.get("/", response_class=HTMLResponse) async def root(): """Главная страница""" return HTMLResponse(content=WEB_UI_HTML) @app.get("/dashboard", response_class=HTMLResponse) async def dashboard(): """Dashboard""" return HTMLResponse(content=DASHBOARD_HTML) if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8383)