Automatización de Navegador con IA Sin BrowserBase: Lo Que Construimos en su Lugar
Cómo construir automatización de navegador con Playwright y LLMs en lugar de herramientas de pago. Pool de instancias, gestión de sesiones, comprensión de página por LLM y comparación de costes.
El mercado de automatización de navegador de pago
BrowserBase, Browserless y servicios similares cobran por minuto o por sesión para navegadores headless gestionados. Para los workflows de IA que necesitan interactuar con páginas web (rellenar formularios, extraer datos estructurados, navegar procesos multi-paso), estos servicios gestionan la infraestructura: instancias de navegador, anti-detección, proxies y gestión de sesiones.
Los costes se acumulan rápido. A 0,10-0,50 $ por minuto de sesión, un workflow que procesa 1.000 páginas por día a 2 minutos cada una cuesta 200-1.000 $ al día. Para un sistema IA que funciona continuamente, son 6.000-30.000 $ al mes solo en infraestructura de navegador.
Construimos una alternativa auto-alojada usando Playwright + LLM para la comprensión de página. Cubre el 90 % de los casos de uso a una fracción del coste. Este artículo cubre la arquitectura. Para entender cómo construimos sistemas de workflows IA e IA agéntica de forma más amplia, esas guías cubren los patrones de nivel superior.
La arquitectura
┌─────────────────────────────────────────────────────────┐
│ AI Browser Engine │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Task Queue │ │ Instance │ │ Session │ │
│ │ (BullMQ) │ │ Pool │ │ Manager │ │
│ │ │ │ (Playwright │ │ (cookies, │ │
│ │ Prioritized │ │ browsers) │ │ localStorage│ │
│ │ Retry logic │ │ │ │ auth state) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Page Interaction Layer │ │
│ │ │ │
│ │ 1. Navegar a la URL │ │
│ │ 2. Esperar la carga de la página │ │
│ │ 3. Extraer la estructura (árbol de accesibilidad)│ │
│ │ 4. Enviar la estructura al LLM │ │
│ │ 5. El LLM devuelve un plan de acciones │ │
│ │ 6. Ejecutar las acciones vía Playwright │ │
│ │ 7. Extraer datos estructurados del resultado │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
Pool de instancias
Lanzar un nuevo navegador para cada tarea es caro (arranque en frío: 1-3 segundos, memoria: 200-400 MB por instancia). Un pool reutiliza instancias de navegador entre tareas.
class BrowserPool {
private available: Browser[] = [];
private inUse = new Map<string, Browser>();
private maxInstances: number;
constructor(options: { maxInstances: number }) {
this.maxInstances = options.maxInstances;
}
async acquire(): Promise<{ browser: Browser; id: string }> {
// Reutilizar una instancia disponible
if (this.available.length > 0) {
const browser = this.available.pop()!;
const id = crypto.randomUUID();
this.inUse.set(id, browser);
return { browser, id };
}
// Crear una nueva si estamos bajo el límite
if (this.inUse.size < this.maxInstances) {
const browser = await chromium.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--single-process',
],
});
const id = crypto.randomUUID();
this.inUse.set(id, browser);
return { browser, id };
}
// Pool agotado: esperar a que se libere una
return new Promise((resolve) => {
this.waitQueue.push(resolve);
});
}
async release(id: string): Promise<void> {
const browser = this.inUse.get(id);
if (!browser) return;
this.inUse.delete(id);
// Limpiar el estado entre tareas
const pages = browser.contexts();
for (const context of pages) {
await context.close();
}
// Si alguien está esperando, darle esta instancia
if (this.waitQueue.length > 0) {
const resolve = this.waitQueue.shift()!;
const newId = crypto.randomUUID();
this.inUse.set(newId, browser);
resolve({ browser, id: newId });
} else {
this.available.push(browser);
}
}
}
Dimensionamiento del pool
| Carga de trabajo | Tamaño del pool | Memoria requerida |
|---|---|---|
| Ligera (< 100 páginas/hora) | 2-3 instancias | 1-2 GB |
| Media (100-500 páginas/hora) | 5-10 instancias | 3-5 GB |
| Pesada (500+ páginas/hora) | 10-20 instancias | 5-10 GB |
Cada instancia de Chromium consume 200-400 MB de RAM. El tamaño del pool determina tu techo de rendimiento y tus requisitos de memoria. Empieza pequeño y ajusta según la carga real.
Gestión de sesiones
Muchos workflows necesitan mantener el estado de conexión entre múltiples interacciones con las páginas. El gestor de sesiones persiste cookies, localStorage y tokens de autenticación entre tareas.
class SessionManager {
private sessions = new Map<string, SessionState>();
async createSession(id: string, options: SessionOptions): Promise<BrowserContext> {
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
userAgent: options.userAgent || this.getRandomUserAgent(),
locale: options.locale || 'en-US',
timezoneId: options.timezone || 'Europe/Berlin',
});
// Restaurar el estado de sesión anterior si existe
const existing = this.sessions.get(id);
if (existing) {
await context.addCookies(existing.cookies);
// localStorage restaurado vía page.evaluate después de la navegación
}
return context;
}
async saveSession(id: string, context: BrowserContext): Promise<void> {
const cookies = await context.cookies();
const pages = context.pages();
let localStorage = {};
if (pages.length > 0) {
localStorage = await pages[0].evaluate(() => {
const data: Record<string, string> = {};
for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i);
if (key) data[key] = window.localStorage.getItem(key) || '';
}
return data;
});
}
this.sessions.set(id, {
cookies,
localStorage,
lastUsed: Date.now(),
});
}
}
Comprensión de página dirigida por LLM
La innovación principal: en lugar de escribir selectores CSS o consultas XPath para cada página, enviamos el árbol de accesibilidad de la página a un LLM y dejamos que decida con qué elementos interactuar.
async function extractPageStructure(page: Page): Promise<string> {
// Obtener el árbol de accesibilidad (representación estructurada y compacta)
const tree = await page.accessibility.snapshot();
// Convertir a formato texto que el LLM pueda entender
return formatAccessibilityTree(tree, {
maxDepth: 5,
includeRoles: ['button', 'link', 'textbox', 'combobox', 'checkbox', 'heading'],
includeText: true,
includeLabels: true,
});
}
function formatAccessibilityTree(node: any, options: any, depth = 0): string {
if (depth > options.maxDepth) return '';
if (!options.includeRoles.includes(node.role) && depth > 1) {
// Ignorar elementos no interactivos, pero recorrer los hijos
return (node.children || []).map(c => formatAccessibilityTree(c, options, depth + 1)).join('');
}
const indent = ' '.repeat(depth);
let result = `${indent}[${node.role}] ${node.name || ''}`;
if (node.value) result += ` value="${node.value}"`;
result += '\n';
for (const child of node.children || []) {
result += formatAccessibilityTree(child, options, depth + 1);
}
return result;
}
Planificación de acciones por el LLM
Enviamos la estructura de la página al LLM junto con la descripción de la tarea. El LLM devuelve una secuencia de acciones:
async function planActions(pageStructure: string, task: string): Promise<Action[]> {
const response = await llm.generate({
model: 'gpt-4o-mini', // Modelo rápido para planificación de acciones
messages: [
{
role: 'system',
content: `You are a browser automation assistant. Given a page structure and a task,
return a JSON array of actions to accomplish the task.
Available actions: click(selector), type(selector, text), select(selector, value),
wait(ms), extract(selector).
Use the element text/labels to identify targets, not CSS selectors.`,
},
{
role: 'user',
content: `Page structure:\n${pageStructure}\n\nTask: ${task}`,
},
],
responseFormat: 'json',
});
return JSON.parse(response.text);
}
// Ejemplo de tarea: "Rellenar el formulario de contacto con nombre Sara Mustermann y email sara.mustermann@beispiel.de"
// El LLM devuelve:
// [
// { "action": "type", "target": "Name input field", "value": "Sara Mustermann" },
// { "action": "type", "target": "Email input field", "value": "sara.mustermann@beispiel.de" },
// { "action": "click", "target": "Submit button" }
// ]
Resolución de acciones LLM a comandos Playwright
El LLM devuelve targets legibles por humanos ("Name input field"). Un resolver los mapea a selectores de Playwright:
async function resolveAndExecute(page: Page, actions: Action[]): Promise<void> {
for (const action of actions) {
// Encontrar el elemento que coincide con la descripción del LLM
const element = await findElementByDescription(page, action.target);
if (!element) {
throw new ActionError(`Could not find element: ${action.target}`);
}
switch (action.action) {
case 'click':
await element.click();
await page.waitForLoadState('networkidle');
break;
case 'type':
await element.fill(action.value);
break;
case 'select':
await element.selectOption(action.value);
break;
case 'wait':
await page.waitForTimeout(action.value);
break;
case 'extract':
const text = await element.textContent();
results.push({ field: action.target, value: text });
break;
}
}
}
async function findElementByDescription(page: Page, description: string): Promise<ElementHandle | null> {
// Probar múltiples estrategias para encontrar el elemento
const strategies = [
// Por aria-label
() => page.$(`[aria-label*="${description}" i]`),
// Por placeholder
() => page.$(`[placeholder*="${description}" i]`),
// Por texto visible
() => page.$(`text=${description}`),
// Por asociación de label
() => page.$(`label:has-text("${description}") + input, label:has-text("${description}") input`),
// Por rol y nombre
() => page.getByRole('textbox', { name: new RegExp(description, 'i') }).first().elementHandle(),
() => page.getByRole('button', { name: new RegExp(description, 'i') }).first().elementHandle(),
];
for (const strategy of strategies) {
try {
const element = await strategy();
if (element) return element;
} catch {
continue;
}
}
return null;
}
Anti-detección: lo básico
Algunos sitios web detectan y bloquean navegadores headless. Las contramedidas básicas:
const context = await browser.newContext({
// Aleatorizar el viewport
viewport: {
width: 1280 + Math.floor(Math.random() * 200),
height: 720 + Math.floor(Math.random() * 100),
},
// Rotación de user agents
userAgent: getRandomUserAgent(),
// Locale y zona horaria realistas
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
// Geolocalización realista
geolocation: { latitude: 48.1351, longitude: 11.5820 },
permissions: ['geolocation'],
});
// Sobreescribir navigator.webdriver (detección de headless)
await page.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
});
Nota: la anti-detección es una carrera armamentística. Para sitios con detección de bots sofisticada (Cloudflare, Akamai), el Playwright auto-alojado acabará siendo detectado. Ahí es donde servicios de pago como BrowserBase aportan valor: invierten continuamente en anti-detección. Para la mayoría de tareas de automatización empresarial (herramientas internas, portales de socios, datos públicos), la anti-detección básica es suficiente.
Cuándo las herramientas de pago SÍ valen la pena
| Escenario | Auto-alojado | Servicio de pago |
|---|---|---|
| Automatización de herramientas internas | Mejor opción (no necesita anti-detección) | Excesivo |
| Extracción de datos públicos (simple) | Bueno (la anti-detección básica funciona) | Innecesario |
| Sitios con detección de bots | Posible pero mantenimiento constante | Justificado (ellos gestionan la anti-detección) |
| Scraping de alto volumen (10K+ páginas/día) | Complejo (rotación de proxy, gestión de IP) | Justificado (infraestructura gestionada) |
| Datos regulados (RGPD, cumplimiento normativo) | Mejor (los datos se quedan en tu infraestructura) | Riesgo (los datos pasan por un tercero) |
| Migración puntual | Bueno (carga de trabajo temporal) | Coste innecesario |
El marco de decisión: si automatizas workflows internos o procesas datos públicos de sitios sin detección de bots agresiva, auto-aloja. Si haces extracción de alto volumen desde sitios con protección nivel Cloudflare, paga por un servicio cuya anti-detección es su negocio principal.
Comparación de costes
| Componente | Auto-alojado (mensual) | BrowserBase (mensual) |
|---|---|---|
| Compute (5 instancias) | 50-100 $ (contenedor/VPS) | N/A |
| Llamadas LLM (planificación de acciones) | 20-50 $ (GPT-4o-mini) | N/A |
| Sesiones BrowserBase | N/A | 500-2.000 $ |
| Servicio de proxy (si es necesario) | 50-200 $ | Incluido |
| Mantenimiento | 2-4 horas/mes | Ninguno |
| Total (1.000 páginas/día) | 120-350 $/mes | 500-2.000 $/mes |
| Total (10.000 páginas/día) | 300-800 $/mes | 3.000-10.000 $/mes |
El auto-alojamiento es de 3 a 10 veces más barato a escala. La contrapartida es el tiempo de mantenimiento y la capacidad de anti-detección.
Errores comunes
-
Sin pool de instancias. Lanzar un nuevo navegador por tarea desperdicia 1-3 segundos en arranque en frío y 200-400 MB de RAM. Usa un pool y reutiliza instancias.
-
Selectores CSS hardcodeados. Las páginas modifican su estructura DOM regularmente. La identificación de elementos por LLM es más resiliente que selectores hardcodeados.
-
Sin persistencia de sesión. Los workflows multi-paso que requieren login fallan cuando el estado de sesión se pierde entre pasos.
-
Ignorar completamente la anti-detección. Incluso las medidas básicas (viewport aleatorio, rotación de user agent, override de webdriver) evitan la detección en la mayoría de sitios.
-
Usar un modelo grande para la planificación de acciones. GPT-4o-mini o Claude Haiku son lo suficientemente rápidos para la comprensión de página. Un modelo grande añade latencia sin mejorar la precisión para esta tarea.
-
Sin timeout en la carga de páginas. Algunas páginas cargan indefinidamente (scroll infinito, scripts de terceros lentos). Define un timeout de navegación y gestiónalo.
-
Ejecutar en producción sin monitorización. Monitoriza la tasa de éxito, el tiempo medio de ejecución y los tipos de error por workflow. Alerta cuando la tasa de éxito baje.
Puntos clave
-
Playwright auto-alojado + LLM cubre el 90 % de los casos de automatización de navegador. Para herramientas internas, portales de socios y datos públicos sin detección de bots agresiva, este es el enfoque correcto.
-
El pool de instancias es esencial. Reutiliza instancias de navegador entre tareas. Los arranques en frío y la asignación de memoria son el mayor cuello de botella de rendimiento.
-
La comprensión de página por LLM reemplaza selectores frágiles. Envía el árbol de accesibilidad a un modelo rápido. Deja que decida con qué elementos interactuar. Más resiliente a cambios de página que selectores CSS hardcodeados.
-
Los servicios de pago valen su coste para la anti-detección. Si tus sitios objetivo tienen Cloudflare o protección similar, BrowserBase invierte continuamente en evadirla. Es su negocio principal. No intentes competir.
-
El auto-alojamiento es de 3 a 10 veces más barato a escala. Pero pagas en tiempo de mantenimiento y limitaciones de anti-detección. Haz el compromiso con conocimiento de causa.
Integramos la automatización de navegador en nuestros sistemas de workflows IA y nuestros proyectos de software a medida. Si necesitas ayuda con la arquitectura de automatización de navegador, habla con nuestro equipo o solicita un presupuesto.
Temas cubiertos
Guías relacionadas
Guía Empresarial de Sistemas de IA Agéntica
Guia tecnica de sistemas de IA agentica en entornos empresariales. Descubre la arquitectura, capacidades y aplicaciones de agentes IA autonomos.
Leer guíaComercio Agéntico: Cómo Dejar que los Agentes IA Compren de Forma Segura
Cómo diseñar comercio iniciado por agentes IA con gobernanza. Motores de políticas, puertas de aprobación HITL, recibos HMAC, idempotencia, aislamiento de tenants y el Agentic Checkout Protocol completo.
Leer guíaLos 9 Puntos Donde Tu Sistema de IA Filtra Datos (y Cómo Sellar Cada Uno)
Un mapa sistemático de cada lugar donde se filtran datos en sistemas de IA. Prompts, embeddings, logs, llamadas a herramientas, memoria de agentes, mensajes de error, caché, datos de fine-tuning y handoffs entre agentes.
Leer guía¿Listo para construir sistemas de IA listos para producción?
Nuestro equipo se especializa en sistemas de IA listos para producción. Hablemos de cómo podemos ayudar.
Iniciar una conversación