Erstellung eines modularen JavaScript-Automatisierungstools von Grund auf
Erfahren Sie, wie Sie Playwright, Cheerio, SQLite und Commander zu einem wiederverwendbaren Node.js-Workflow-Engine kombinieren können, der sich von einem Skript zu einem verkaufbaren Automatisierungsprodukt entwickelt.
1. Ein wiederkehrendes Problem wurde zum Ausgangspunkt
Irgendwann stellt man fest, dass man immer wieder dieselbe Art von Aufgabe ausführt.
Eine Seite laden.
Auf dieser Seite nach nützlichen Informationen suchen.
Die relevanten Teile herausfiltern.
Sie an einem Ort für später speichern.
Zur nächsten Seite wechseln.
Den Kreislauf erneut beginnen.
Niemand dieser Schritte ist für sich genommen schwierig, doch die ständige Wiederholung ist absurd.
Anstatt also einfach ein weiteres JavaScript-Framework zu nutzen, um nur wieder einen Dashboard zu erstellen, ist ein sinnvolleres Vorgehen die Entwicklung von etwas, das tatsächlich genutzt werden kann: einem JavaScript-Tool für Automatisierung und Datenerfassung.
Die Idee dahinter ist einfach:
Eine Aufgabe dem Programm übergeben → JavaScript kümmert sich um den repetitiven Teil → strukturierte Ergebnisse erhalten.
Eine frühe Version eines solchen Tools benötigt nur wenige Technologien:
- Node.js
- Playwright
- Cheerio
- SQLite
- Commander
Diese Kombination reicht aus, um ein kleines Skript in etwas zu verwandeln, das bereits an ein echtes Produkt erinnert.
2. Playwright als erstes Baustein
Ziel war es, JavaScript die Steuerung eines echten Browsers zu ermöglichen.
Playwright macht das viel einfacher, als erwartet.
const { chromium } = require("playwright");
async function visitWebsite(url) {
const browser = await chromium.launch({
headless: false
});
const page = await browser.newPage();
await page.goto(url, {
waitUntil: "domcontentloaded"
});
console.log(
"Page title:",
await page.title()
);
console.log(
"Current URL:",
page.url()
);
await browser.close();
}
visitWebsite(
"https://example.com"
);
Zum ersten Mal, wenn man so etwas ausführt, wirkt es fast zu einfach.
JavaScript öffnet einen Browser.
JavaScript navigiert zu einer Seite.
JavaScript liest den Inhalt der Seite.
JavaScript schließt den Browser wieder.
Allein das bietet bereits eine Grundlage, auf der man Tests im Browser, Überwachungsfunktionen, wiederkehrende Arbeitsabläufe sowie allgemeine Automatisierungen entwickeln kann.
3. Vom Koordinatensystem zu Elementen
Eines, was man von Anfang an vermeiden sollte, ist anfällige Automatisierung.
Wenn man dem Programm etwas wie Folgendes mitteilt:
Klicken Sie an dieser genauen Stelle.
bedeutet das, dass die Automatisierung bereits bei geringsten Veränderungen im Layout ausfallen kann.
Ein besseres Vorgehen ist es, Code zu schreiben, der beschreibt, womit der Benutzer tatsächlich interagiert, anstatt nur den Platz auf dem Bildschirm anzugeben.
async function searchPage(page, query) {
await page
.getByRole("textbox")
.fill(query);
await page
.getByRole("button", {
name: "Search"
})
.click();
await page.waitForLoadState(
"domcontentloaded"
);
}
So ist die Wartung im Laufe der Zeit weitaus einfacher.
Der Code besagt nicht:
Klicken Sie auf den Punkt mit den Koordinaten 742, 381.
Sondern:
Finden Sie das Eingabefeld und die Suchschaltfläche.
Es handelt sich dabei um eine kleine Gestaltungsempfehlung, doch sie macht die Browserautomatisierung später erheblich unkomplizierter.
4. Daten aus der Seite extrahieren
Sobald der Browser in der Lage ist, sich selbstständig auf einer Seite zu bewegen, ist der nächste Schritt, dass er Informationen sammelt.
Stellen Sie sich beispielsweise eine Seite vor, die mit Produktkarten gefüllt ist.
async function extractProducts(page) {
return page
.locator(".product-card")
.evaluateAll(cards => {
return cards.map(card => {
const name =
card
.querySelector(".product-name")
?.textContent
?.trim();
const price =
card
.querySelector(".price")
?.textContent
?.trim();
return {
name,
price
};
});
});
}
Zu diesem Zeitpunkt besucht der Browser nicht mehr nur Seiten.
Er wandelt den Inhalt der Seite in JavaScript-Objekte um.
Dadurch werden die Daten für weitere Verarbeitungen nutzbar.
const products =
await extractProducts(page);
console.log(
JSON.stringify(
products,
null,
2
)
);
Sobald die Informationen eine echte Struktur haben, können Sie sie speichern, vergleichen, analysieren oder an einen anderen Teil der Anwendung weitergeben.
5. Einsatz von Cheerio für das HTML-Parsing
Playwright ist dann besonders nützlich, wenn Sie tatsächlich einen laufenden Browser benötigen.
Oft haben Sie jedoch bereits das HTML zur Hand und müssen Chromium überhaupt nicht starten.
Dann kommt Cheerio ins Spiel.
const cheerio = require("cheerio");
function parseProducts(html) {
const $ = cheerio.load(html);
const products = [];
$(".product-card").each(
(_, element) => {
const name = $(element)
.find(".product-name")
.text()
.trim();
const price = $(element)
.find(".price")
.text()
.trim();
products.push({
name,
price
});
}
);
return products;
}
Die Verfügbarkeit beider Tools ist ein echter Vorteil.
Playwright kümmert sich um die Interaktion mit dem Browser.
Cheerio übernimmt die leichte HTML-Parsing-Funktion.
Auf diese Weise wird eine vollständige Browserinstanz nur dann verwendet, wenn sie tatsächlich notwendig ist, während ein einfacher Parser für den Rest sorgt.
6. Bereitstellung eines Speichers für die Automatisierung mit SQLite
Der Speicher war die nächste Herausforderung, die gelöst werden musste.
Falls ein Skript heute Daten sammelt, wo befinden sich diese Daten dann morgen?
Die Lösung bestand darin, SQLite einzusetzen.
const sqlite3 = require("sqlite3").verbose();
const db = new sqlite3.Database(
"automation.db"
);
db.run(`
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price TEXT,
source TEXT,
created_at DATETIME
DEFAULT CURRENT_TIMESTAMP
)
`);
Ab dort kümmerte sich eine speziell dafür vorgesehene Funktion um das Speichern jedes Ergebnisses.
function saveProduct(product, source) {
return new Promise(
(resolve, reject) => {
db.run(
`
INSERT INTO products
(name, price, source)
VALUES (?, ?, ?)
`,
[
product.name,
product.price,
source
],
error => {
if (error) {
reject(error);
return;
}
resolve();
}
);
}
);
}
Diese Ergänzung veränderte die Struktur des Projekts auf signifikante Weise.
Durch die Verwendung einer Datenbank konnten im Laufe der Zeit historische Aufzeichnungen angesammelt werden. Dadurch wurde es möglich, praktische Fragen zu beantworten wie:
- Was hat sich geändert?
- Was ist kürzlich aufgetaucht?
- Was ist verschwunden?
Automatisierung wird um ein Vielfaches wertvoller, sobald sie sich daran erinnern kann, was zuvor geschehen ist.
7. Aufbau eines wiederverwendbaren Workflow-Engines
Zu diesem Zeitpunkt bestand das Projekt aus mehreren unabhängigen Komponenten: Browsersteuerung, HTML-Parser und Datenbankpersistenz. Was fehlte, war eine Struktur – es bestand tatsächlich die Gefahr, dass lose Funktionen überall verstreut blieben anstelle eines kohärenten Systems.
Um das zu beheben, brachte eine Workflow-Klasse alles zusammen.
class AutomationWorkflow {
constructor(browser) {
this.browser = browser;
this.page = null;
}
async start() {
this.page =
await this.browser.newPage();
}
async visit(url) {
await this.page.goto(url, {
waitUntil: "domcontentloaded"
});
}
async search(query) {
await this.page
.getByRole("textbox")
.fill(query);
await this.page
.getByRole("button", {
name: "Search"
})
.click();
}
async getTitle() {
return this.page.title();
}
async close() {
await this.page.close();
}
}
Sobald diese Klasse vorhanden ist, lässt sich eine vollständige Aufgabe von Anfang bis Ende leicht nachvollziehen.
const browser =
await chromium.launch({
headless: false
});
const workflow =
new AutomationWorkflow(
browser
);
await workflow.start();
await workflow.visit(
"https://example.com"
);
await workflow.search(
"JavaScript automation"
);
console.log(
await workflow.getTitle()
);
await workflow.close();
await browser.close();
Hier zeigt sich, wo das objektorientierte Design anfängt, Vorteile zu bringen. Die Klasseninstanz modelliert den Workflow selbst, und ihre Methoden repräsentieren die einzelnen Operationen. Der Rest des Codebases muss nicht wissen, wie jeder Schritt intern umgesetzt wird.
8. Fehlerbehandlung mit Wiederholungslogik
Automatisierungs-Skripte neigen dazu, neunmal fehlerfrei zu laufen und dann beim zehnten Mal zu versagen. Das Netzwerk verlangsamt sich, eine Seite lädt nicht vollständig, ein Server hat Probleme oder ein Element braucht länger als erwartet, um angezeigt zu werden.
Um damit umzugehen, wurde ein generischer Hilfsfunktion für Wiederholungen eingeführt.
async function retry(
operation,
attempts = 3,
delay = 2000
) {
let lastError;
for (
let attempt = 1;
attempt <= attempts;
attempt++
) {
try {
return await operation();
} catch (error) {
lastError = error;
console.log(
`Attempt ${attempt} failed.`
);
if (
attempt < attempts
) {
await new Promise(
resolve =>
setTimeout(
resolve,
delay
)
);
}
}
}
throw lastError;
}
Diese Hilfsfunktion ermöglichte es, kritische Schritte mit automatischen Wiederholungen zu umgeben.
await retry(
async () => {
await page.goto(
"https://example.com",
{
waitUntil:
"domcontentloaded"
}
);
},
3,
1500
);
Die Schlussfolgerung war eindeutig: Automatisierungen in Produktivumgebungen müssen mit Fehlern als normalen Ereignissen umgehen, nicht als Ausnahmen. Anleitungen gehen in der Regel von einem perfekt kooperativen Netzwerk aus. Reale Systeme können sich solche Annahmen nicht leisten.
9. Einbetten in eine Kommandozeilenoberfläche
Irgendwann wurde es mühsam, jedes Mal die Quelldatei öffnen zu müssen, nur um eine URL auszutauschen.
Das Ziel verschob sich darauf, das Tool so zu gestalten, dass es wie eine echte Kommandozeilenanwendung funktioniert.
Commander macht dies leicht umsetzbar.
const { Command } = require("commander");
const program = new Command();
program
.name("webpilot")
.description(
"JavaScript automation toolkit"
)
.version("1.0.0");
program
.command("visit")
.description(
"Open a webpage"
)
.argument("<url>")
.action(async url => {
const browser =
await chromium.launch({
headless: false
});
const page =
await browser.newPage();
await page.goto(url);
console.log(
await page.title()
);
await browser.close();
});
program.parseAsync();
Sobald das vorhanden ist, kann das Tool direkt aus einer Terminalanwendung gestartet werden.
node webpilot.js visit https://example.com
Es sieht wie eine kleine Anpassung aus, verändert aber grundlegend die Art und Weise, wie man mit der Software interagiert.
Anstatt das Programm jedes Mal zu bearbeiten, wenn man etwas benötigt,
läuft man es einfach aus.
10. KI als Frontend betrachten
Das wirft eine neue Frage auf:
Warum sollte der Benutzer überhaupt die genaue Befehlssyntax auswendig lernen müssen?
Anstatt etwas wie Folgendes einzugeben:
node webpilot.js screenshot https://example.com
könnte eine Person einfach den Zweck in Alltagssprache beschreiben:
">Nimm ein Screenshot der Startseite auf."
Eine KI-Schicht könnte diesen Satz in ein strukturiertes Aufgabobjekt übersetzen.
const task = {
action: "screenshot",
url: "https://example.com",
output: "homepage.png"
};
Allerdings würde der KI niemals die Erlaubnis erteilt, direkt beliebigen JavaScript-Code auszuführen.
Jede angeforderte Aktion müsste zunächst einer Validierungsstufe unterzogen werden.
const allowedActions = new Set([
"visit",
"search",
"screenshot",
"download"
]);
function validateTask(task) {
if (
!allowedActions.has(
task.action
)
) {
throw new Error(
"Unsupported action."
);
}
if (
task.url &&
!task.url.startsWith("https://")
) {
throw new Error(
"Invalid URL."
);
}
return true;
}
Dadurch entsteht eine klare Trennung der Verantwortlichkeiten:
Die KI interpretiert, was der Benutzer möchte.
Die JavaScript-Schicht entscheidet, was tatsächlich erlaubt ist.
Playwright führt nur die genehmigte Aktion aus.
Ein solches schichtweises Design ist ein viel sichererer Ansatz als die direkte, uneingeschränkte Kontrolle über die Maschine durch ein KI-Modell.
11. Das Projekt als verkaufbares Produkt neu formulieren
In dieser Phase richtete sich der Fokus nicht mehr auf die zugrundeliegenden Bibliotheken, sondern auf den eigentlichen Kunden.
Der Verkaufsvorschlag sollte niemals lauten:
"Eine Browser-Automatisierungsanwendung, die mit Playwright und JavaScript erstellt wurde."
Niemand sucht gezielt nach Playwright – sie suchen ein Ergebnis.
Daher wurde das Angebot selbst zum Ergebnis. Einige Beispiele:
Webseitenüberwachung
Unternehmen können ihre eigenen Webseiten überwachen und benachrichtigt werden, wenn wichtige Seiten sich ändern oder nicht mehr verfügbar sind.
Automatisierte Qualitätskontrolle
Ingenieurteams können konsistente, wiederholbare Browser-Prüfungen an ihren eigenen Anwendungen durchführen.
Interne Workflow-Automatisierung
Organisationen können wiederholende, browserbasierte Aufgaben in den Tools automatisieren, auf die sie bereits Zugriff haben.
Berichtserstellungs-Automatisierung
Eine terminierte Aufgabe kann genehmigte Daten automatisch sammeln, speichern und zu einem Bericht zusammenfassen.
Automatisierung für Agenturen
Eine Agentur kann maßgeschneiderte Automatisierungsworkflows für Kunden entwickeln und für die Einrichtung sowie den laufenden Support abrechnen.
Die Preise können in verschiedenen Formen angegeben werden:
- Eine feste Einmalkostenpauschale für die Einrichtung
- Monatliche Wartungsgebühren
- Preis pro individuellem Workflow
- Lizenzierung nach Teamgröße
- Maßgeschneiderte Integrationen
- Hosted-Zugang auf Abonnementbasis
Der Schlüssel besteht darin, das Angebot an ein konkretes, spezifisches Problem zu knüpfen und nicht an den Technologiestack.
12. Kleine Skripte können zu echten Produkten heranwachsen
Zum Schluss sah die Gesamtsystemarchitektur ungefähr so aus:
User
│
▼
CLI / AI Input
│
▼
Task Validator
│
▼
Workflow Engine
│
┌────────┼────────┐
▼ ▼ ▼
Playwright Cheerio SQLite
│ │ │
└────────┼────────┘
▼
Result Data
│
▼
Report / API
Das ist der Teil, mit dem man sich auseinandersetzen muss.
Das zugrunde liegende Problem betraf nie wirklich die Browserautomatisierung an sich.
Es ging darum, wiederholende manuelle Arbeiten zu beseitigen.
Die Bibliotheken – Playwright, Cheerio, SQLite, Commander – waren lediglich die Mechanismen, um diese Wiederholungen in funktionsfähige Software umzuwandeln.
Diese Denkweise prägt heute, wie neue JavaScript-Projekte angegangen werden.
Sobald eine Abfolge von manuellen Schritten zum zwanzigsten Mal wiederholt wird, ist der Instinkt nicht:
"Zeit, ein neues Framework zu nutzen.“
Sondern:
">Könnte dieser Workflow in eine Funktion umgewandelt werden?"
Falls die Antwort ja lautet, handelt es sich um den Ausgangspunkt für ein Automatisierungsprojekt.
Und wenn diese Automatisierung jemandem genügend Zeit und Aufwand spart, kann sie zu etwas werden, das man verkaufen kann.
Zusätzliche Literatur
- Layered Node.js API Design: Von umfangreichen Controllern zur sauberen Architektur — Erfahren Sie, wie man eine Node.js-API in Controller-, Service- und Datenzugriffsschichten refaktorieren kann, um verwickelte Geschäftslogik, inkonsistente Fehler sowie Skalierungsprobleme zu beheben.