AI-агент для автоматического заполнения форм в веб-интерфейсах

Проектируем и внедряем системы искусственного интеллекта: от прототипа до production-ready решения. Наша команда объединяет экспертизу в машинном обучении, дата-инжиниринге и MLOps, чтобы AI работал не в лаборатории, а в реальном бизнесе.
Показано 1 из 1 услугВсе 1566 услуг
AI-агент для автоматического заполнения форм в веб-интерфейсах
Простая
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы
Направления AI-разработки
Этапы разработки AI-решения
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1218
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    853
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1047
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    825

AI-агент для автозаполнения веб-форм

Ручное заполнение однотипных веб-форм — регистрация поставщиков, подача заявок на тендеры, ввод данных в государственные порталы — занимает часы рабочего времени. AI-агент решает задачу: берёт данные из структурированного источника (БД, JSON, Excel), находит нужные поля на странице, правильно заполняет и отправляет. Ключевое преимущество перед обычным RPA — агент понимает смысл полей и умеет маппировать данные без жёсткого скрипта.

Агент заполнения форм с Playwright

from anthropic import Anthropic
from playwright.async_api import async_playwright, Page
import json
import asyncio
import base64
from io import BytesIO

client = Anthropic()


class FormFillerAgent:
    """AI-агент для заполнения веб-форм"""

    FORM_TOOLS = [
        {
            "name": "analyze_form",
            "description": "Анализирует структуру формы на странице: поля, типы, обязательность",
            "input_schema": {
                "type": "object",
                "properties": {
                    "include_screenshot": {"type": "boolean", "default": True},
                },
            },
        },
        {
            "name": "fill_field",
            "description": "Заполняет конкретное поле формы",
            "input_schema": {
                "type": "object",
                "properties": {
                    "selector": {"type": "string"},
                    "value": {"type": "string"},
                    "field_type": {
                        "type": "string",
                        "enum": ["text", "select", "checkbox", "radio", "date", "file"],
                        "default": "text",
                    },
                },
                "required": ["selector", "value"],
            },
        },
        {
            "name": "click_element",
            "description": "Кликает на кнопку, ссылку или элемент",
            "input_schema": {
                "type": "object",
                "properties": {
                    "selector": {"type": "string"},
                    "wait_after_ms": {"type": "integer", "default": 500},
                },
                "required": ["selector"],
            },
        },
        {
            "name": "get_form_errors",
            "description": "Проверяет наличие ошибок валидации на форме",
            "input_schema": {"type": "object", "properties": {}},
        },
        {
            "name": "take_screenshot",
            "description": "Делает скриншот текущего состояния страницы",
            "input_schema": {"type": "object", "properties": {}},
        },
        {
            "name": "submit_form",
            "description": "Отправляет форму",
            "input_schema": {
                "type": "object",
                "properties": {
                    "submit_selector": {"type": "string", "default": "[type=submit], button[type=submit], .btn-submit"},
                    "confirm_dialog": {"type": "boolean", "default": False},
                },
            },
        },
    ]

    def __init__(self, page: Page, dry_run: bool = False):
        self.page = page
        self.dry_run = dry_run  # не отправляем форму в режиме тестирования

    async def get_form_structure(self) -> dict:
        """Извлекает структуру формы через DOM"""
        return await self.page.evaluate("""
            () => {
                const form = document.querySelector('form') || document.body;
                const inputs = form.querySelectorAll('input, select, textarea');
                const fields = [];

                inputs.forEach(input => {
                    if (input.type === 'hidden') return;

                    // Пытаемся найти label для поля
                    let label = '';
                    if (input.id) {
                        const labelEl = document.querySelector(`label[for="${input.id}"]`);
                        if (labelEl) label = labelEl.textContent.trim();
                    }
                    if (!label && input.placeholder) label = input.placeholder;
                    if (!label && input.name) label = input.name;

                    const options = [];
                    if (input.tagName === 'SELECT') {
                        input.querySelectorAll('option').forEach(opt => {
                            options.push({value: opt.value, text: opt.textContent.trim()});
                        });
                    }

                    fields.push({
                        tag: input.tagName.toLowerCase(),
                        type: input.type || 'text',
                        name: input.name || '',
                        id: input.id || '',
                        label: label,
                        required: input.required,
                        placeholder: input.placeholder || '',
                        current_value: input.value || '',
                        options: options,
                        css_path: input.id ? `#${input.id}` : (input.name ? `[name="${input.name}"]` : ''),
                    });
                });

                return {fields, url: location.href, title: document.title};
            }
        """)

    async def _execute_tool(self, tool_name: str, tool_input: dict) -> str:
        if tool_name == "analyze_form":
            structure = await self.get_form_structure()
            result = {"form_structure": structure}

            if tool_input.get("include_screenshot", True):
                screenshot_bytes = await self.page.screenshot(type="png")
                result["screenshot"] = base64.b64encode(screenshot_bytes).decode()

            return json.dumps(result, ensure_ascii=False)

        elif tool_name == "fill_field":
            selector = tool_input["selector"]
            value = tool_input["value"]
            field_type = tool_input.get("field_type", "text")

            try:
                await self.page.wait_for_selector(selector, timeout=3000)

                if field_type == "select":
                    await self.page.select_option(selector, label=value)
                elif field_type == "checkbox":
                    is_checked = await self.page.is_checked(selector)
                    should_check = value.lower() in ("true", "yes", "да", "1")
                    if is_checked != should_check:
                        await self.page.click(selector)
                elif field_type == "radio":
                    await self.page.check(f'{selector}[value="{value}"]')
                elif field_type == "date":
                    await self.page.fill(selector, value)
                else:
                    await self.page.fill(selector, "")
                    await self.page.type(selector, value, delay=20)

                return f"Заполнено: {selector} = {value}"
            except Exception as e:
                return f"Ошибка заполнения {selector}: {e}"

        elif tool_name == "click_element":
            try:
                await self.page.click(tool_input["selector"])
                await asyncio.sleep(tool_input.get("wait_after_ms", 500) / 1000)
                return f"Кликнул: {tool_input['selector']}"
            except Exception as e:
                return f"Ошибка клика: {e}"

        elif tool_name == "get_form_errors":
            errors = await self.page.evaluate("""
                () => {
                    const errorElements = document.querySelectorAll(
                        '.error, .invalid-feedback, [class*="error"], [class*="invalid"], .help-block'
                    );
                    return Array.from(errorElements)
                        .map(el => el.textContent.trim())
                        .filter(t => t.length > 0);
                }
            """)
            return json.dumps({"errors": errors})

        elif tool_name == "take_screenshot":
            screenshot_bytes = await self.page.screenshot(type="png")
            return json.dumps({
                "screenshot": base64.b64encode(screenshot_bytes).decode()
            })

        elif tool_name == "submit_form":
            if self.dry_run:
                return '{"dry_run": true, "message": "Форма не отправлена (dry run mode)"}'

            try:
                selector = tool_input.get("submit_selector", "[type=submit]")
                await self.page.click(selector)

                if tool_input.get("confirm_dialog"):
                    self.page.on("dialog", lambda d: d.accept())

                await self.page.wait_for_load_state("networkidle", timeout=15000)
                return json.dumps({"submitted": True, "url": self.page.url})
            except Exception as e:
                return json.dumps({"submitted": False, "error": str(e)})

        return "Unknown tool"

    async def fill_form(self, data: dict, context: str = "") -> dict:
        """Заполняет форму данными из словаря"""
        data_text = json.dumps(data, ensure_ascii=False, indent=2)

        messages = [{
            "role": "user",
            "content": f"""Заполни форму на странице следующими данными:
{data_text}

{f"Контекст: {context}" if context else ""}

Алгоритм:
1. Сначала analyze_form чтобы увидеть структуру
2. Сопоставь данные с полями формы по смыслу (маппинг может быть нелинейным)
3. Заполни каждое поле последовательно
4. Проверь ошибки валидации
5. Если ошибок нет — отправь форму
6. Сообщи о результате"""
        }]

        steps = 0
        while steps < 25:
            response = client.messages.create(
                model="claude-sonnet-4-5",
                max_tokens=2048,
                system="Ты — агент заполнения веб-форм. Маппируй данные в поля по смыслу, а не только по имени.",
                tools=self.FORM_TOOLS,
                messages=messages,
            )

            tool_results = []
            done = False

            for block in response.content:
                if hasattr(block, "text") and block.text:
                    if "заполнена" in block.text.lower() or "отправлена" in block.text.lower():
                        done = True

                elif block.type == "tool_use":
                    result = await self._execute_tool(block.name, block.input)

                    try:
                        result_data = json.loads(result)
                        if "screenshot" in result_data or (isinstance(result_data, dict) and "form_structure" in result_data):
                            content: list = [{"type": "text", "text": json.dumps(
                                {k: v for k, v in result_data.items() if k != "screenshot"}, ensure_ascii=False
                            )}]
                            if "screenshot" in result_data:
                                content.append({
                                    "type": "image",
                                    "source": {
                                        "type": "base64",
                                        "media_type": "image/png",
                                        "data": result_data["screenshot"],
                                    }
                                })
                            tool_results.append({
                                "type": "tool_result",
                                "tool_use_id": block.id,
                                "content": content,
                            })
                        else:
                            tool_results.append({
                                "type": "tool_result",
                                "tool_use_id": block.id,
                                "content": result,
                            })
                    except (json.JSONDecodeError, TypeError):
                        tool_results.append({
                            "type": "tool_result",
                            "tool_use_id": block.id,
                            "content": result,
                        })

            if done or response.stop_reason == "end_turn":
                final_text = next((b.text for b in response.content if hasattr(b, "text")), "")
                return {"success": True, "steps": steps, "message": final_text}

            messages.append({"role": "assistant", "content": response.content})
            messages.append({"role": "user", "content": tool_results})
            steps += 1

        return {"success": False, "steps": steps}

Практический кейс: подача заявок на госзакупки

Задача: компания-поставщик участвует в 30–50 тендерах ежемесячно на Росэлторг и ЕИС. Заполнение заявки занимает 45–90 мин, часть полей повторяется (реквизиты, сертификаты, ценовое предложение).

Реализация:

  • База данных с шаблонами (реквизиты компании, стандартные документы)
  • Playwright агент обходит формы на площадках
  • Умный маппинг: «Юридический адрес» → «Адрес места нахождения» → «Место регистрации» — одно поле, разные названия
  • Dry-run режим для проверки до отправки

Результаты:

  • Время на заявку: 60 мин → 8 мин (проверка + подпись)
  • Ошибки из-за пропущенных полей: снизились на 91%
  • Самая частая проблема: CAPTCHA на некоторых площадках — решается ручным прохождением + продолжением агентом

Сроки

  • Базовый агент заполнения (одна форма): 3–5 дней
  • Адаптивный маппинг для нескольких платформ: 1–2 недели
  • Батчевая обработка очереди заявок: +1 неделя
  • Интеграция с системой хранения документов: +1 неделя