Браузерный агент за 30 минут — Playwright + Claude
Парсинг сайтов через regex и cheerio — каменный век. JavaScript-сайты не парсятся, формы требуют скриптов, капчи ломают всё. Playwright — это полноценный браузер которым управляешь из кода. Подключаем к нему Claude как «мозги» — получаем агента который смотрит на страницу глазами, кликает, печатает, перемещается. То, что раньше требовало дни программирования, теперь делается за вечер.
Зачем именно Playwright
Playwright — это headless browser от Microsoft (форк Chromium с API сверху). Альтернативы: Puppeteer (Google, очень похож), Selenium (древний, медленный). Playwright выигрывает в трёх вещах: (1) auto-wait — не нужно вручную ждать загрузки элементов; (2) нативная поддержка Chrome, Firefox и Safari через одно API; (3) встроенный screenshot, video, network mocking. Для агентов это золото: можно дать Claude скриншот страницы, попросить «куда кликнуть» и получить координаты или селектор.
Архитектура браузерного агента
Простой подход: даёшь Claude несколько инструментов — open(url), screenshot(), click(selector), type(selector, text), get_text(selector), wait_for(selector). На каждом шаге Claude получает скриншот и/или DOM, решает что делать, вызывает один из инструментов. Цикл повторяется пока задача не решена. Чтобы не отправлять весь HTML (огромный) — отдаёшь либо скриншот (vision), либо текстовое представление страницы (упрощённый DOM с интерактивными элементами).
DOM или screenshot — что отдавать модели
Два пути. (1) Текстовый DOM — парсишь страницу в плоский текст с маркерами кликабельных элементов: "[1] Login button, [2] Email input, [3] Password input". Модель работает с номерами. Дешевле, быстрее, но теряется визуальная инфа. (2) Screenshot + grounding — отправляешь PNG страницы, модель отвечает «кликни на кнопку Login в правом верхнем углу», ты ищешь ближайший button по тексту. Дороже но универсальнее. На практике — гибрид: текстовое представление + screenshot для визуальных задач.
Главные грабли
Что валит браузерных агентов чаще всего: (1) Не дождался загрузки — странные ошибки. Решение: использовать page.waitForLoadState и page.waitForSelector. (2) Динамические селекторы — сайт меняет class="x_a8sd" на каждом релизе. Решение: цепляться за data-testid, role, или text. (3) Pop-ups и cookie-баннеры — закрывают нужные элементы. Решение: на старте сессии всегда закрывать известные баннеры. (4) Anti-bot защита — Cloudflare палит headless. Решение: playwright-extra со stealth-плагином или playwright-stealth. (5) Бесконечная прокрутка — модель не знает где остановиться. Решение: явный max_scroll параметр.
Безопасность — обязательная часть
Браузерный агент == код может попасть на ЛЮБОЙ сайт. Опасности: переход по фишинг-ссылкам, ввод твоих кредов на похожем сайте, downloading malware, скачивание персональных данных третьих лиц. Минимум: (1) Whitelist доменов в инструменте open — без него отказ. (2) Никогда не давать инструменту type значения секретов — клади их в env и подставляй из инструмента fill_login(name) который тянет из vault. (3) Запускай браузер в изолированной директории (separate browser profile), не в основной. (4) Логируй ВСЕ действия для аудита.
import { chromium, Page } from 'playwright';
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const ALLOWED_DOMAINS = ['example.com', 'wikipedia.org'];
let page: Page | null = null;
async function openUrl(url: string): Promise<string> {
const host = new URL(url).hostname;
if (!ALLOWED_DOMAINS.some(d => host.endsWith(d))) {
return `Error: домен ${host} не в whitelist`;
}
if (!page) {
const browser = await chromium.launch({ headless: true });
page = await browser.newPage();
}
await page.goto(url, { waitUntil: 'domcontentloaded' });
return `Открыта страница ${url}, заголовок: ${await page.title()}`;
}
async function getText(selector: string): Promise<string> {
if (!page) return 'Error: страница не открыта';
try {
return (await page.locator(selector).first().textContent()) ?? '';
} catch (e) {
return `Error: ${(e as Error).message}`;
}
}
const tools = [
{
name: 'open_url',
description: 'Открывает URL в браузере. Whitelist доменов обязателен.',
input_schema: {
type: 'object',
properties: { url: { type: 'string' } },
required: ['url'],
},
},
{
name: 'get_text',
description: 'Возвращает текст элемента по CSS-селектору. Используй после open_url.',
input_schema: {
type: 'object',
properties: { selector: { type: 'string' } },
required: ['selector'],
},
},
];
// Дальше стандартный agent loop из урока «Цикл агента»
// На каждом step вызываем нужный tool, передаём результат обратно в Claude.Минимальный браузерный агент: открыть сайт, найти заголовок, вернуть текст