Automatisation de Navigateur IA Sans BrowserBase : Ce Que Nous Avons Construit à la Place
Comment construire une automatisation de navigateur avec Playwright et des LLM au lieu d'outils payants. Pool d'instances, gestion de sessions, compréhension de page par LLM et comparaison de coûts.
Le marché des outils d'automatisation de navigateur payants
BrowserBase, Browserless et les services similaires facturent à la minute ou à la session pour des navigateurs headless managés. Pour les workflows IA qui doivent interagir avec des pages web (remplir des formulaires, extraire des données structurées, naviguer dans des processus multi-étapes), ces services gèrent l'infrastructure : instances de navigateur, anti-détection, proxies et gestion de sessions.
Les coûts s'accumulent vite. À 0,10-0,50 $ par minute de session, un workflow qui traite 1 000 pages par jour à 2 minutes chacune coûte 200-1 000 $ par jour. Pour un système IA qui tourne en continu, ça fait 6 000-30 000 $ par mois juste pour l'infrastructure navigateur.
Nous avons construit une alternative auto-hébergée avec Playwright + LLM pour la compréhension de page. Elle couvre 90 % des cas d'usage à une fraction du coût. Cet article couvre l'architecture. Pour comprendre comment nous construisons des systèmes de workflows IA et de l'IA agentique de manière plus large, ces guides couvrent les patterns de niveau supérieur.
L'architecture
┌─────────────────────────────────────────────────────────┐
│ AI Browser Engine │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Task Queue │ │ Instance │ │ Session │ │
│ │ (BullMQ) │ │ Pool │ │ Manager │ │
│ │ │ │ (Playwright │ │ (cookies, │ │
│ │ Prioritized │ │ browsers) │ │ localStorage│ │
│ │ Retry logic │ │ │ │ auth state) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Page Interaction Layer │ │
│ │ │ │
│ │ 1. Naviguer vers l'URL │ │
│ │ 2. Attendre le chargement de la page │ │
│ │ 3. Extraire la structure (arbre d'accessibilité) │ │
│ │ 4. Envoyer la structure au LLM │ │
│ │ 5. Le LLM retourne un plan d'action │ │
│ │ 6. Exécuter les actions via Playwright │ │
│ │ 7. Extraire les données structurées du résultat │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
Pool d'instances
Lancer un nouveau navigateur pour chaque tâche coûte cher (démarrage à froid : 1-3 secondes, mémoire : 200-400 Mo par instance). Un pool réutilise les instances de navigateur entre les tâches.
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 }> {
// Réutiliser une instance disponible
if (this.available.length > 0) {
const browser = this.available.pop()!;
const id = crypto.randomUUID();
this.inUse.set(id, browser);
return { browser, id };
}
// Créer une nouvelle si on est sous la limite
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 épuisé : attendre qu'une instance se libère
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);
// Nettoyer l'état entre les tâches
const pages = browser.contexts();
for (const context of pages) {
await context.close();
}
// Si quelqu'un attend, lui donner cette instance
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);
}
}
}
Dimensionnement du pool
| Charge de travail | Taille du pool | Mémoire requise |
|---|---|---|
| Légère (< 100 pages/heure) | 2-3 instances | 1-2 Go |
| Moyenne (100-500 pages/heure) | 5-10 instances | 3-5 Go |
| Lourde (500+ pages/heure) | 10-20 instances | 5-10 Go |
Chaque instance Chromium consomme 200-400 Mo de RAM. La taille du pool détermine ton plafond de débit et tes besoins en mémoire. Commence petit et ajuste en fonction de la charge réelle.
Gestion de sessions
Beaucoup de workflows nécessitent de maintenir l'état de connexion entre plusieurs interactions avec les pages. Le gestionnaire de sessions persiste les cookies, le localStorage et les tokens d'authentification entre les tâches.
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',
});
// Restaurer l'état de session précédent s'il existe
const existing = this.sessions.get(id);
if (existing) {
await context.addCookies(existing.cookies);
// localStorage restauré via page.evaluate après navigation
}
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(),
});
}
}
Compréhension de page pilotée par LLM
L'innovation principale : au lieu d'écrire des sélecteurs CSS ou des requêtes XPath pour chaque page, on envoie l'arbre d'accessibilité de la page à un LLM et on le laisse décider avec quels éléments interagir.
async function extractPageStructure(page: Page): Promise<string> {
// Récupérer l'arbre d'accessibilité (représentation structurée et compacte)
const tree = await page.accessibility.snapshot();
// Convertir en format texte compréhensible par le LLM
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) {
// Ignorer les éléments non interactifs, mais parcourir les enfants
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;
}
Planification d'actions par le LLM
On envoie la structure de la page au LLM avec la description de la tâche. Le LLM retourne une séquence d'actions :
async function planActions(pageStructure: string, task: string): Promise<Action[]> {
const response = await llm.generate({
model: 'gpt-4o-mini', // Modèle rapide pour la planification d'actions
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);
}
// Exemple de tâche : "Remplir le formulaire de contact avec le nom Sara Mustermann et l'email sara.mustermann@beispiel.de"
// Le LLM retourne :
// [
// { "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" }
// ]
Résolution des actions LLM en commandes Playwright
Le LLM retourne des cibles lisibles par un humain ("Name input field"). Un résolveur les mappe vers des sélecteurs Playwright :
async function resolveAndExecute(page: Page, actions: Action[]): Promise<void> {
for (const action of actions) {
// Trouver l'élément correspondant à la description du 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> {
// Essayer plusieurs stratégies pour trouver l'élément
const strategies = [
// Par aria-label
() => page.$(`[aria-label*="${description}" i]`),
// Par placeholder
() => page.$(`[placeholder*="${description}" i]`),
// Par texte visible
() => page.$(`text=${description}`),
// Par association de label
() => page.$(`label:has-text("${description}") + input, label:has-text("${description}") input`),
// Par rôle et nom
() => 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-détection : les bases
Certains sites web détectent et bloquent les navigateurs headless. Les contre-mesures de base :
const context = await browser.newContext({
// Randomiser le viewport
viewport: {
width: 1280 + Math.floor(Math.random() * 200),
height: 720 + Math.floor(Math.random() * 100),
},
// Rotation des user agents
userAgent: getRandomUserAgent(),
// Locale et fuseau horaire réalistes
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
// Géolocalisation réaliste
geolocation: { latitude: 48.1351, longitude: 11.5820 },
permissions: ['geolocation'],
});
// Surcharger navigator.webdriver (détection de headless)
await page.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
});
Note : l'anti-détection est une course aux armements. Pour les sites avec une détection de bots sophistiquée (Cloudflare, Akamai), le Playwright auto-hébergé finira par être détecté. C'est là que les services payants comme BrowserBase apportent de la valeur : ils investissent continuellement dans l'anti-détection. Pour la plupart des tâches d'automatisation métier (outils internes, portails partenaires, données publiques), l'anti-détection de base suffit.
Quand les outils payants VALENT le coût
| Scénario | Auto-hébergé | Service payant |
|---|---|---|
| Automatisation d'outils internes | Meilleur choix (pas d'anti-détection nécessaire) | Surdimensionné |
| Extraction de données publiques (simple) | Bon (l'anti-détection basique fonctionne) | Inutile |
| Sites avec détection de bots | Possible mais maintenance constante | Justifié (ils gèrent l'anti-détection) |
| Scraping à haut volume (10K+ pages/jour) | Complexe (rotation de proxy, gestion d'IP) | Justifié (infrastructure managée) |
| Données réglementées (RGPD, conformité) | Mieux (les données restent sur ton infrastructure) | Risque (les données passent par un tiers) |
| Migration ponctuelle | Bon (charge de travail temporaire) | Coût inutile |
Le cadre de décision : si tu automatises des workflows internes ou traites des données publiques de sites sans détection de bots agressive, auto-héberge. Si tu fais de l'extraction à haut volume depuis des sites avec une protection niveau Cloudflare, paie un service dont l'anti-détection est le coeur de métier.
Comparaison de coûts
| Composant | Auto-hébergé (mensuel) | BrowserBase (mensuel) |
|---|---|---|
| Compute (5 instances) | 50-100 $ (conteneur/VPS) | N/A |
| Appels LLM (planification d'actions) | 20-50 $ (GPT-4o-mini) | N/A |
| Sessions BrowserBase | N/A | 500-2 000 $ |
| Service de proxy (si nécessaire) | 50-200 $ | Inclus |
| Maintenance | 2-4 heures/mois | Aucune |
| Total (1 000 pages/jour) | 120-350 $/mois | 500-2 000 $/mois |
| Total (10 000 pages/jour) | 300-800 $/mois | 3 000-10 000 $/mois |
L'auto-hébergement est 3 à 10 fois moins cher à l'échelle. La contrepartie, c'est le temps de maintenance et les capacités d'anti-détection.
Erreurs courantes
-
Pas de pool d'instances. Lancer un nouveau navigateur par tâche gaspille 1-3 secondes en démarrage à froid et 200-400 Mo de RAM. Mets les instances en pool et réutilise-les.
-
Sélecteurs CSS en dur. Les pages modifient régulièrement leur structure DOM. L'identification d'éléments par LLM est plus résiliente que des sélecteurs en dur.
-
Pas de persistance de session. Les workflows multi-étapes qui nécessitent une connexion échouent quand l'état de session est perdu entre les étapes.
-
Ignorer complètement l'anti-détection. Même les mesures de base (viewport aléatoire, rotation de user agent, surcharge de webdriver) empêchent la détection sur la plupart des sites.
-
Utiliser un gros modèle pour la planification d'actions. GPT-4o-mini ou Claude Haiku sont assez rapides pour la compréhension de page. Un gros modèle ajoute de la latence sans améliorer la précision pour cette tâche.
-
Pas de timeout sur le chargement des pages. Certaines pages chargent indéfiniment (scroll infini, scripts tiers lents). Définis un timeout de navigation et gère-le.
-
Tourner en production sans monitoring. Suis le taux de succès, le temps d'exécution moyen et les types d'erreurs par workflow. Alerte quand le taux de succès chute.
Points clés à retenir
-
Playwright auto-hébergé + LLM couvre 90 % des cas d'automatisation de navigateur. Pour les outils internes, portails partenaires et données publiques sans détection de bots agressive, c'est la bonne approche.
-
Le pool d'instances est essentiel. Réutilise les instances de navigateur entre les tâches. Les démarrages à froid et l'allocation mémoire sont le plus gros goulot d'étranglement.
-
La compréhension de page par LLM remplace les sélecteurs fragiles. Envoie l'arbre d'accessibilité à un modèle rapide. Laisse-le décider avec quels éléments interagir. Plus résilient aux changements de page que des sélecteurs CSS en dur.
-
Les services payants méritent leur coût pour l'anti-détection. Si tes sites cibles ont Cloudflare ou une protection similaire, BrowserBase investit continuellement pour la contourner. C'est leur coeur de métier. N'essaie pas de rivaliser.
-
L'auto-hébergement est 3 à 10 fois moins cher à l'échelle. Mais tu paies en temps de maintenance et en limitations d'anti-détection. Fais le compromis en connaissance de cause.
Nous intégrons l'automatisation de navigateur dans nos systèmes de workflows IA et nos projets de logiciels sur mesure. Si tu as besoin d'aide pour l'architecture d'automatisation de navigateur, parle à notre équipe ou demande un devis.
Sujets couverts
Guides connexes
Guide Entreprise des Systèmes d'IA Agentiques
Guide technique des systemes d'IA agentiques en entreprise. Decouvre l'architecture, les capacites et les applications des agents IA autonomes.
Lire le guideCommerce Agentique : Comment laisser les agents IA acheter en toute securite
Comment concevoir un commerce agentique gouverne. Moteurs de politiques, portes d'approbation HITL, reçus HMAC, idempotence, isolation multi-tenant et le protocole Agentic Checkout complet.
Lire le guideLes 9 endroits où ton système IA laisse fuir des données (et comment colmater chacun)
Cartographie systématique de chaque point de fuite de données dans les systèmes IA. Prompts, embeddings, logs, appels d'outils, mémoire d'agent, messages d'erreur, cache, données de fine-tuning et transferts entre agents.
Lire le guidePrêt à construire des systèmes IA prêts pour la production ?
Notre équipe est spécialisée dans les systèmes IA prêts pour la production. Discutons de comment nous pouvons aider.
Démarrer une conversation