KI-Browser-Automatisierung ohne BrowserBase: Was wir stattdessen gebaut haben
Wie du KI-gestützte Browser-Automatisierung mit Playwright und LLMs statt kostenpflichtiger Tools baust. Instance Pooling, Session-Management, LLM-basiertes Seitenverständnis und Kostenvergleich.
Der Markt für kostenpflichtige Browser-Automatisierung
BrowserBase, Browserless und ähnliche Dienste berechnen pro Minute oder pro Session für verwaltete Headless-Browser. Für KI-Workflows, die mit Webseiten interagieren müssen (Formulare ausfüllen, strukturierte Daten extrahieren, mehrstufige Prozesse navigieren), übernehmen diese Dienste die Infrastruktur: Browser-Instanzen, Anti-Detection, Proxies und Session-Management.
Die Kosten summieren sich schnell. Bei $0,10-0,50 pro Session-Minute kostet ein Workflow, der 1.000 Seiten pro Tag bei 2 Minuten pro Seite verarbeitet, $200-1.000 pro Tag. Für ein KI-System im Dauerbetrieb sind das $6.000-30.000 pro Monat allein für Browser-Infrastruktur.
Wir haben eine selbst gehostete Alternative mit Playwright + LLM für Seitenverständnis gebaut. Sie deckt 90% der Anwendungsfälle zu einem Bruchteil der Kosten ab. Dieser Artikel beschreibt die Architektur. Wie wir KI-Workflow-Systeme und agentische KI grundsätzlich bauen, behandeln die verlinkten Guides im Detail.
Die Architektur
┌─────────────────────────────────────────────────────────┐
│ AI Browser Engine │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Task Queue │ │ Instance │ │ Session │ │
│ │ (BullMQ) │ │ Pool │ │ Manager │ │
│ │ │ │ (Playwright │ │ (cookies, │ │
│ │ Prioritized │ │ browsers) │ │ localStorage│ │
│ │ Retry logic │ │ │ │ auth state) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Page Interaction Layer │ │
│ │ │ │
│ │ 1. URL aufrufen │ │
│ │ 2. Auf Seitenladevorgang warten │ │
│ │ 3. Seitenstruktur extrahieren (Accessibility Tree)│ │
│ │ 4. Struktur an LLM zum Verständnis senden │ │
│ │ 5. LLM gibt Aktionsplan zurück (click, type, select)│ │
│ │ 6. Aktionen via Playwright ausführen │ │
│ │ 7. Strukturierte Daten aus Ergebnis extrahieren │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
Instance Pooling
Einen neuen Browser für jede Aufgabe zu starten ist teuer (Cold Start: 1-3 Sekunden, Speicher: 200-400 MB pro Instanz). Ein Pool verwendet Browser-Instanzen über Aufgaben hinweg wieder.
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 }> {
// Verfügbare Instanz wiederverwenden
if (this.available.length > 0) {
const browser = this.available.pop()!;
const id = crypto.randomUUID();
this.inUse.set(id, browser);
return { browser, id };
}
// Neue Instanz erstellen, wenn unter dem Limit
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 erschöpft: warten, bis eine Instanz freigegeben wird
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);
// Zustand zwischen Aufgaben bereinigen
const pages = browser.contexts();
for (const context of pages) {
await context.close();
}
// Wenn jemand wartet, diese Instanz zuweisen
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);
}
}
}
Pool-Dimensionierung
| Workload | Pool-Größe | Benötigter Speicher |
|---|---|---|
| Leicht (< 100 Seiten/Stunde) | 2-3 Instanzen | 1-2 GB |
| Mittel (100-500 Seiten/Stunde) | 5-10 Instanzen | 3-5 GB |
| Schwer (500+ Seiten/Stunde) | 10-20 Instanzen | 5-10 GB |
Jede Chromium-Instanz braucht 200-400 MB RAM. Die Pool-Größe bestimmt deinen Durchsatz und Speicherbedarf. Fang klein an und skaliere basierend auf der tatsächlichen Last.
Session-Management
Viele Workflows erfordern einen persistenten Login-Zustand über mehrere Seiteninteraktionen hinweg. Der Session Manager speichert Cookies, localStorage und Authentifizierungs-Tokens zwischen Aufgaben.
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',
});
// Vorherigen Session-Zustand wiederherstellen, falls vorhanden
const existing = this.sessions.get(id);
if (existing) {
await context.addCookies(existing.cookies);
// localStorage wird nach Navigation via page.evaluate wiederhergestellt
}
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(),
});
}
}
LLM-basiertes Seitenverständnis
Die zentrale Innovation: Statt CSS-Selektoren oder XPath-Abfragen für jede Seite zu schreiben, sendest du den Accessibility Tree der Seite an ein LLM und lässt es entscheiden, mit welchen Elementen interagiert werden soll.
async function extractPageStructure(page: Page): Promise<string> {
// Accessibility Tree abrufen (strukturierte, kompakte Darstellung)
const tree = await page.accessibility.snapshot();
// In ein Textformat konvertieren, das das LLM versteht
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) {
// Nicht-interaktive Elemente überspringen, aber in Kinder rekursieren
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;
}
LLM-Aktionsplanung
Die Seitenstruktur wird mit der Aufgabenbeschreibung an das LLM gesendet. Das LLM gibt eine Sequenz von Aktionen zurück:
async function planActions(pageStructure: string, task: string): Promise<Action[]> {
const response = await llm.generate({
model: 'gpt-4o-mini', // Schnelles Modell für Aktionsplanung
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);
}
// Beispielaufgabe: "Kontaktformular ausfüllen mit Name Sara Mustermann und E-Mail sara.mustermann@beispiel.de"
// LLM gibt zurück:
// [
// { "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" }
// ]
LLM-Aktionen in Playwright-Befehle auflösen
Das LLM gibt menschenlesbare Ziele zurück ("Name input field"). Ein Resolver bildet sie auf Playwright-Selektoren ab:
async function resolveAndExecute(page: Page, actions: Action[]): Promise<void> {
for (const action of actions) {
// Element finden, das der LLM-Beschreibung entspricht
const element = await findElementByDescription(page, action.target);
if (!element) {
throw new ActionError(`Element nicht gefunden: ${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> {
// Mehrere Strategien zum Finden des Elements
const strategies = [
// Per aria-label
() => page.$(`[aria-label*="${description}" i]`),
// Per placeholder
() => page.$(`[placeholder*="${description}" i]`),
// Per sichtbarem Text
() => page.$(`text=${description}`),
// Per Label-Zuordnung
() => page.$(`label:has-text("${description}") + input, label:has-text("${description}") input`),
// Per Rolle und Name
() => 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-Detection Grundlagen
Manche Websites erkennen und blockieren Headless-Browser. Grundlegende Gegenmaßnahmen:
const context = await browser.newContext({
// Viewport randomisieren
viewport: {
width: 1280 + Math.floor(Math.random() * 200),
height: 720 + Math.floor(Math.random() * 100),
},
// User Agents rotieren
userAgent: getRandomUserAgent(),
// Realistische Locale und Zeitzone setzen
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
// Realistische Geolocation
geolocation: { latitude: 48.1351, longitude: 11.5820 },
permissions: ['geolocation'],
});
// navigator.webdriver überschreiben (Headless-Erkennung)
await page.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
});
Hinweis: Anti-Detection ist ein Wettrüsten. Für Websites mit ausgefeilter Bot-Erkennung (Cloudflare, Akamai) wird selbst gehostetes Playwright irgendwann erkannt. Hier liefern kostenpflichtige Dienste wie BrowserBase einen echten Mehrwert: Sie investieren kontinuierlich in Anti-Detection. Für die meisten geschäftlichen Automatisierungsaufgaben (interne Tools, Partnerportale, öffentliche Daten) reicht grundlegende Anti-Detection aus.
Wann sich kostenpflichtige Tools LOHNEN
| Szenario | Self-Hosted | Kostenpflichtiger Dienst |
|---|---|---|
| Interne Tool-Automatisierung | Beste Wahl (keine Anti-Detection nötig) | Overkill |
| Öffentliche Datenextraktion (einfach) | Gut (grundlegende Anti-Detection reicht) | Unnötig |
| Websites mit Bot-Erkennung | Möglich, aber ständige Wartung | Lohnt sich (Anti-Detection ist deren Kerngeschäft) |
| Hohes Volumen (10K+ Seiten/Tag) | Komplex (Proxy-Rotation, IP-Management) | Lohnt sich (verwaltete Infrastruktur) |
| Regulierte Daten (DSGVO, Compliance) | Besser (Daten bleiben auf deiner Infrastruktur) | Risiko (Daten gehen durch Drittanbieter) |
| Einmalige Migration | Gut (temporäre Arbeitslast) | Unnötige Kosten |
Die Entscheidungsregel: Wenn du interne Workflows automatisierst oder öffentliche Daten von Websites ohne aggressive Bot-Erkennung verarbeitest, hoste selbst. Wenn du hohes Volumen von Websites mit Cloudflare-Level-Schutz extrahierst, zahle für einen Dienst, der Anti-Detection als Kerngeschäft betreibt.
Kostenvergleich
| Komponente | Self-Hosted (monatlich) | BrowserBase (monatlich) |
|---|---|---|
| Compute (5 Instanzen) | $50-100 (Container/VPS) | N/A |
| LLM-Aufrufe (Aktionsplanung) | $20-50 (GPT-4o-mini) | N/A |
| BrowserBase Sessions | N/A | $500-2.000 |
| Proxy-Dienst (bei Bedarf) | $50-200 | Inklusive |
| Wartung | 2-4 Stunden/Monat | Keine |
| Gesamt (1.000 Seiten/Tag) | $120-350/Monat | $500-2.000/Monat |
| Gesamt (10.000 Seiten/Tag) | $300-800/Monat | $3.000-10.000/Monat |
Self-Hosting ist 3-10x günstiger bei Skalierung. Der Trade-off ist Wartungsaufwand und eingeschränkte Anti-Detection-Fähigkeiten.
Häufige Fehler
-
Kein Instance Pooling. Einen neuen Browser pro Aufgabe zu starten verschwendet 1-3 Sekunden Cold Start und 200-400 MB RAM. Instanzen poolen und wiederverwenden.
-
Hardcodierte CSS-Selektoren. Seiten ändern ihre DOM-Struktur regelmäßig. LLM-basierte Element-Identifikation ist widerstandsfähiger als hardcodierte Selektoren.
-
Keine Session-Persistenz. Mehrstufige Workflows, die einen Login erfordern, scheitern, wenn der Session-Zustand zwischen den Schritten verloren geht.
-
Anti-Detection komplett ignorieren. Selbst grundlegende Maßnahmen (zufälliger Viewport, User-Agent-Rotation, Webdriver-Override) verhindern die Erkennung auf den meisten Websites.
-
Großes Modell für Aktionsplanung verwenden. GPT-4o-mini oder Claude Haiku sind schnell genug für Seitenverständnis. Ein großes Modell fügt Latenz hinzu, ohne bei dieser Aufgabe genauer zu sein.
-
Kein Timeout bei Seitenladungen. Manche Seiten laden endlos (Infinite Scrolling, langsame Third-Party-Skripte). Setze einen Navigation-Timeout und behandle den Fall.
-
Ohne Monitoring in Produktion betreiben. Tracke Erfolgsrate, durchschnittliche Ausführungszeit und Fehlertypen pro Workflow. Alarmiere, wenn die Erfolgsrate sinkt.
Zentrale Erkenntnisse
-
Self-Hosted Playwright + LLM deckt 90% der Browser-Automatisierungsfälle ab. Für interne Tools, Partnerportale und öffentliche Daten ohne aggressive Bot-Erkennung ist das der richtige Ansatz.
-
Instance Pooling ist unverzichtbar. Browser-Instanzen über Aufgaben hinweg wiederverwenden. Cold Starts und Speicherzuweisung sind die größten Performance-Engpässe.
-
LLM-Seitenverständnis ersetzt fragile Selektoren. Sende den Accessibility Tree an ein schnelles Modell. Lass es entscheiden, mit welchen Elementen interagiert werden soll. Widerstandsfähiger gegen Seitenänderungen als hardcodierte CSS-Selektoren.
-
Kostenpflichtige Dienste verdienen ihr Geld bei Anti-Detection. Wenn deine Zielseiten Cloudflare oder ähnlichen Schutz haben, investiert BrowserBase kontinuierlich in die Umgehung. Das ist deren Kerngeschäft. Versuche nicht, damit zu konkurrieren.
-
Self-Hosting ist 3-10x günstiger bei Skalierung. Aber du zahlst mit Wartungszeit und Anti-Detection-Einschränkungen. Triff den Trade-off bewusst.
Wir bauen Browser-Automatisierung in unsere KI-Workflow-Systeme und Custom-Software-Projekte ein. Wenn du Hilfe mit Browser-Automatisierungs-Architektur brauchst, sprich mit unserem Team oder fordere ein Angebot an.
Behandelte Themen
Verwandte Guides
Unternehmenshandbuch zu Agentischen KI-Systemen
Technischer Leitfaden zu agentischen KI-Systemen in Unternehmen. Erfahre mehr ueber Architektur, Faehigkeiten und Anwendungen autonomer KI-Agenten.
Guide lesenAgentic Commerce: Wie du KI-Agenten sicher einkaufen lässt
Wie du gesteuerten, KI-initiierten Handel designst. Policy Engines, HITL-Freigabe-Gates, HMAC-Quittungen, Idempotenz, Tenant-Scoping und das vollständige Agentic Checkout Protocol.
Guide lesenDie 9 Stellen, an denen dein KI-System Daten verliert (und wie du jede einzelne abdichtest)
Eine systematische Übersicht aller Stellen, an denen KI-Systeme Daten preisgeben. Prompts, Embeddings, Logs, Tool Calls, Agent Memory, Fehlermeldungen, Cache, Fine-Tuning-Daten und Agent Handoffs.
Guide lesenBereit, produktionsreife KI-Systeme zu bauen?
Unser Team ist spezialisiert auf produktionsreife KI-Systeme. Lass uns besprechen, wie wir deinem Unternehmen helfen können.
Gespräch starten