Puppeteer ist Googles Node.js-Bibliothek zum Steuern von Chrome (und Firefox) über das Chrome DevTools Protocol oder WebDriver BiDi. Wenn eine Seite ihre Daten erst liefert, nachdem JavaScript gelaufen ist, bekommst du mit einem einfachen fetch nur eine leere Hülle — du brauchst einen echten Browser. Dieser Guide zeigt den praktischen Weg in 2026: installieren, eine JS-Sandbox scrapen, schwere Assets blockieren, JSON abfangen, ehrlich mit den Grenzen der Erkennung umgehen und wissen, wann du aufhören solltest, selbst Chromium zu betreiben.
Wenn du neu im Scraping bist, starte mit Web Scraping mit Python für die statische-HTML-Stufe. Für den plattformübergreifenden Python-Standard siehe Web Scraping mit Playwright. Dieser Beitrag richtet sich an Node.js-Teams, die gezielt Puppeteer wollen.
Warum Puppeteer fürs Scraping?
| Situation | Richtiges Werkzeug |
|---|---|
| Die Daten stecken im initialen HTML | fetch + ein Parser (cheerio) — spar dir den Browser |
| Die Daten kommen aus einer JSON-API, die die Seite aufruft | Ruf diese API direkt auf, sobald du sie in den DevTools gefunden hast |
| Die Daten rendern erst nach JavaScript | Puppeteer (oder Playwright) |
| Mehrstufige UI (klicken, tippen, scrollen) | Puppeteer (oder Playwright) |
| Screenshots / PDFs | Puppeteer |
Puppeteer bleibt die natürliche Wahl, wenn dein Stack ohnehin Node ist, du nur Chromium brauchst und einen schlanken CDP-Wrapper willst. Playwright gewinnt bei Multi-Browser-Abdeckung und Auto-Waiting-Ergonomie in Greenfield-Projekten; der Vergleich folgt weiter unten in diesem Beitrag.
Setup und dein erster Scrape
npm i puppeteer
# If install scripts are blocked by your package manager:
npx puppeteer browsers install
puppeteer lädt einen passenden Chrome-Build herunter. Nutze puppeteer-core, wenn du auf ein System-Chrome oder einen Remote-Browser-Dienst zeigst.
Hier ist ein vollständiger Scraper gegen quotes.toscrape.com/js, eine Sandbox, die Zitate per JavaScript rendert (reines HTTP liefert null Zitate):
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto("https://quotes.toscrape.com/js/", {
waitUntil: "domcontentloaded",
});
await page.waitForSelector(".quote");
const quotes = await page.$$eval(".quote", (cards) =>
cards.map((card) => ({
text: card.querySelector(".text")?.textContent?.trim() ?? "",
author: card.querySelector(".author")?.textContent?.trim() ?? "",
tags: [...card.querySelectorAll(".tag")].map((t) => t.textContent.trim()),
})),
);
console.log(`scraped ${quotes.length} quotes`);
console.log(quotes[0]);
await browser.close();
Hinweise, die dir später Stunden sparen:
waitUntil: "domcontentloaded"reicht meist aus;networkidle0hängt sich bei Seiten mit Analytics oder Websockets auf.waitForSelector(oderpage.locator(...).wait()) ersetzt das Rätselraten mitsetTimeout.- Ein expliziter Viewport vermeidet die winzige Standard-Headless-Größe, die Mobile-Layouts und fehlende Desktop-Selektoren auslöst.
Daten sauber extrahieren
Drei Muster decken die meisten Scraper ab:
// 1) One field
const title = await page.$eval("h1", (el) => el.textContent.trim());
// 2) Many records ($$eval runs in the page context — fast)
const rows = await page.$$eval("article.product", (nodes) =>
nodes.map((n) => ({
name: n.querySelector("h2")?.textContent?.trim(),
price: n.querySelector(".price")?.textContent?.trim(),
})),
);
// 3) Locator API (Puppeteer 22+) — auto-retry style waits
await page.locator("button#load-more").click();
Paginiere, indem du in einer Schleife auf "Next" klickst, oder indem du ?page=N-URLs durchläufst, sofern die Seite sie anbietet. Begrenze die Nebenläufigkeit; jede offene Seite ist ein echter Browser-Prozess.
Power-Features für Scraper
Bilder, Fonts und Medien blockieren
Ein Scraper braucht keine Pixel. Brich schwere Ressourcentypen vor der Navigation ab:
await page.setRequestInterception(true);
page.on("request", (req) => {
const type = req.resourceType();
if (type === "image" || type === "font" || type === "media") {
req.abort();
} else {
req.continue();
}
});
await page.goto("https://quotes.toscrape.com/js/");
Erwarte deutliche Bandbreiten- und CPU-Ersparnisse bei bildlastigen Katalogen. Zwei Vorbehalte: Das Blockieren von Stylesheets kann sichtbarkeitsabhängige Selektoren zerstören, und das Entfernen von Analytics kann dich auf manchen Anti-Bot-Seiten verdächtiger machen.
JSON abfangen statt das DOM zu scrapen
Wenn der Network-Tab der DevTools einen XHR zeigt, der bereits strukturierte Daten liefert, greif ihn ab:
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
/** @type {any[]} */
const payloads = [];
page.on("response", async (res) => {
const url = res.url();
if (!url.includes("/api/") || res.request().resourceType() !== "xhr") return;
try {
payloads.push(await res.json());
} catch {
// not JSON
}
});
await page.goto("https://example.com/catalog", { waitUntil: "networkidle2" });
console.log(payloads[0]);
await browser.close();
Wenn das klappt, schlägt es CSS-Selektoren: typisierte Felder, weniger Ausfälle durch Redesigns, oft zusätzliche Felder, die die UI nie zeigt. Schau in den Network-Tab, bevor du auch nur einen Locator schreibst.
Parallele Seiten ohne N Browser
Starte einen Browser, öffne viele Seiten (oder Browser-Contexts) und drossle mit einem einfachen Pool:
import puppeteer from "puppeteer";
async function mapPool(items, limit, worker) {
const ret = [];
let i = 0;
await Promise.all(
Array.from({ length: limit }, async () => {
while (i < items.length) {
const idx = i++;
ret[idx] = await worker(items[idx]);
}
}),
);
return ret;
}
const browser = await puppeteer.launch({ headless: true });
const urls = [
"https://quotes.toscrape.com/js/page/1/",
"https://quotes.toscrape.com/js/page/2/",
];
const results = await mapPool(urls, 3, async (url) => {
const page = await browser.newPage();
try {
await page.goto(url, { waitUntil: "domcontentloaded" });
await page.waitForSelector(".quote");
return page.$$eval(".quote .text", (els) => els.map((e) => e.textContent));
} finally {
await page.close();
}
});
await browser.close();
console.log(results.flat().length);
Drei bis fünf gleichzeitige Seiten pro Maschine sind ein realistischer Startwert. Unbegrenzte Nebenläufigkeit lässt den Host in ein OOM laufen und bringt dir Rate-Limits beim Ziel ein.
Häufige Fehler und wie du sie behebst
| Fehler | Wahrscheinliche Ursache | Lösung |
|---|---|---|
| Timeout beim Warten auf einen Selektor | Falsches CSS, Inhalt in einem iframe, oder JS ist nie gelaufen | Headed ausführen (headless: false); page.frames() / Frame-Locators nutzen; auf eine Netzwerk-Response warten |
| Leerer Scrape, 200 HTML | SPA-Hülle; Daten laden später per XHR | waitForSelector auf den hydrierten Knoten oder das JSON abfangen |
| Läuft headed, scheitert headless | Fingerprint-/Viewport-Unterschiede; Lazy Load außerhalb des sichtbaren Bereichs | Viewport + realistischen User Agent setzen; mit page.mouse.wheel scrollen |
net::ERR_FAILED bei aktivierter Interception | req.continue() bei manchen Requests vergessen | Im Request-Handler immer explizit continue oder abort aufrufen |
| Ziel liefert CAPTCHA / soft 403 | Rechenzentrums-IP + Automatisierungssignale | Residential-Proxys helfen; Stealth-Plugins helfen ein wenig; bei Volumen eine verwaltete API nutzen |
Kann Puppeteer erkannt werden? Ja
Out of the box ist Headless Chrome, das von Puppeteer gesteuert wird, leicht zu erkennen. Typische Signale:
navigator.webdriver === true- Headless-User-Agent / fehlende Plugins / merkwürdige WebGL-Renderer-Strings
- CDP-Runtime-Artefakte, nach denen Erkennungs-Skripte suchen
- Rechenzentrums-IP-Reputation zusätzlich zum Fingerprint
Der übliche DIY-Fix ist puppeteer-extra mit puppeteer-extra-plugin-stealth:
import puppeteer from "puppeteer-extra";
import StealthPlugin from "puppeteer-extra-plugin-stealth";
puppeteer.use(StealthPlugin());
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto("https://example.com");
await browser.close();
Stealth hilft weiterhin gegen naive Checks. Gegen Cloudflare, DataDome, PerimeterX und ähnliche Anbieter bist du 2026 auf der falschen Seite eines Wettrüstens: Behavioral Scoring, TLS-Fingerprinting und kontinuierliche Labortests gegen bekannte Automatisierungs-Stacks. Kombiniere jeden Browser mit Residential-Proxys und rechne trotzdem mit CAPTCHAs und stillen Müll-Daten bei harten Zielen. Die Eskalationsleiter findest du in Seiten scrapen, die Bots blockieren.
Puppeteer vs. Playwright vs. Selenium
| Puppeteer | Playwright | Selenium 4 | |
|---|---|---|---|
| Heimatsprache | Node.js (offiziell) | Python, Node, Java, .NET | Viele (WebDriver) |
| Browser | Chrome/Firefox (CDP/BiDi) | Chromium, Firefox, WebKit | Alle gängigen über Treiber |
| Auto-Wait | Locators werden besser; oft noch manuell | Erstklassige Locators | Explizites WebDriverWait |
| Netzwerkkontrolle | Request Interception + CDP | page.route eingebaut | Historisch auf Add-ons angewiesen |
| Stealth-Ökosystem | Ausgereift (puppeteer-extra) | Portiert / patchright | undetected-chromedriver etc. |
| Beste Eignung | Node + Chromium-Scraper | Neue Multi-Browser-Scraper | Legacy-Grids / mehrsprachiges QA |
Wähle Puppeteer, wenn du in Node Chrome-first arbeitest und schon investiert hast. Wähle Playwright für einen Greenfield-Scraper, der eventuell WebKit/Firefox oder Python braucht. Wähle Selenium, wenn Grid und bestehende WebDriver-Suites dominieren. Keines von ihnen ist frei von Erkennung; alle sind im Flottenmaßstab teuer.
Wann du aufhören solltest, Browser zu betreiben
Eine Browser-Seite braucht ein echtes Stück CPU und Hunderte MB RAM. Ein Host, der Tausende einfache HTTP-Requests pro Minute stemmt, trägt vielleicht nur ein paar Dutzend gleichzeitige Puppeteer-Seiten. Kommen Residential-Proxys und CAPTCHA-Lösung dazu, bricht die Einheitswirtschaftlichkeit zusammen.
Für öffentliche Seiten, bei denen du nur sauberes HTML oder Markdown brauchst, übernimmt Crawloras Web Scraping API (POST /api/v1/web/scrape) den Abruf (und eskaliert bei Bedarf zu einem Headless-Browser, wenn render: "auto" das verlangt) — hinter einem einzigen Key:
curl -X POST "https://api.crawlora.net/api/v1/web/scrape" \
-H "x-api-key: $CRAWLORA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "formats": ["markdown", "html"], "render": "auto"}'
const res = await fetch("https://api.crawlora.net/api/v1/web/scrape", {
method: "POST",
headers: {
"x-api-key": process.env.CRAWLORA_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://example.com",
formats: ["markdown", "html"],
render: "auto",
}),
});
const { data } = await res.json();
console.log(data.markdown?.slice(0, 400));
console.log("fetch method:", data.scrape?.method); // "http" | "browser" | ...
Für Plattformen, die Crawlora bereits normalisiert (Amazon, Reddit, Google SERP, …), bevorzuge den Plattform-Endpoint gegenüber einem generischen Browser: Du bekommst typisiertes JSON, statt Selektoren zu pflegen. Das kostenlose Kontingent liegt bei 2.000 Credits/Monat, ohne Karte; die Abrechnung erfolgt pay-on-success. Siehe Pricing, probiere eine beliebige URL im Free Web Scraper aus, oder öffne den Playground.
- Statisches HTML — nutze fetch + cheerio (oder Python requests + BeautifulSoup), kein Puppeteer.
- JS-gerendert, leicht geschützt — Puppeteer mit Asset-Blocking und Response-Interception.
- Die Daten kommen bereits als XHR-JSON — fang sie ab; überspring das DOM.
- Harte WAFs bei Volumen — verwaltetes /web/scrape oder eine Plattform-API schlägt eine Stealth-Farm.
- Gemischte Workloads — Puppeteer für die Long-Tail-UI-Flows behalten; API für Ziele mit hohem Volumen.
Spar dir die Chromium-Farm für geschützte öffentliche Seiten
Dokumentierte Endpoints, verwaltetes Rendering und Proxys, Pay-on-Success-Abrechnung. 2.000 Credits/Monat kostenlos, ohne Karte.
Weiterlesen
- Web Scraping mit Playwright — das Python-Multi-Browser-Gegenstück
- Selenium Web Scraping — der WebDriver-Weg und wann er noch sinnvoll ist
- Seiten scrapen, die Bots blockieren — die Eskalationsleiter der Erkennung
- Proxies für Web Scraping erklärt — Residential vs. Rechenzentrum-Ökonomie
- Die besten Web-Scraping-APIs 2026 — wann du die Zugriffsschicht kaufen solltest
Häufig gestellte Fragen
Ist Puppeteer 2026 gut für Web Scraping?
Ja, für Node.js-Teams, die Chromium brauchen, um JavaScript zu rendern, UI-Abläufe durchzuklicken oder Screenshots zu erfassen. Für statisches HTML ist es keine gute Standardwahl (nutze fetch + cheerio), und im Flottenmaßstab gegen moderne Anti-Bot-Anbieter wird es teuer.
Ist Puppeteer besser als Playwright fürs Scraping?
Für reine Chrome-Node-Stacks ist Puppeteer ein schlanker, ausgereifter CDP-Wrapper mit einem starken Stealth-Plugin-Ökosystem. Playwright ist meist besser für Greenfield-Scraper, die Multi-Browser-Abdeckung, erstklassige Auto-Waiting-Locators und erstklassige Python-Unterstützung brauchen.
Kann man Puppeteer erkennen?
Ja. Standard-Headless-Chrome zeigt navigator.webdriver, Headless-Fingerprints und CDP-Artefakte. puppeteer-extra-plugin-stealth patcht offensichtliche Lecks, aber Cloudflare, DataDome und ähnliche Anbieter bewerten auch TLS-Fingerprints, IP-Reputation und Verhalten — Stealth allein reicht bei Volumen nicht.
Wie scrape ich eine Seite, die Daten per JavaScript lädt?
Puppeteer starten, navigieren, auf den Selektor warten, der nach der Hydration erscheint (oder den XHR abfangen, der JSON liefert), dann mit $$eval oder der Locator-API extrahieren. Bevorzuge das Abfangen des eigenen JSON der Seite, wenn die DevTools es zeigen — das ist stabiler als DOM-Scraping.
Warum hängt sich networkidle in Puppeteer auf?
Seiten mit Analytics, Websockets oder Long-Polling werden nie vollständig idle, sodass networkidle0/2 den gesamten Timeout verbrennen kann. Bevorzuge waitUntil domcontentloaded plus waitForSelector (oder eine konkrete Response) für den Inhalt, den du brauchst.
Wann sollte ich eine Scraping-API statt Puppeteer nutzen?
Wenn Ziele hinter harten WAFs stehen, du hohe Nebenläufigkeit brauchst, oder Browser-RAM und Proxy-Betrieb mehr kosten als die Daten wert sind. Ein verwaltetes POST /web/scrape oder ein plattformspezifischer JSON-Endpoint nimmt dir die Flotte, die Stealth-Patches und die CAPTCHA-Tretmühle ab.
Ist Puppeteer kostenlos?
Ja — Open Source unter Apache-2.0. Deine echten Kosten sind Rechenleistung (Hunderte MB RAM pro Seite), Proxys für geschützte Seiten und Engineering-Zeit, um Selektoren und Stealth-Patches funktionsfähig zu halten.
