Браузерная автоматизация AI-агентами — Полный гайд | QantCore
🌐

Браузерная автоматизация AI-агентами

Превратите AI-агента в автономного веб-сёрфера: он открывает страницы, кликает по элементам, заполняет формы и извлекает данные. Гайд по интеграции Playwright с LLM, построению семантических локаторов и безопасной навигации в продакшене.

intermediate ⏱ 15 мин
Task Input "Заполни форму на сайте" Browser Agent Task Planner (LLM) Action Executor Page State DOM snapshot + history Playwright/Puppeteer click / type / extract / navigate 🛠 Screenshot | OCR | JS eval | Anti-bot stealth plugin / proxy rotation / captcha Result Data Extracted text / screenshot / JSON

# 1. Почему AI-агент + браузер — это прорыв

Традиционная автоматизация браузера (Selenium, Puppeteer, Playwright) требует жёстких CSS/XPath-селекторов, которые ломаются при любом изменении вёрстки. AI-агент с LLM смотрит на страницу «глазами человека»: он читает текст, понимает семантику элементов и принимает решения на основе контента, а не фиксированных локаторов. Это открывает сценарии, которые раньше были невозможны: автономный research в интернете, заполнение незнакомых форм, обход капч через визуальное распознавание.

# Три уровня браузерной автоматизации

# Уровень 1: Жёсткие селекторы (Selenium/Puppeteer) — хрупкие
await page.click('#checkout-button > span.btn-text')

# Уровень 2: AI-усиленные селекторы — семантический поиск
await ai_click(page, "кнопка оформления заказа")

# Уровень 3: Автономный AI-агент — полное понимание задачи
result = await browser_agent.execute(
    "Зайди на сайт авиакомпании, найди рейс Москва→Сочи на 15 июля,
     выбери самый дешёвый прямой рейс и заполни форму бронирования"
)
    

# 2. Инструменты браузерного агента: Playwright + LLM

Определяем набор инструментов, которые LLM может вызывать для взаимодействия с браузером. Каждый инструмент — это атомарное действие: навигация, клик, ввод текста, скриншот, извлечение данных. Важно предоставить модели контекст страницы (упрощённый DOM, видимый текст), чтобы она могла принимать информированные решения о следующем действии.

# browser_tools.py — инструменты AI-агента для браузера
from playwright.async_api import Page, async_playwright
import base64

BROWSER_TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "navigate",
            "description": "Перейти на указанный URL",
            "parameters": {"type": "object", "properties": {
                "url": {"type": "string", "description": "Полный URL для перехода"}
            }, "required": ["url"]}
        }
    },
    {
        "type": "function",
        "function": {
            "name": "click_element",
            "description": "Кликнуть по элементу. Используй семантическое описание (текст, aria-label, placeholder)",
            "parameters": {"type": "object", "properties": {
                "selector": {"type": "string", "description": "Текст или описание элемента"},
                "context": {"type": "string", "description": "Дополнительный контекст для уточнения"}
            }, "required": ["selector"]}
        }
    },
    {
        "type": "function",
        "function": {
            "name": "type_text",
            "description": "Ввести текст в поле ввода",
            "parameters": {"type": "object", "properties": {
                "label_or_placeholder": {"type": "string"},
                "text": {"type": "string"}
            }, "required": ["label_or_placeholder", "text"]}
        }
    },
    {
        "type": "function",
        "function": {
            "name": "extract_text",
            "description": "Извлечь весь видимый текст со страницы или конкретного элемента",
            "parameters": {"type": "object", "properties": {
                "css_selector": {"type": "string", "description": "CSS-селектор (опционально)"}
            }}
        }
    },
    {
        "type": "function",
        "function": {
            "name": "take_screenshot",
            "description": "Сделать скриншот текущей страницы для визуального анализа",
            "parameters": {"type": "object", "properties": {}}
        }
    }
]
    

# 3. Семантический поиск элементов: AI-усиленные локаторы

Ключевая инновация — отказ от хрупких CSS-селекторов в пользу семантического поиска. Мы извлекаем со страницы все интерактивные элементы с их текстом, атрибутами и позицией, формируем структурированный JSON и передаём LLM. Модель находит нужный элемент по семантическому описанию, а мы преобразуем его в Playwright-локатор. Если точного совпадения нет — включается fuzzy matching по тексту и атрибутам.

# semantic_locator.py — AI-поиск элементов на странице
from playwright.async_api import Page
from difflib import SequenceMatcher

async def extract_interactive_elements(page: Page) -> list:
    """Извлекаем все кликабельные элементы и поля ввода со страницы."""
    return await page.evaluate("""() => {
        const elements = [];
        const selectors = 'a, button, input, textarea, select, [onclick], [role="button"], [role="link"], [role="textbox"]';
        document.querySelectorAll(selectors).forEach((el, idx) => {
            const rect = el.getBoundingClientRect();
            if (rect.width === 0 || rect.height === 0) return;
            elements.push({
                index: idx,
                tag: el.tagName.toLowerCase(),
                text: (el.textContent || '').trim().slice(0, 80),
                placeholder: el.placeholder || '',
                aria_label: el.getAttribute('aria-label') || '',
                name: el.name || '',
                id: el.id || '',
                class: el.className || '',
                type: el.type || '',
                href: el.href || '',
                visible: rect.top < window.innerHeight && rect.left < window.innerWidth
            });
        });
        return elements.slice(0, 100);
    }""")

async def find_by_semantic_match(page: Page, description: str, context: str = ""):
    elements = await extract_interactive_elements(page)
    description_lower = description.lower()
    best_score = 0.0
    best_element = None

    for el in elements:
        if not el["visible"]:
            continue
        # Формируем строку поиска из всех текстовых атрибутов
        search_text = " ".join(filter(None, [
            el["text"], el["aria_label"], el["placeholder"],
            el["name"], el["id"]
        ])).lower()

        # Точное совпадение — приоритет
        if description_lower == search_text.strip():
            best_element = el
            break

        # Похожесть строк
        score = SequenceMatcher(None, description_lower, search_text).ratio()
        if description_lower in search_text:
            score += 0.5  # бонус за подстроку
        if score > best_score:
            best_score = score
            best_element = el

    if best_element:
        # Строим Playwright-локатор
        if best_element["id"]:
            locator = page.locator(f"#{best_element['id']}")
        elif best_element["text"]:
            locator = page.get_by_text(best_element["text"], exact=True)
        else:
            locator = page.locator(f"*:has-text('{description}')")
        return locator.first
    return None
    

# 4. Основной цикл браузерного агента

Агент работает в цикле: получает задачу → анализирует текущее состояние страницы → выбирает следующее действие → выполняет → оценивает результат. Цикл продолжается до достижения цели или исчерпания лимита шагов. На каждом шаге модель получает упрощённый дамп DOM (до 100 элементов), что позволяет ей ориентироваться на странице без загрузки полного HTML.

# browser_agent.py — основной цикл автономного браузерного агента
import json
from openai import OpenAI
from playwright.async_api import async_playwright

client = OpenAI()

async def browser_agent_loop(task: str, max_steps=15):
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        page = await browser.new_page()
        messages = [{
            "role": "system",
            "content": """Ты браузерный AI-агент. Твоя задача — выполнить поручение,
используя инструменты браузера. Анализируй состояние страницы
перед каждым действием. Если страница изменилась — адаптируйся.
Не делай более 3 кликов подряд без extract_text для проверки."""
        }, {"role": "user", "content": f"Задача: {task}"}]

        for step in range(max_steps):
            # Собираем контекст страницы
            elements = await extract_interactive_elements(page)
            page_text = await page.inner_text("body")
            page_text = page_text[:2000]  # обрезаем для экономии токенов
            url = page.url

            context_msg = f"""Шаг {step+1}/{max_steps}. Текущий URL: {url}
Видимый текст страницы (начало):
{page_text}

Интерактивные элементы ({len(elements)}):
{json.dumps(elements, ensure_ascii=False, indent=2)[:1500]}

Что делаем дальше?"""
            messages.append({"role": "user", "content": context_msg})

            response = client.chat.completions.create(
                model="gpt-4", messages=messages,
                tools=BROWSER_TOOLS, tool_choice="auto"
            )
            msg = response.choices[0].message

            if not msg.tool_calls:
                # Модель считает задачу выполненной
                await browser.close()
                return msg.content

            messages.append(msg)
            for tc in msg.tool_calls:
                result = await execute_browser_tool(page, tc.function.name,
                                                     json.loads(tc.function.arguments))
                messages.append({"role": "tool", "tool_call_id": tc.id,
                                 "content": json.dumps(result, ensure_ascii=False)})

        await browser.close()
        return "Достигнут лимит шагов. Задача не завершена."
    

# 5. Исполнение действий и обработка ошибок

Каждое действие агента (клик, ввод, навигация) должно быть обёрнуто в обработку ошибок. Страница может не загрузиться, элемент может отсутствовать, может появиться капча или popup. Агент должен уметь распознавать такие ситуации через анализ текста страницы и скриншотов, и адаптировать стратегию: подождать, обновить страницу, попробовать другой селектор.

# browser_executor.py — исполнение действий с обработкой ошибок
import asyncio
from playwright.async_api import Page

async def execute_browser_tool(page: Page, tool: str, args: dict) -> dict:
    try:
        if tool == "navigate":
            await page.goto(args["url"], wait_until="domcontentloaded", timeout=15000)
            await asyncio.sleep(1)  # ждём рендеринг JS
            return {"status": "ok", "url": page.url, "title": await page.title()}

        elif tool == "click_element":
            locator = await find_by_semantic_match(
                page, args["selector"], args.get("context", "")
            )
            if not locator:
                return {"status": "error", "message": "Элемент не найден на странице"}
            await locator.click(timeout=5000)
            await asyncio.sleep(0.5)
            return {"status": "ok", "clicked": args["selector"]}

        elif tool == "type_text":
            # Ищем поле по placeholder
            locator = page.get_by_placeholder(args["label_or_placeholder"])
            if not await locator.count():
                # Пробуем по label
                locator = page.get_by_label(args["label_or_placeholder"])
            await locator.fill(args["text"])
            return {"status": "ok", "filled": args["label_or_placeholder"]}

        elif tool == "extract_text":
            selector = args.get("css_selector", "body")
            text = await page.inner_text(selector)
            return {"status": "ok", "text": text[:3000]}

        elif tool == "take_screenshot":
            screenshot_bytes = await page.screenshot(type="png", full_page=False)
            return {"status": "ok", "screenshot_base64": base64.b64encode(screenshot_bytes).decode()}

    except Exception as e:
        return {"status": "error", "message": str(e), "tool": tool}
    

# 6. Продакшен: stealth-режим, прокси и обход блокировок

При автоматизации реальных сайтов вас ждут анти-бот системы: Cloudflare, DataDome, reCAPTCHA. Для продакшен-использования браузерный агент должен маскироваться под реального пользователя. Playwright поддерживает stealth-плагины, подмену fingerprint'а, прокси-ротацию и эмуляцию человеческого поведения (случайные задержки, неравномерное движение мыши). Для сложных капч интегрируются сервисы распознавания (2Captcha, Anti-Captcha).

# stealth_config.py — конфигурация stealth-браузера для продакшена
from playwright.async_api import async_playwright

async def create_stealth_browser(proxy_url: str = None):
    p = await async_playwright().start()

    browser_args = [
        "--disable-blink-features=AutomationControlled",
        "--disable-infobars",
        "--no-sandbox",
        "--window-size=1920,1080",
    ]

    launch_opts = {
        "headless": False,  # headful сложнее детектить
        "args": browser_args,
    }
    if proxy_url:
        launch_opts["proxy"] = {"server": proxy_url}

    browser = await p.chromium.launch(**launch_opts)
    context = await browser.new_context(
        user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                   "(KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
        viewport={"width": 1920, "height": 1080},
        locale="ru-RU",
        timezone_id="Europe/Moscow",
        permissions=["geolocation"],
        geolocation={"latitude": 55.7558, "longitude": 37.6173}  # Москва
    )

    # Подменяем navigator.webdriver
    await context.add_init_script("""
        Object.defineProperty(navigator, 'webdriver', { get: () => false });
        Object.defineProperty(navigator, 'plugins', { get: () => [1,2,3,4,5] });
        window.chrome = { runtime: {} };
    """)

    page = await context.new_page()
    return p, browser, page
    
✅ Итог

Мы построили AI-агента, способного автономно работать с браузером: семантический поиск элементов вместо хрупких селекторов, цикл «наблюдение-действие-оценка» с контекстом страницы, обработка ошибок и продакшен-стелс-конфигурация. Такой агент решает задачи веб-сёрфинга, которые раньше требовали ручного написания скриптов под каждый сайт. Дальнейшие направления: интеграция vision-моделей (GPT-4V) для анализа скриншотов напрямую, обучение агента на историях успешных сессий (imitation learning), и создание безопасной песочницы для изоляции браузерных сессий в контейнерах.