Tutorial
Shotomatic Team
13 Min. Lesezeit

Website-Screenshots mit Puppeteer automatisieren

Automatisiere Website-Screenshots mit Puppeteer und JavaScript. Erfasse einzelne Seiten, Ganzseiten, URL-Stapel und mobile Ansichten mit einem wiederverwendbaren Skript.

Laptop mit Code für ein Website-Screenshot-Skript mit Puppeteer

In dieser Anleitung bauen wir schrittweise ein Screenshot-Skript mit Puppeteer: zuerst eine einzelne Ansichtsgröße, danach eine Ganzseiten-Aufnahme, einen wiederverwendbaren URL-Runner und eine mobile Ansicht.

Puppeteer passt gut dazu, weil schon das erste Skript klein bleiben kann: Browser starten, URL öffnen und page.screenshot() aufrufen. Die offizielle Puppeteer-Übersicht beschreibt Puppeteer als JavaScript-API zur Steuerung von Chrome oder Firefox, standardmäßig im Headless-Modus.

Die Beispiele verwenden die Werkzeugseite von Shotomatic unter https://www.shotomatic.com/tools. So kannst du vergleichen, wie dieselbe Seite als Ansichtsgröße, Ganzseite, stapelfähige Aufnahme und mobile Ansicht aussieht.

Kurz gesagt: Nutze Puppeteer, wenn Screenshot-Automatisierung in Code gehört. Lege die Ansichtsgröße fest, warte auf den wichtigen Seitenzustand, rufe page.screenshot() auf und ergänze danach Ganzseiten-Aufnahmen, Stapel, Wiederholungen und Berichte. Wenn du nur eine Liste öffentlicher URLs ohne eigenes Skript aufnehmen möchtest, ist ein No-Code-Ablauf wie Website Capture von Shotomatic möglicherweise einfacher.

Hinweis: Wir entwickeln Shotomatic. Deshalb verwenden die Beispiele unsere öffentliche Werkzeugseite als Ziel. Die gleichen Puppeteer-Muster funktionieren auch mit anderen öffentlichen Websites, sofern du sie aufnehmen darfst. Für URL-Listen ohne Code steht Website Capture in Shotomatic zur Verfügung.

Wann Puppeteer das richtige Werkzeug ist

Puppeteer passt, wenn der Screenshot-Ablauf in Code gehört und es hauptsächlich um die Steuerung des Browsers geht.

Nutze Puppeteer, wenn du:

  • öffentliche Seiten mit einem Node.js-Skript aufnehmen möchtest
  • das Rendering-Verhalten von Chrome für Screenshots nutzen möchtest
  • dich vor der Aufnahme anmelden oder ein Pop-up schließen musst
  • Ansichtsgrößen-, Ganzseiten-, PNG-, JPEG- oder WebP-Screenshots speichern möchtest

Der Screenshot stammt aus einem echten Headless-Browser, nicht aus einer Bildbibliothek. Genau deshalb ist Puppeteer hier so nützlich.

Puppeteer für ein Screenshot-Skript installieren

Installiere Puppeteer für ein gewöhnliches Node.js-Projekt:

$ mkdir website-screenshots-puppeteer
$ cd website-screenshots-puppeteer
$ npm init -y
$ npm i puppeteer
$ mkdir scripts screenshots

Das Standardpaket puppeteer lädt bei der Installation eine kompatible Version von Chrome for Testing herunter. Laut der offiziellen Installationsdokumentation von Puppeteer wird außerdem eine chrome-headless-shell-Binärdatei heruntergeladen. Wenn du den Browser bereits selbst verwaltest oder eine Verbindung zu einem Remote-Browser herstellst, nutze stattdessen puppeteer-core.

Moderne Paketmanager blockieren gelegentlich Installationsskripte. Falls Puppeteer installiert ist, aber der Browser fehlt, zeigt die offizielle Konfigurationsdokumentation, wie du Browser separat installierst:

$ npx puppeteer browsers install

Einen Website-Screenshot aufnehmen

Erstelle scripts/capture-one.mjs:

import puppeteer from "puppeteer";
import { mkdir } from "node:fs/promises";

const url = "https://www.shotomatic.com/tools";
const outputPath = "screenshots/shotomatic-tools.png";

const browser = await puppeteer.launch();
const page = await browser.newPage();

try {
  await mkdir("screenshots", { recursive: true });

  await page.setViewport({
    width: 1440,
    height: 1000,
    deviceScaleFactor: 1,
  });

  await page.goto(url, {
    waitUntil: "networkidle2",
    timeout: 45_000,
  });

  await page.screenshot({ path: outputPath });
  console.log(`Saved ${outputPath}`);
} finally {
  await browser.close();
}

Führe das Skript aus:

$ node scripts/capture-one.mjs

Das Skript erzeugt screenshots/shotomatic-tools.png:

Screenshot-Ausgabe des Puppeteer-Beispiels capture-one mit der Werkzeugseite von Shotomatic
Ausgabe der Werkzeugseite aus dem obigen Skript, aufgenommen mit einer Ansichtsgröße von 1440 × 1000.

Die offizielle Screenshot-Anleitung von Puppeteer verwendet denselben grundlegenden Ablauf: Browser starten, Seite öffnen, zu einer URL navigieren und page.screenshot() aufrufen. Die Dateiendung in path bestimmt das Ausgabeformat, sofern du nicht ausdrücklich die Option type übergibst. In der Dokumentation zu ScreenshotOptions und ImageFormat findest du Einzelheiten zu fullPage, path, quality und den unterstützten Formaten png, jpeg und webp.

Lege die Ansichtsgröße vor der Navigation fest. Darauf weist auch die Dokumentation zu page.setViewport() hin, weil manche Websites anders reagieren, wenn ihre Größe erst nach dem Laden geändert wird. Das gilt besonders für mobile Layouts.

Einen Ganzseiten-Screenshot aufnehmen

Übergib für einen Ganzseiten-Screenshot fullPage: true. In Puppeteers ScreenshotOptions ist fullPage als Option für die Aufnahme der gesamten Seite definiert:

await page.screenshot({
  path: "screenshots/shotomatic-tools-full-page.png",
  fullPage: true,
});

Nutze die Ganzseiten-Aufnahme, wenn du das gesamte scrollbare Dokument brauchst. Ein Ansichtsgrößen-Screenshot ist die bessere Wahl, wenn es darum geht, wie die Seite bei einer bestimmten Bildschirmgröße aussieht.

Ganzseiten-Ausgabe eines Puppeteer-Screenshots der kostenlosen Werkzeugseite von Shotomatic
Ganzseiten-Ausgabe derselben Seite `/tools`, nachdem die Abschnitte unterhalb des sichtbaren Bereichs gerendert wurden.

Auch eine Ganzseiten-Aufnahme hängt vom Zustand der Seite ab. fullPage: true ändert den Aufnahmebereich, zwingt aber weder Lazy-Loading-Bilder noch Animationen oder beim Scrollen eingeblendete Abschnitte zum Rendern. Der Abschnitt zu Lazy Loading weiter unten zeigt, warum der Seitenzustand wichtig ist.

Einen wiederverwendbaren URL-Runner erstellen

Sobald ein Screenshot funktioniert, verschiebst du die Ziel-URL in ein Skript, das eine Liste verarbeiten kann. Das Beispiel verwendet weiterhin nur /tools, damit die Ausgabe übersichtlich bleibt. Später kannst du demselben Array weitere URLs hinzufügen.

Erstelle scripts/capture-batch.mjs:

import puppeteer from "puppeteer";
import { mkdir } from "node:fs/promises";

const urls = ["https://www.shotomatic.com/tools"];

const outputDir = "screenshots";

function filenameForUrl(url, index) {
  const parsed = new URL(url);
  const rawName = `${parsed.hostname}${parsed.pathname}`;

  const slug = rawName
    .replace(/^www\./, "")
    .replace(/\/$/, "")
    .replace(/[^a-z0-9]+/gi, "-")
    .replace(/^-+|-+$/g, "")
    .toLowerCase();

  return `${String(index + 1).padStart(2, "0")}-${slug || "home"}.png`;
}

async function captureUrl(browser, url, index) {
  const context = await browser.createBrowserContext();
  const page = await context.newPage();
  const outputPath = `${outputDir}/${filenameForUrl(url, index)}`;

  try {
    await page.setViewport({
      width: 1440,
      height: 1000,
      deviceScaleFactor: 1,
    });

    await page.goto(url, {
      waitUntil: "networkidle2",
      timeout: 45_000,
    });

    await page.waitForSelector("body", {
      visible: true,
      timeout: 15_000,
    });

    await page.screenshot({
      path: outputPath,
      fullPage: true,
    });

    console.log(`Captured ${url} -> ${outputPath}`);
    return { url, ok: true, outputPath };
  } catch (error) {
    console.error(`Failed ${url}: ${error.message}`);
    return { url, ok: false, error: error.message };
  } finally {
    await context.close();
  }
}

await mkdir(outputDir, { recursive: true });

const browser = await puppeteer.launch();

try {
  const results = [];

  for (const [index, url] of urls.entries()) {
    results.push(await captureUrl(browser, url, index));
  }

  const failed = results.filter((result) => !result.ok);

  if (failed.length > 0) {
    process.exitCode = 1;
    console.error(`${failed.length} screenshot(s) failed.`);
  }
} finally {
  await browser.close();
}

Damit bleibt ein Browser geöffnet, während für jede URL ein eigener Browserkontext entsteht. Puppeteers Dokumentation zu BrowserContext beschreibt diese Kontexte als isolierte Benutzersitzungen mit getrennten Cookies und Speichern. Das hilft, wenn eine Seite während der Aufnahme Cookies oder Daten im lokalen Speicher ablegt.

Die Schleife läuft bewusst sequenziell. Das ist langsamer, lässt sich zu Beginn aber einfacher debuggen.

Parallelität für größere Stapel ergänzen

Bei wenigen URLs reicht die sequenzielle Aufnahme aus. Für größere Listen solltest du eine niedrige Obergrenze für parallele Aufnahmen einführen.

Beginne mit zwei bis vier gleichzeitigen Aufnahmen. Auch ein Headless-Browser braucht Prozessorleistung und Arbeitsspeicher. Zu aggressive Stapel können außerdem Zugriffsbeschränkungen auslösen oder zu instabilen Seitenaufrufen führen.

Ergänze diese Hilfsfunktion:

async function runWithConcurrency(items, limit, worker) {
  const results = new Array(items.length);
  let nextIndex = 0;

  async function runNext() {
    while (nextIndex < items.length) {
      const currentIndex = nextIndex;
      nextIndex += 1;
      results[currentIndex] = await worker(items[currentIndex], currentIndex);
    }
  }

  const workers = Array.from(
    { length: Math.min(limit, items.length) },
    () => runNext(),
  );

  await Promise.all(workers);
  return results;
}

Ersetze anschließend die sequenzielle Schleife:

const results = await runWithConcurrency(urls, 3, (url, index) =>
  captureUrl(browser, url, index),
);

So verarbeitet der Stapel mehr Seiten, ohne dass das Skript eine unbegrenzte Zahl von Seiten gleichzeitig öffnet.

Mobile Screenshots aufnehmen

Lege die Ansichtsgröße vor page.goto() fest, um einen Screenshot in Mobilgröße aufzunehmen:

await page.setViewport({
  width: 390,
  height: 844,
  deviceScaleFactor: 2,
  isMobile: true,
  hasTouch: true,
});

await page.goto("https://www.shotomatic.com/tools", {
  waitUntil: "networkidle2",
});

await page.screenshot({
  path: "screenshots/shotomatic-tools-mobile-viewport.png",
});

Das reicht, wenn du nur eine ungefähre mobile Ansichtsgröße brauchst. Hier siehst du dieselbe Werkzeugseite mit diesen Einstellungen:

Mobile Screenshot-Ausgabe der Werkzeugseite von Shotomatic aus Puppeteer
Mobile Ausgabe aus Puppeteer. Die Datei ist 780 × 1688 Pixel groß, weil das Skript den Wert 2 für deviceScaleFactor verwendet.

Wenn du ein umfangreicheres Geräteprofil brauchst, stellt Puppeteer außerdem bekannte Geräte bereit:

import puppeteer, { KnownDevices } from "puppeteer";

const iPhone = KnownDevices["iPhone 15"];

await page.emulate(iPhone);
await page.goto("https://www.shotomatic.com/tools", {
  waitUntil: "networkidle2",
});
await page.screenshot({
  path: "screenshots/shotomatic-tools-iphone-15.png",
  fullPage: true,
});

Geräteemulation ist sinnvoller, wenn Ansichtsgröße, User-Agent, Touch-Verhalten und Pixeldichte gemeinsam wichtig sind. Laut der Dokumentation zu page.emulate() setzt die Methode sowohl den User-Agent als auch die Ansichtsgröße. Rufe sie deshalb vor der Navigation auf.

Inhalte mit Lazy Loading berücksichtigen

Viele Seiten laden Bilder oder blenden Abschnitte erst ein, wenn sie in den sichtbaren Bereich gelangen. Nimmst du die gesamte Seite zu früh auf, können dort leere Flächen erscheinen, wo Inhalte unterhalb des sichtbaren Bereichs noch nicht gerendert wurden.

Ganzseiten-Screenshot aus Puppeteer mit einer großen leeren Fläche, bevor Inhalte mit Lazy Loading gerendert wurden
Sofort aufgenommen: Die Seitenhöhe stimmt, aber ein Teil des Inhalts wurde noch nicht gerendert.
Ganzseiten-Screenshot aus Puppeteer nach dem Scrollen durch Abschnitte mit Lazy Loading
Nach dem Scrollen aufgenommen: Die Abschnitte unterhalb des sichtbaren Bereichs konnten eingeblendet werden.

Scrolle vor einem Ganzseiten-Screenshot durch das Dokument, um verbreitete Lazy-Loading-Mechanismen auszulösen:

async function triggerLazyLoading(page) {
  await page.evaluate(async () => {
    const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
    const viewportHeight = window.innerHeight;
    const scrollHeight = document.body.scrollHeight;

    for (let y = 0; y < scrollHeight; y += viewportHeight) {
      window.scrollTo(0, y);
      await delay(150);
    }

    window.scrollTo(0, 0);
  });
}

Rufe die Funktion vor page.screenshot() auf:

await page.goto(url, {
  waitUntil: "networkidle2",
  timeout: 45_000,
});

await triggerLazyLoading(page);

await page
  .waitForFunction(
    () => Array.from(document.images).every((image) => image.complete),
    { timeout: 15_000 },
  )
  .catch(() => {});

await page.screenshot({
  path: outputPath,
  fullPage: true,
});

Manche Seiten brauchen mehr als das. Eigene Scroll-Container, Animationen, verzögerte API-Aufrufe und virtualisierte Inhalte können einen seitenspezifischen Selektor oder Zustand erfordern.

Die passende Wartestrategie wählen

Die meisten Zuverlässigkeitsprobleme bei Screenshots hängen mit dem Zeitpunkt der Aufnahme zusammen.

networkidle2 ist für viele Screenshot-Skripte ein vernünftiger Ausgangspunkt und wird auch in Puppeteers eigener Screenshot-Anleitung im Grundbeispiel verwendet. Es ist jedoch keine Garantie dafür, dass die Seite fertig ist. Manche Websites halten Hintergrundanfragen offen, andere rendern den Hauptinhalt erst nach Ende der Netzwerkaktivität.

Übliche Wartestrategien:

  • Nutze waitUntil: "networkidle2" für gewöhnliche öffentliche Seiten.
  • Nutze page.waitForSelector(), wenn ein bestimmtes Element zeigt, dass die Seite bereit ist.
  • Nutze page.waitForFunction(), wenn die Bereitschaft von einem App-Zustand abhängt.
  • Scrolle vor der Aufnahme, wenn Bilder erst bei Bedarf geladen werden.
  • Verlasse dich nicht hauptsächlich auf feste Wartezeiten.

Beispiel:

await page.goto(url, {
  waitUntil: "networkidle2",
  timeout: 45_000,
});

await page.waitForSelector("main", {
  visible: true,
  timeout: 15_000,
});

await page.screenshot({
  path: outputPath,
  fullPage: true,
});

Wenn du die Ziel-Website selbst betreust, sind Selektor-Wartebedingungen normalerweise zuverlässiger als eine allgemeine Netzwerkbedingung.

Wiederholungsversuche und einen Bericht ergänzen

Echte Screenshot-Stapel schlagen gelegentlich aus ganz gewöhnlichen Gründen fehl: vorübergehende Netzwerkfehler, langsame Seiten, Weiterleitungen, Zugriffsbeschränkungen, Cookie-Banner und Skripte von Drittanbietern.

Ergänze Wiederholungsversuche für jede URL:

async function withRetries(task, retries = 2) {
  let lastError;

  for (let attempt = 0; attempt <= retries; attempt += 1) {
    try {
      return await task();
    } catch (error) {
      lastError = error;
      console.warn(`Attempt ${attempt + 1} failed: ${error.message}`);
    }
  }

  throw lastError;
}

Umschließe damit die eigentliche Aufnahme:

await withRetries(async () => {
  await page.goto(url, {
    waitUntil: "networkidle2",
    timeout: 45_000,
  });

  await page.waitForSelector("body", {
    visible: true,
    timeout: 15_000,
  });

  await page.screenshot({
    path: outputPath,
    fullPage: true,
  });
});

Schreibe für wiederkehrende Jobs einen JSON-Bericht mit URL, Ausgabepfad, Erfolgsstatus, Fehlermeldung und Zeitstempel. Der Screenshot zeigt die Seite, der Bericht erklärt den Lauf.

Eine praktische Ordnerstruktur

Trenne bei wiederkehrenden Jobs Eingaben, Ausgaben und Berichte:

website-screenshots-puppeteer/
  scripts/
    capture-batch.mjs
  urls/
    product-pages.txt
    competitor-pages.txt
  screenshots/
    2026-06-23-product-pages/
    2026-06-23-competitors/
  reports/
    2026-06-23-product-pages.json

So lässt sich der Ablauf leichter wiederholen und debuggen. Außerdem bleibt jeder Screenshot-Satz mit der URL-Liste verbunden, aus der er entstanden ist.

Häufige Fehler

  • Aufnahme, bevor die Seite bereit ist: Ein abgeschlossenes page.goto() bedeutet nicht immer, dass der wichtige Inhalt gerendert wurde. Warte auf einen Selektor oder App-Zustand.
  • Zu spät gesetzte Ansichtsgröße: Lege sie vor der Navigation fest, damit die Seite von Anfang an mit dem richtigen Layout lädt.
  • fullPage mit Lazy-Loading-Behandlung verwechseln: Die Ganzseiten-Option ändert den Aufnahmebereich. Sie garantiert nicht, dass jedes verzögert geladene Bild verfügbar ist.
  • Zu viele Seiten gleichzeitig öffnen: Beginne mit niedriger Parallelität. Browserautomatisierung kann deinen Rechner oder die Ziel-Website überfordern.
  • Dateinamen wiederverwenden: URLs können sich nur durch einen abschließenden Schrägstrich, die Abfragezeichenfolge oder die Groß- und Kleinschreibung im Pfad unterscheiden. Ergänze einen Index und bereinige die Namen.
  • Aufräumen überspringen: Schließe Kontexte und Browser. Bei länger laufenden Jobs führen offen gebliebene Seiten und Sitzungen schnell zu Problemen.

Alternativen zu einem Puppeteer-Screenshot-Skript

Puppeteer ist nur eine Möglichkeit, Website-Screenshots zu automatisieren.

Wenn der Job in Code bleibt, ist Playwright die naheliegendste Alternative. Die Umsetzung beschreiben wir in der Anleitung Website-Screenshots mit Playwright automatisieren. Playwright passt oft besser, wenn Screenshots Teil einer größeren Testsuite sind, du Chromium, Firefox und WebKit abdecken möchtest oder den Test-Runner sowie Traces und Berichte von Playwright Test für die Aufnahmen brauchst.

Wenn du Screenshot-Aufnahmen in ein Produkt einbaust, kann eine Screenshot-API weniger Arbeit verursachen, als Browser selbst zu verwalten. Sobald das Skript für andere zuverlässig laufen muss, liegen Browser-Binärdateien, CI-Arbeitsspeicher, Wiederholungen, Aufräumen und Anmeldestatus bei dir.

Wenn du dieses Skript nicht selbst schreiben und pflegen möchtest, lies die Anleitung Website-Screenshots ohne Code automatisieren. Oder nutze Website Capture in Shotomatic, wenn du URL-Listen, Optionen pro Seite, prüfbare Ergebnisse und Exporte ohne eigenes Puppeteer-Skript brauchst.

FAQ

Kann Puppeteer Website-Screenshots automatisieren?

Ja. Puppeteer kann einen Browser starten, eine URL öffnen, die Ansichtsgröße festlegen und mit page.screenshot() einen Screenshot speichern.

Kann Puppeteer Ganzseiten-Screenshots aufnehmen?

Ja. Übergib fullPage: true an page.screenshot(), um die gesamte scrollbare Seite statt nur der sichtbaren Ansichtsgröße aufzunehmen.

Kann ich mit Puppeteer mehrere URLs aufnehmen?

Ja. Lass einen Browser geöffnet, erstelle für jede URL eine Seite oder einen Browserkontext und begrenze bei größeren Stapeln die Parallelität.

Ist Puppeteer für Screenshots besser als Playwright?

Puppeteer ist für auf Chrome ausgerichtete Screenshot-Skripte oft einfacher. Playwright ist meist sinnvoller, wenn du Chromium, Firefox und WebKit abdecken oder die Aufnahmen in einen größeren Testablauf einbinden möchtest.

Wann sollte ich Shotomatic statt Puppeteer verwenden?

Nutze Shotomatic für einen No-Code-Ablauf mit URL-Listen, den jemand ohne Pflege eines JavaScript-Skripts ausführen, prüfen und exportieren soll.

Quellen

Die Beispiele oben wurden anhand der offiziellen Dokumentation geprüft:

Ähnliche Artikel

Weitere Artikel

Website-Screenshots ohne Code automatisieren

Finde den passenden No-Code-Weg für automatische Website-Screenshots: URL-Listen, Automatisierungsplattformen mit Screenshot-API, Monitoring oder Browser-Helfer.

7 Min. Lesezeit
Mehrere Bildschirme auf einem Schreibtisch für die Prüfung von Website-Screenshots

Website-Screenshots ohne Code automatisieren

Mit Website Capture nimmst du URL-Listen mit eigenen Einstellungen pro Seite auf, prüfst die Ergebnisse und exportierst sie – ohne ein Skript zu pflegen.

Website-Screenshots mit Puppeteer automatisieren | Blog | Shotomatic