Суть урока
Это и есть браузерный агент — AI-система, которая управляет настоящим браузером, читает страницы так же, как человек, и принимает решения о следующем действии на основе того, что видит. Не просто скрипт с фиксированными CSS-селекторами, а настоящий интеллект, понимающий смысл страницы.
В этом уроке мы разберём, как строить таких агентов с помощью Browserbase и Stagehand, поймём, чем они отличаются от классической автоматизации, и напишем живой агент для мониторинга цен конкурентов.
Ключевые концепции
- Браузерный агент vs классическая автоматизация — AI понимает смысл страницы, а не просто ищет CSS-селекторы
- Browserbase — облачная инфраструктура браузеров для AI-агентов с антидетект-функциями
- Stagehand — SDK от Browserbase поверх Playwright с методами
act(),extract(),observe(), управляемыми LLM - Session replay — запись и воспроизведение сессий для дебага агентов
- Антидетект-техники — stealth-режим, жилые прокси, обход CAPTCHA
- Этика скрапинга — публичные данные vs приватные, Terms of Service, PII
- Монетизация — услуги по сбору и мониторингу данных (формат услуги, а не гарантия дохода)
Теория
Почему браузерные агенты — это не Selenium 2.0
Когда в 2004 году появился Selenium, автоматизация веба строилась по одному принципу: программист руками находит CSS-селектор или XPath-путь к нужному элементу и говорит скрипту: «нажми на #buy-button». Это работало, пока сайты не меняли вёрстку. Но стоило разработчикам поменять класс кнопки — скрипт ломался.
Puppeteer (2017) и Playwright (2020) сделали этот подход быстрее и надёжнее, но принцип остался тем же: жёсткие инструкции на основе структуры DOM. Если страница изменилась — автоматизация сломана.
Браузерный агент думает иначе. Вместо «нажми на элемент с id=submit» он получает задачу «оформи заказ» и сам разбирается, что для этого нужно сделать. Он читает текст на странице, понимает контекст, видит, что кнопка называется «Оформить заказ» или «Checkout» — и нажимает на неё. Если вёрстка поменялась, агент просто переработает свой план.
Browserbase: облачный браузер для AI
Browserbase — это управляемая облачная инфраструктура для запуска браузеров. Не просто headless Chrome на твоём сервере, а полноценная платформа с рядом ключевых возможностей:
Что даёт Browserbase:
Управляемые браузеры — не нужно настраивать Chromium, версии, зависимости. Browserbase запускает браузеры в своей инфраструктуре, ты подключаешься через WebSocket.
Session Replay — каждую сессию агента можно воспроизвести как видео. Незаменимо для дебага: видишь буквально то, что видел агент в момент ошибки.
Стелс-режим — браузеры настроены так, чтобы выглядеть как обычный пользователь: правильные заголовки, реалистичный отпечаток браузера. Базовые антибот-проверки это часто проходит, серьёзные защиты — не всегда.
Параллельность — можно запускать много сессий одновременно (предел зависит от тарифа). Мониторить десятки сайтов сразу — обычная задача.
Жилые прокси — возможность выходить через реальные IP-адреса пользователей разных стран.
Цены: у Browserbase есть бесплатный тариф и платные, которые различаются числом браузерных часов в месяц и числом браузеров, работающих одновременно. Для проверки идеи хватает бесплатного тарифа. Актуальные тарифы и цены: browserbase.com/pricing.
Stagehand: AI-Playwright
Stagehand — это open-source SDK от команды Browserbase, который добавляет AI-интеллект к автоматизации браузера (изначально поверх Playwright). Если Playwright — это руки (инструменты управления браузером), то Stagehand — это мозг, который решает, что делать руками.
Про версии. В версии 3 методы act(), extract() и observe() вызываются у самого объекта stagehand, а страницу берут из stagehand.context.pages()[0]. В версии 2 они вызывались у page (page.act(...)). Примеры ниже написаны под версию 3; перед работой сверься с документацией установленной версии: docs.stagehand.dev.
Три ключевых метода:
act(instruction) — выполни действие
await stagehand.act('Нажми на кнопку "Войти"');
await stagehand.act('Заполни поле email адресом test@example.com');
await stagehand.act('Прокрути страницу вниз до раздела "Цены"');Stagehand берёт твою инструкцию на естественном языке, смотрит на страницу через screenshot или DOM, определяет нужный элемент и выполняет действие. Если элемент переехал — не проблема, AI найдёт его снова.
extract(instruction, schema) — извлеки данные
const products = await stagehand.extract(
'Найди все товары с ценами на странице',
z.object({
items: z.array(z.object({
name: z.string(),
price: z.number(),
inStock: z.boolean(),
}))
})
);Метод возвращает структурированные данные (валидируется через Zod), извлечённые со страницы. AI понимает, что такое «цена» и «наличие», даже если разные сайты показывают это по-разному.
observe(instruction) — посмотри и скажи
const elements = await stagehand.observe(
'Какие кнопки доступны для действий с этим товаром?'
);
// возвращает список найденных элементов и предлагаемых действий,
// например кнопки «Добавить в корзину», «Купить сейчас», «Сравнить»Наблюдение без действия — полезно для принятия решений в агентном цикле.
Полный пример: мониторинг цен конкурента
import { Stagehand } from '@browserbasehq/stagehand';
import { z } from 'zod';
const PriceSchema = z.object({
products: z.array(z.object({
name: z.string().describe('Название товара'),
price: z.number().describe('Цена в рублях'),
oldPrice: z.number().nullable().describe('Старая цена со скидкой'),
inStock: z.boolean().describe('Есть ли в наличии'),
url: z.string().describe('Ссылка на товар'),
}))
});
async function monitorCompetitorPrices(competitorUrl: string) {
// Ключи Browserbase и модели читаются из .env / переменных окружения
const stagehand = new Stagehand({
env: 'BROWSERBASE',
model: 'anthropic/claude-sonnet-5-5', // актуальные модели: страница «Актуальное сейчас»
verbose: 1,
});
await stagehand.init();
const page = stagehand.context.pages()[0];
try {
// Переходим на страницу каталога
await page.goto(competitorUrl);
await page.waitForLoadState('networkidle');
// Если нужно принять куки — делаем это
const cookieBanner = await stagehand.observe(
'Есть ли баннер о куки или GDPR, который нужно закрыть?'
);
if (cookieBanner.length > 0) {
await stagehand.act('Нажми "Принять все" или закрой баннер о куки');
}
// Извлекаем товары с текущей страницы
const result = await stagehand.extract(
'Найди все товары в каталоге: название, цену, старую цену и наличие',
PriceSchema
);
// Проверяем, есть ли пагинация
const hasNextPage = await stagehand.observe(
'Есть ли кнопка "Следующая страница" или "Ещё товары"?'
);
if (hasNextPage.length > 0) {
await stagehand.act('Нажми на следующую страницу');
await page.waitForLoadState('networkidle');
const nextPageResult = await stagehand.extract(
'Найди все товары на этой странице',
PriceSchema
);
result.products.push(...nextPageResult.products);
}
return result.products;
} finally {
await stagehand.close();
}
}
// Запуск
const prices = await monitorCompetitorPrices('https://competitor.ru/catalog/laptops');
console.log(`Найдено ${prices.length} товаров`);Сравнение с Playwright MCP
В уроке Автоматизация браузера мы работали с Playwright и Claude Code — инструментом, который позволяет управлять браузером в рамках диалога с Claude. Это отличный инструмент для интерактивной автоматизации под руководством человека.
| Характеристика | Playwright MCP | Stagehand + Browserbase |
|---|---|---|
| Управление | Интерактивное (через Claude Code) | Автономное (агент решает сам) |
| Инфраструктура | Локальный браузер | Облако (много параллельных) |
| Антидетект | Базовый | Расширенный (stealth + прокси, без гарантий) |
| Session replay | Нет | Есть |
| Масштабирование | 1 браузер | Много одновременно (по тарифу) |
| Лучше для | Разовых задач с участием человека | Регулярных автономных задач |
Для продакшн-агента, который работает по расписанию и обрабатывает десятки сайтов — выбор за Stagehand. Для интерактивного исследования сайта в Claude Code — Playwright MCP удобнее.
DIY-подход: Puppeteer + Claude API
Если не хочешь платить за Browserbase, можно собрать аналог самостоятельно. Плагин stealth для puppeteer-extra развивается нерегулярно: перед использованием проверь, что он ещё работает:
import puppeteer from 'puppeteer-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
import Anthropic from '@anthropic-ai/sdk';
puppeteer.use(StealthPlugin());
const client = new Anthropic();
async function aiScrapePage(url: string, task: string) {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
// Имитация реального браузера
await page.setUserAgent(
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
);
await page.goto(url, { waitUntil: 'networkidle2' });
// Делаем скриншот и передаём Claude
const screenshot = await page.screenshot({ encoding: 'base64' });
const htmlContent = await page.content();
const response = await client.messages.create({
model: 'claude-sonnet-5-5', // актуальные модели: страница «Актуальное сейчас»
max_tokens: 2000,
messages: [{
role: 'user',
content: [
{
type: 'image',
source: { type: 'base64', media_type: 'image/png', data: screenshot as string }
},
{
type: 'text',
text: `Задача: ${task}\n\nHTML страницы:\n${htmlContent.substring(0, 5000)}\n\nВерни результат в JSON.`
}
]
}]
});
await browser.close();
return response.content.map((b) => (b.type === 'text' ? b.text : '')).join('');
}Этот подход бесплатный по инфраструктуре (только API Claude), но не масштабируется так же хорошо и требует больше настройки.
Антидетект: как не попасть в бан
Сайты используют несколько уровней защиты от ботов:
Уровень 1 — базовый (обходится легко):
- Проверка User-Agent — решается установкой реального UA
- Слишком быстрые запросы — решается рандомными паузами (
Math.random() * 2000 + 500ms) - Отсутствие cookies/localStorage — решается нормальным браузером
Уровень 2 — продвинутый (требует усилий):
- Fingerprinting браузера (canvas, WebGL, fonts) — Browserbase и puppeteer-stealth частично скрывают
- Анализ поведения мыши — нужны реалистичные движения
- IP-репутация — жилые прокси (residential proxies) от providers вроде Bright Data или Oxylabs
Уровень 3 — серьёзная защита:
- CAPTCHA (reCAPTCHA v3, hCaptcha) — существуют платные сервисы решения вроде 2captcha.com или CapSolver (цену смотри на их сайтах)
- Cloudflare Bot Management — обход не гарантирован ни одним сервисом
Важно. Обход защиты от ботов часто нарушает условия использования сайта, а в ряде стран и закон. Урок показывает механику, чтобы ты понимал, как это устроено. Если у сайта есть официальный API или можно договориться о доступе, это надёжнее и безопаснее.
// Схематичный пример интеграции 2captcha (проверь API сервиса и условия сайта)
import Captcha2 from '2captcha-ts';
const solver = new Captcha2.Solver(process.env.CAPTCHA_API_KEY);
// При обнаружении CAPTCHA
const siteKey = await page.evaluate(() =>
document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey')
);
if (siteKey) {
const result = await solver.recaptcha({
pageurl: page.url(),
googlekey: siteKey,
});
await page.evaluate((token) => {
(window as any).grecaptcha?.enterprise?.execute?.(token);
}, result.data);
}Этика и легальность: что можно, что нельзя
Что обычно можно (публичные данные):
- ✅ Цены на публичных страницах магазинов
- ✅ Публично доступные объявления (недвижимость, работа)
- ✅ Новостные статьи (для агрегации, не для перепубликации)
- ✅ Данные из публичных API
- ✅ Информация о компаниях из открытых источников
Что нельзя (юридические риски):
- ❌ PII — личные данные пользователей (email, телефоны из закрытых разделов)
- ❌ Контент за paywall без подписки
- ❌ Данные, скрапинг которых прямо запрещён в Terms of Service
- ❌ Нагрузка, мешающая работе сайта (DDoS-like behavior)
- ❌ Обход систем аутентификации
Перед запуском всегда проверяй robots.txt и Terms of Service целевого сайта. В ЕС — дополнительно смотри GDPR, в США — CFAA и scraping-решения по LinkedIn vs hiQ.
Практическое правило: если информация отображается для любого неавторизованного пользователя без регистрации — скорее всего, скрапинг публичных данных допустим. Если нужен логин — уже серая зона.
Монетизация: продажа дата-фидов
Браузерные агенты — это не только инструмент для собственных нужд. Это продукт, который можно продавать.
Модели продажи:
Регулярный поток данных — клиент платит за периодическую выгрузку. Например, ежедневный дамп цен конкурентов в определённой нише. Агента настраиваешь один раз, дальше он работает по расписанию, а ты следишь за ошибками и правишь сценарий, когда сайт меняется.
Разовый проект по сбору данных — клиент платит за единичную выгрузку. База компаний в определённом регионе, список потенциальных партнёров, анализ рынка.
Monitoring as a service — отслеживание изменений на сайтах (новые вакансии, изменения цен, появление новых конкурентов). Клиент получает алерты в Telegram или email.
White-label решение — продаёшь не данные, а инструмент. Настраиваешь агента для клиента, он сам его использует. Разовая оплата + поддержка.
Цену задают рынок и твои затраты (серверы, тариф Browserbase, токены, время на поддержку). Заработок в этой нише не гарантирован: прикинь расходы и спрос на своём рынке по урокам Упаковка предложения и Цены и монетизация.
Практика
Задача: агент мониторинга цен с уведомлениями в Telegram
Построим агент, который раз в час проверяет цены на товары конкурента и отправляет алерт, если цена снизилась более чем на 5%.
Шаг 1: Установка зависимостей
mkdir price-monitor-agent && cd price-monitor-agent
npm init -y
npm install @browserbasehq/stagehand zod dotenv node-telegram-bot-api
npm install -D typescript @types/node tsxСоздай .env:
BROWSERBASE_API_KEY=bb_live_xxxx
BROWSERBASE_PROJECT_ID=prj_xxxx
TELEGRAM_BOT_TOKEN=xxxx
TELEGRAM_CHAT_ID=xxxx
ANTHROPIC_API_KEY=sk-ant-xxxxШаг 2: Схема данных и конфигурация
Создай src/types.ts:
import { z } from 'zod';
export const ProductSchema = z.object({
name: z.string(),
price: z.number(),
oldPrice: z.number().nullable(),
inStock: z.boolean(),
sku: z.string().optional(),
});
export type Product = z.infer<typeof ProductSchema>;
export const MonitorConfig = {
targetUrl: 'https://example-shop.ru/catalog/laptops',
checkIntervalMinutes: 60,
priceDropThresholdPercent: 5,
};Шаг 3: Функция скрапинга
Создай src/scraper.ts:
import { Stagehand } from '@browserbasehq/stagehand';
import { z } from 'zod';
import { ProductSchema, type Product } from './types';
export async function scrapeProducts(url: string): Promise<Product[]> {
// Ключи Browserbase и модели читаются из .env (файл .env загружает dotenv)
const stagehand = new Stagehand({
env: 'BROWSERBASE',
model: 'anthropic/claude-sonnet-5-5',
});
await stagehand.init();
const page = stagehand.context.pages()[0];
try {
await page.goto(url);
await page.waitForLoadState('networkidle');
// Обработка куки-баннеров
const cookieBanner = await stagehand.observe(
'Есть ли баннер о куки или согласии, который нужно принять?'
);
if (cookieBanner.length > 0) {
await stagehand.act('Прими или закрой баннер о куки');
await page.waitForTimeout(1000);
}
// Извлекаем данные о товарах
const result = await stagehand.extract(
`
Найди все товары на странице каталога.
Для каждого извлеки: точное название, текущую цену числом,
старую цену (если есть скидка), наличие на складе.
Цены должны быть числами без символов валют.
`,
z.object({
products: z.array(ProductSchema)
})
);
return result.products;
} finally {
await stagehand.close();
}
}Шаг 4: Хранение и сравнение цен
Создай src/storage.ts:
import fs from 'fs/promises';
import path from 'path';
import type { Product } from './types';
const DATA_FILE = path.join(process.cwd(), 'prices.json');
export interface PriceRecord {
timestamp: string;
products: Product[];
}
export async function saveProducts(products: Product[]): Promise<void> {
const record: PriceRecord = {
timestamp: new Date().toISOString(),
products,
};
await fs.writeFile(DATA_FILE, JSON.stringify(record, null, 2));
}
export async function loadPreviousProducts(): Promise<Product[] | null> {
try {
const data = await fs.readFile(DATA_FILE, 'utf-8');
const record: PriceRecord = JSON.parse(data);
return record.products;
} catch {
return null; // Первый запуск
}
}
export interface PriceDrop {
product: Product;
oldPrice: number;
newPrice: number;
dropPercent: number;
}
export function findPriceDrops(
current: Product[],
previous: Product[],
thresholdPercent: number
): PriceDrop[] {
const drops: PriceDrop[] = [];
for (const currentProduct of current) {
const prevProduct = previous.find(p => p.name === currentProduct.name);
if (!prevProduct) continue;
const dropPercent = ((prevProduct.price - currentProduct.price) / prevProduct.price) * 100;
if (dropPercent >= thresholdPercent) {
drops.push({
product: currentProduct,
oldPrice: prevProduct.price,
newPrice: currentProduct.price,
dropPercent,
});
}
}
return drops.sort((a, b) => b.dropPercent - a.dropPercent);
}Шаг 5: Уведомления и главный цикл
Создай src/index.ts:
import 'dotenv/config';
import TelegramBot from 'node-telegram-bot-api';
import { scrapeProducts } from './scraper';
import { saveProducts, loadPreviousProducts, findPriceDrops } from './storage';
import { MonitorConfig } from './types';
const bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN!);
async function sendAlert(drops: ReturnType<typeof findPriceDrops>) {
const chatId = process.env.TELEGRAM_CHAT_ID!;
let message = `🔥 *Снижение цен обнаружено!*\n\n`;
for (const drop of drops) {
message += `📦 *${drop.product.name}*\n`;
message += `💰 ${drop.oldPrice.toLocaleString()} → ${drop.newPrice.toLocaleString()} руб\n`;
message += `📉 Снижение: *${drop.dropPercent.toFixed(1)}%*\n`;
message += drop.product.inStock ? '✅ Есть в наличии\n' : '❌ Нет в наличии\n';
message += '\n';
}
await bot.sendMessage(chatId, message, { parse_mode: 'Markdown' });
}
async function runCheck() {
console.log(`[${new Date().toISOString()}] Запуск проверки цен...`);
try {
// Скрапинг текущих цен
const currentProducts = await scrapeProducts(MonitorConfig.targetUrl);
console.log(`Найдено товаров: ${currentProducts.length}`);
// Сравнение с предыдущими данными
const previousProducts = await loadPreviousProducts();
if (previousProducts) {
const drops = findPriceDrops(
currentProducts,
previousProducts,
MonitorConfig.priceDropThresholdPercent
);
if (drops.length > 0) {
console.log(`Найдено снижений цен: ${drops.length}`);
await sendAlert(drops);
} else {
console.log('Значимых изменений цен не обнаружено');
}
} else {
console.log('Первый запуск — базовые цены сохранены');
}
// Сохраняем текущие цены как эталон
await saveProducts(currentProducts);
} catch (error) {
console.error('Ошибка при проверке:', error);
await bot.sendMessage(
process.env.TELEGRAM_CHAT_ID!,
`⚠️ Ошибка агента мониторинга: ${error}`
);
}
}
// Запуск и повтор по расписанию
async function main() {
console.log('Агент мониторинга запущен');
// Первый запуск сразу
await runCheck();
// Затем по расписанию
setInterval(
runCheck,
MonitorConfig.checkIntervalMinutes * 60 * 1000
);
}
main().catch(console.error);Запуск:
npx tsx src/index.tsДля продакшна — простой VPS с pm2 start или запуск одного прохода по расписанию через cron вместо setInterval.
Инструменты и ресурсы
| Инструмент | Назначение | Ссылка |
|---|---|---|
| Browserbase | Облачные управляемые браузеры для агентов | browserbase.com |
| Stagehand | AI-powered Playwright SDK (open source) | github.com/browserbase/stagehand |
| Playwright | Базовый движок автоматизации браузера | playwright.dev |
| Puppeteer + Stealth | DIY-альтернатива с антидетект плагином | github.com/berstend/puppeteer-extra |
| 2captcha | Сервис решения CAPTCHA (цена на сайте) | 2captcha.com |
| CapSolver | Альтернатива 2captcha с AI | capsolver.com |
| Bright Data | Жилые прокси для обхода блокировок | brightdata.com |
Ключевые выводы
«Браузерный агент — это не улучшенный скрипт. Это сотрудник, который понимает цель, а не просто следует инструкциям. Когда сайт меняется — человек адаптируется. Агент тоже.»
«Один раз настроенный агент мониторинга можно оформить как услугу, но это не пассивный доход: сайты меняются, агента нужно поддерживать. Сколько это принесёт, зависит от ниши и клиентов, гарантий нет. Browserbase и Stagehand позволяют быстро собрать рабочий прототип.»
«Публичные данные — это как витрина магазина. Смотреть и анализировать — твоё право. Но ломать замок или копировать чужое содержимое в коммерческих целях — уже другая история. Знай границу.»
Следующий урок
→ Мультиагентная оркестрация — LangGraph, CrewAI и Mastra
Если один агент — это один сотрудник, то мультиагентная система — это целый отдел. Разберём, как агенты координируются, передают задачи друг другу и как строить pipeline из десятков специализированных AI-воркеров. LangGraph для stateful workflow, CrewAI для role-based команд, Mastra как TypeScript-фреймворк с открытым кодом.
Отметка хранится только в этом браузере и никуда не отправляется. Мой прогресс