Zum Inhalt springen
Zurück zum Blog

So extrahieren Sie HTML-Tabellen in JavaScript und exportieren saubere CSV-Dateien

Mihai MaximZuletzt aktualisiert am 13 min read
So extrahieren Sie HTML-Tabellen in JavaScript und exportieren saubere CSV-Dateien
TL;DR: Stellen Sie zunächst fest, ob die Tabellenzeilen in der HTTP-Antwort enthalten sind, über eine Datenanfrage eingehen oder von einem Browser gerendert werden müssen. Verwenden Sie dann das statische Axios- und Cheerio-Skript oder den unten beschriebenen gezielten Puppeteer-Ansatz, um HTML-Tabellen in JavaScript zu scrapen, header-gesteuerte Datensätze zu validieren, Seiten zu deduplizieren und CSV-Dateien sicher zu schreiben. Eine HTML-Tabelle ordnet Informationen in Zeilen und Spalten an, in der Regel mit <table>, <tr>, <th>, und <td> -Elementen. Das Auslesen einer HTML-Tabelle in JavaScript bedeutet, dieses Markup abzurufen oder zu rendern, jede Datenzeile in ein JavaScript-Objekt umzuwandeln, das Ergebnis zu validieren und es in ein Format wie CSV zu serialisieren.

Das Durchlaufen der Zeilen ist selten der schwierige Teil. Die meisten Fehler treten auf, weil der Scraper die Serverantwort ausliest, während der Entwickler ein vom Browser gerendertes DOM überprüft, die falsche Tabelle anspricht, feste Zellenpositionen annimmt oder nicht maskierte Werte exportiert. Ein zuverlässiger Arbeitsablauf wählt die einfachste Extraktionsmethode, mit der die Daten tatsächlich erfasst werden können.

Dieses Node.js-Tutorial zum Web-Scraping beginnt mit Axios und Cheerio für statisches Markup und fügt anschließend einen gezielten Puppeteer-Ansatz zum Scrapen von Tabellen für Zeilen hinzu, die nach dem Laden der Seite erstellt wurden. Der Parser leitet Schlüssel aus den Tabellenüberschriften ab, anstatt von einem einzigen Datensatz auszugehen. Er meldet zudem fehlerhafte Zeilen, bewahrt Werte als Zeichenketten auf, bis eine Konvertierung bewusst erfolgt, und überprüft, ob die Ausgabe verwendbar ist. Das Ergebnis ist eine praktische Methode zum Scrapen von HTML-Tabellen in JavaScript, ohne so zu tun, als könnten rowspan, colspan, verschachtelte Tabellen oder jedes benutzerdefinierte Raster automatisch abgeflacht werden könnten.

Wählen Sie den richtigen Extraktionsansatz, bevor Sie Code schreiben

Bevor Sie HTML-Tabellen in JavaScript auslesen, sollten Sie feststellen, wo sich die Zeilendaten befinden. Im Standard-Markup <tr> eine Zeile dar, <th> bezeichnet in der Regel eine Spalte, und <td> enthält einen regulären Wert. Die Referenz zum HTML-Tabellenelement auf MDN ist eine nützliche semantische Hilfe, wenn Ihnen die Struktur unbekannt ist.

Befolgen Sie diese Reihenfolge:

  1. Zeilen im HTML der Antwort: Fordere die Seite mit Axios an und parse sie mit Cheerio.
  2. Von Fetch oder XHR zurückgegebene Zeilen: Verwenden Sie den zulässigen Daten-Endpunkt, sofern dieser stabil und geeignet ist.
  3. Zeilen, die erst nach Skripten oder Interaktionen erstellt werden: Verwenden Sie Browser-Automatisierung.

Diese Reihenfolge sorgt dafür, dass ein JavaScript-Tabellen-Scraper einfacher und kostengünstiger zu betreiben ist, als für jede Seite mit einem Browser zu beginnen.

Überprüfen Sie, ob die Zeilen im Antwort-HTML vorhanden sind

Öffnen Sie „Quelltext anzeigen“ (nicht nur das Element-Panel) und suchen Sie nach einem eindeutigen Zellwert. Sie können diesen Wert auch speichern response.data und direkt danach suchen. Sind der Wert und ein bereichsspezifischer Zeilenselektor vorhanden, ist Browser-Automatisierung nicht erforderlich – selbst wenn die Seite Paginierungselemente anzeigt.

Testen Sie Selektoren in den DevTools, bevor Sie wiederholte Anfragen senden, und überprüfen Sie sie anschließend anhand des abgerufenen Markups. Ein Spickzettel für CSS-Selektoren hilft dabei, wenn table tr auch Navigation, verschachtelte Tabellen oder Fußzeilenzeilen erfasst werden sollen.

Prüfen Sie auf über das Netzwerk abgerufene oder vom Browser gerenderte Zeilen

Falls der Wert im Quellcode fehlt, filtere das Netzwerk-Panel nach „Fetch/XHR“, lade die Seite neu, ändere die Seite oder den Filter und überprüfe die Antworten. Ein zulässiger, stabiler Datenendpunkt lässt sich in der Regel leichter validieren als gerenderte Zellen, aber vergewissere dich, dass dessen Verwendung erlaubt ist.

Wählen Sie einen Headless-Browser, wenn JavaScript, der Anmeldestatus, Klicks oder das Scrollen unerlässlich sind. Ein Vergleich zwischen Cheerio und Puppeteer kann helfen, diesen Kompromiss zu dokumentieren. Vermeiden Sie reguläre Ausdrücke als allgemeinen HTML-Parser; verwenden Sie sie nur für eng gefasste Muster innerhalb einer bereits ausgewählten Zelle.

Erstellen Sie einen wiederverwendbaren Axios- und Cheerio-Scraper, um HTML-Tabellen in JavaScript zu scrapen

Bei einer statischen Seite ist der Ablauf einfach: Axios ruft die Antwort ab, Cheerio lädt das Markup, und ein bereichsgebundener Parser wandelt Zeilen in Objekte um. Die wichtige Designentscheidung betrifft die Konfiguration. Nehmen Sie die URL, den Tabellenselektor und den Ausgabepfad als Eingaben entgegen und leiten Sie dann Datensatzschlüssel aus den Überschriften der ausgewählten Tabelle ab.

Dieser Web-Scraping-Ansatz mit Axios und Cheerio vermeidet eine instabile Zuordnung wie „Zelle Null ist Name, Zelle Eins ist Preis“. Er funktioniert bei herkömmlichen Tabellen, deren Überschriften und Zeilenbreiten konsistent sind, und deckt dennoch strukturelle Ausnahmen auf.

Richten Sie das Node.js-Projekt ein und rufen Sie die Seite sicher ab

Erstellen Sie ein Projekt und installieren Sie die drei Pakete, die im statischen Beispiel verwendet werden:

mkdir table-scraper
cd table-scraper
npm init -y
npm install axios cheerio objects-to-csv

Erstellen scrape-table.js. Das vollständige Skript verwendet durchgehend CommonJS, wendet ein Zeitlimit für Anfragen an, akzeptiert nur erfolgreiche HTTP-Statuscodes und fasst Anfrage, Parsing, Validierung und Export in einer einzigen Fehlergrenze zusammen. Es versucht nicht blindlings, Client-Fehler erneut auszuführen, da wiederholte Anfragen einen fehlenden Selektor, verweigerten Zugriff oder eine ungültige URL nicht beheben können.

Paket-APIs können sich ändern. Fixieren Sie die Versionen, nachdem Sie das Skript in Ihrer Umgebung getestet haben, und lesen Sie die offizielle Paketdokumentation durch, bevor Sie das Beispiel als Produktionsabhängigkeit verwenden.

Wählen Sie die Tabelle aus und leiten Sie stabile Spaltennamen ab

Beschränken Sie den Selektor nach Möglichkeit auf eine Tabelle, zum Beispiel #results anstatt table. Der Parser wählt bewusst die erste Übereinstimmung aus und sucht dann nach der letzten Zeile in <thead>. Wenn kein <thead>, behandelt er die erste Zeile, die <th> Zellen als Kopfzeile. Sind noch immer keine Beschriftungen vorhanden, generiert er column_1, column_2usw.

Überschriftentext wird auf Kleinbuchstaben im Snake-Case-Format normiert. Eine leere Überschrift erhält einen positionsbasierten Fallback, während Duplikate zu Schlüsseln wie price und price_2. Dadurch wird verhindert, dass spätere Zellen frühere Eigenschaften überschreiben. Bei mehrsprachigen Überschriften oder einem öffentlichen Datenvertrag sollte die automatische Normalisierung durch eine explizite Überschriftenzuordnung ersetzt werden.

Mehrzeilige Überschriften, rowspan, und colspan erfordern tabellenspezifische Logik, da das visuelle Raster möglicherweise nicht mit der Anzahl der DOM-Zellen übereinstimmt. Machen Sie keine stillschweigenden Annahmen. Überprüfen Sie das Markup, definieren Sie die beabsichtigten Spalten und fügen Sie einen Schritt zur Rastererweiterung hinzu, wenn diese Semantik von Bedeutung ist.

Ordnen Sie jede Zeile einem Objekt ohne fest codierte Feldnamen zu

Lies die direkten untergeordneten Zellen jeder Zeile statt jeder Nachkommezelle. Diese Unterscheidung verhindert, dass eine verschachtelte Tabelle ihrer übergeordneten Zeile unerwartete Werte hinzufügt. Das Skript erstellt zunächst eine Matrix aus bereinigten Zellstrings, überspringt Zeilen, die keinen aussagekräftigen Text enthalten, und berechnet die breiteste beobachtete Zeile.

Anschließend erstellt es für jede Spalte einen Schlüssel und ordnet den Schlüsseln die Werte anhand des Indexes zu. Fehlende Werte werden zu leeren Zeichenfolgen. Zusätzliche Zellen erhalten generierte Überschriften, sobald sie beim ersten Durchlauf entdeckt werden. Der Parser protokolliert außerdem eine Warnung, sobald die Zellanzahl einer Zeile von der berechneten Breite abweicht, sodass verschobene Spalten nicht unbemerkt bleiben.

Dies ist der wiederverwendbare Kern beim Scrapen von HTML-Tabellen in JavaScript: Das Ziel-Array befindet sich außerhalb der Zeileniteration, und jede akzeptierte Zeile trägt ein vollständiges Objekt bei. Es ist dennoch wichtig, Warnungen zu überprüfen. Eine Breitenabweichung kann auf eine legitime, sich über mehrere Zeilen erstreckende Zelle, eine versteckte Kontrollspalte, fehlerhaftes Markup oder einen Selektor hinweisen, der in einen anderen Tabellenabschnitt übergegangen ist.

Normalisieren und validieren Sie die extrahierten Werte

Normalisieren Sie Darstellungsrauschen, nicht die Bedeutung. Das Beispiel wandelt geschützte Leerzeichen in normale Leerzeichen um, kürzt die Enden und fasst wiederholte Leerzeichen zusammen. Es belässt Datumsangaben, Währungen, Prozentsätze, Bezeichner und führende Nullen als Zeichenfolgen, da Werte wie 01/02/03 oder 1,234 ohne Ländereinstellungen und Schemaregeln mehrdeutig sind.

Die Validierung sollte deutlich fehlschlagen, wenn der Tabellenselektor fehlt oder keine nicht leeren Datensätze mehr vorhanden sind. Überprüfen Sie außerdem erforderliche Spalten, sehen Sie sich ein Beispielobjekt an, prüfen Sie Warnungen wegen Nichtübereinstimmungen und vergewissern Sie sich, dass die endgültige Datei vorhanden ist, bevor ein nachgelagerter Job sie verarbeitet. Wenn Sie typisierte Werte benötigen, wenden Sie nach der Extraktion explizite, spaltenbezogene Konverter an und behalten Sie den Rohwert für die Fehlersuche bei. Ein Leitfaden zur Datenauswertung ist eine nützliche Hilfe, wenn diese Konvertierungen zu einer separaten Pipeline-Stufe werden.

Serialisieren Sie die Datensätze sicher in CSV

Erstellen Sie CSV-Dateien nicht mit row.join(","). Kommas, doppelte Anführungszeichen, Wagenrückläufe und Zeilenumbrüche innerhalb einer Zelle müssen maskiert und in Anführungszeichen gesetzt werden. Verwenden Sie einen aktiv gepflegten Serializer, dessen Verhalten Sie getestet haben. Das Beispiel verwendet objects-to-csv, gibt die Eigenschaften jedes Objekts in derselben Reihenfolge wie im Header wieder und schreibt das resultierende JavaScript-Array in eine CSV-Datei.

Testen Sie die gewählte Version mit Testdaten, die Kommas, Anführungszeichen, eingebettete Zeilenumbrüche, leere Werte und Nicht-ASCII-Text enthalten. Überprüfen Sie das Verhalten in Bezug auf Kopfzeilen und UTF-8 in den Anwendungen, die die Datei lesen werden. Eine korrekte CSV-Serialisierung kann eine unregelmäßige Quelltabelle nicht reparieren; beheben Sie übergreifende Zellen, doppelte Semantiken und nicht übereinstimmende Zeilen vor dem Export.

Führen Sie den vollständigen Scraper für statische Tabellen aus

Speichern Sie Folgendes unter scrape-table.js. Er umfasst die Abfrage, die Auswahl, die Normalisierung der Kopfzeilen, die Zeilenzuordnung, die Prüfungen und den CSV-Export. Bearbeiten Sie requiredColumns , wenn nachgelagerte Daten garantierte Felder benötigen.

const axios = require("axios");
const cheerio = require("cheerio");
const ObjectsToCsv = require("objects-to-csv");

const [url, tableSelector = "table", outputPath = "table.csv"] =
  process.argv.slice(2);

const requiredColumns = [];

function clean(value) {
  return value
    .replace(/\u00a0/g, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function uniqueHeaders(labels, width) {
  const seen = new Map();

  return Array.from({ length: width }, (_, index) => {
    const normalized = clean(labels[index] || "")
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, "_")
      .replace(/^_+|_+$/g, "");

    const base = normalized || `column_${index + 1}`;
    const count = (seen.get(base) || 0) + 1;
    seen.set(base, count);

    return count === 1 ? base : `${base}_${count}`;
  });
}

function parseTable(html) {
  const $ = cheerio.load(html);
  const table = $(tableSelector).first();

  if (!table.length) {
    throw new Error(`No table matched selector: ${tableSelector}`);
  }

  let headerRow = table.find("thead tr").last();

  if (!headerRow.length) {
    const firstRow = table.find("tr").first();
    if (firstRow.children("th").length) {
      headerRow = firstRow;
    }
  }

  const headerLabels = headerRow
    .children("th, td")
    .map((_, cell) => clean($(cell).text()))
    .get();

  let dataRows = table.children("tbody").children("tr");
  if (!dataRows.length) {
    dataRows = table.children("tr");
  }

  const headerNode = headerRow.get(0);
  const matrix = dataRows
    .toArray()
    .filter((row) => row !== headerNode)
    .map((row) =>
      $(row)
        .children("th, td")
        .map((_, cell) => clean($(cell).text()))
        .get()
    )
    .filter((cells) => cells.some(Boolean));

  const width = Math.max(
    0,
    headerLabels.length,
    ...matrix.map((cells) => cells.length)
  );

  if (!width) {
    throw new Error("The selected table has no readable cells.");
  }

  const headers = uniqueHeaders(headerLabels, width);
  const warnings = [];

  if (headerLabels.length && headerLabels.length !== width) {
    warnings.push(
      `Header has ${headerLabels.length} cells; widest row has ${width}.`
    );
  }

  const records = matrix.map((cells, rowIndex) => {
    if (cells.length !== width) {
      warnings.push(
        `Row ${rowIndex + 1} has ${cells.length} cells; expected ${width}.`
      );
    }

    return Object.fromEntries(
      headers.map((header, cellIndex) => [
        header,
        cells[cellIndex] ?? "",
      ])
    );
  });

  return { headers, records, warnings };
}

async function main() {
  if (!url) {
    throw new Error(
      'Usage: node scrape-table.js "<url>" "<table-selector>" "<output.csv>"'
    );
  }

  const response = await axios.get(url, {
    timeout: 15000,
    responseType: "text",
    maxRedirects: 5,
    validateStatus: (status) => status >= 200 && status < 300,
    headers: {
      "User-Agent": "TableScraper/1.0 (+contact@example.com)",
    },
  });

  const { headers, records, warnings } = parseTable(response.data);

  if (!records.length) {
    throw new Error("The table matched, but no nonempty rows were parsed.");
  }

  for (const column of requiredColumns) {
    if (!headers.includes(column)) {
      throw new Error(`Required column is missing: ${column}`);
    }
  }

  if (warnings.length) {
    console.warn(warnings.slice(0, 10).join("\n"));
  }

  await new ObjectsToCsv(records).toDisk(outputPath);

  console.log(`Wrote ${records.length} rows to ${outputPath}`);
  console.log(`Columns: ${headers.join(", ")}`);
  console.log("Sample record:", records[0]);
}

main().catch((error) => {
  console.error(`Scrape failed: ${error.message}`);
  process.exitCode = 1;
});

Führen Sie ihn mit der Ziel-URL, einem in Anführungszeichen gesetzten Tabellenselektor und einem Ausgabepfad aus:

node scrape-table.js "https://example.com/page" "#results" "results.csv"

Überprüfen Sie die gemeldeten Spalten und den Beispieldatensatz, bevor Sie der CSV-Datei vertrauen. Behandeln Sie jede Warnung bezüglich der Spaltenbreite als ein Problem der Datenqualität, das untersucht werden muss.

Umgang mit JavaScript-gerenderten und paginierten Tabellen

Um HTML-Tabellen in JavaScript zu scrapen, wenn Zeilen in der Antwort fehlen, nutzen Sie Browser-Automatisierung, nachdem Sie einen geeigneten Daten-Endpunkt ausgeschlossen haben. Ein Headless-Browser führt Seitenskripte aus und kann Steuerelemente anklicken, verbraucht jedoch mehr Speicher und führt zu mehr Fehlerzuständen als eine HTTP-Anfrage.

Der im Folgenden beschriebene Ansatz nutzt Puppeteer und ein konfigurierbares Next-Steuerelement. Installieren Sie es im selben Projekt:

npm install puppeteer

Dieser Ansatz eignet sich für herkömmliche gerenderte Tabellen. Er versucht nicht, virtualisierte Raster zu lösen, bei denen DOM-Knoten wiederverwendet werden, noch Authentifizierungsherausforderungen oder beliebige Komponenten-Frameworks ohne Änderungen am Selektor.

Warten Sie auf Zeilen und erfassen Sie jede Seite oder jeden Scroll-Batch

Warten Sie auf einen Zeilenselektor, anstatt für eine beliebige Dauer zu pausieren. Extrahieren Sie auf jeder Seite eine Zellenmatrix, fügen Sie deren Datensätze hinzu, klicken Sie auf „Weiter“ und warten Sie, bis sich die Matrix ändert. Die offizielle Puppeteer- waitForSelector beschreibt das für den ersten Batch verwendete Warten auf den Selektor.

Speichern Sie dies als scrape-rendered.js. Der Code verwendet die Header-Strategie erneut, schlägt bei verschobenen Spalten fehl und verweigert das Schreiben einer Datei, wenn die Paginierung hängend zu sein scheint.

const puppeteer = require("puppeteer");
const ObjectsToCsv = require("objects-to-csv");

const [
  url,
  tableSelector = "#results",
  nextSelector = 'button[aria-label="Next"]',
  outputPath = "rendered.csv",
] = process.argv.slice(2);

function clean(value) {
  return value.replace(/\u00a0/g, " ").replace(/\s+/g, " ").trim();
}

function uniqueHeaders(labels, width) {
  const seen = new Map();

  return Array.from({ length: width }, (_, index) => {
    const normalized = clean(labels[index] || "")
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, "_")
      .replace(/^_+|_+$/g, "");

    const base = normalized || `column_${index + 1}`;
    const count = (seen.get(base) || 0) + 1;
    seen.set(base, count);
    return count === 1 ? base : `${base}_${count}`;
  });
}

async function main() {
  if (!url) {
    throw new Error(
      'Usage: node scrape-rendered.js "<url>" "<table>" "<next>" "<output.csv>"'
    );
  }

  const rowSelector = `${tableSelector} tbody tr`;
  const headerSelector =
    `${tableSelector} thead tr:last-child th, ` +
    `${tableSelector} thead tr:last-child td`;

  const browser = await puppeteer.launch({ headless: true });

  try {
    const page = await browser.newPage();
    await page.goto(url, {
      waitUntil: "domcontentloaded",
      timeout: 30000,
    });
    await page.waitForSelector(rowSelector, { timeout: 15000 });

    const labels = await page.$$eval(headerSelector, (cells) =>
      cells.map((cell) => cell.textContent.replace(/\s+/g, " ").trim())
    );

    const recordsByValue = new Map();
    let headers;
    let pageNumber = 0;

    while (true) {
      pageNumber += 1;

      if (pageNumber > 1000) {
        throw new Error("Safety limit reached before pagination completed.");
      }

      const matrix = await page.$$eval(rowSelector, (rows) =>
        rows.map((row) =>
          Array.from(row.children)
            .filter((cell) => cell.matches("th, td"))
            .map((cell) => cell.textContent.replace(/\s+/g, " ").trim())
        )
      );

      if (!matrix.length) {
        throw new Error(`No rows found on rendered page ${pageNumber}.`);
      }

      if (!headers) {
        const width = Math.max(
          labels.length,
          ...matrix.map((cells) => cells.length)
        );
        headers = uniqueHeaders(labels, width);
      }

      for (const cells of matrix) {
        if (cells.length !== headers.length) {
          throw new Error(
            `Column mismatch on page ${pageNumber}: ` +
              `${cells.length} cells, expected ${headers.length}.`
          );
        }

        const record = Object.fromEntries(
          headers.map((header, index) => [header, cells[index]])
        );

        recordsByValue.set(JSON.stringify(record), record);
      }

      const next = await page.$(nextSelector);
      if (!next) break;

      const disabled = await next.evaluate(
        (element) =>
          element.disabled ||
          element.getAttribute("aria-disabled") === "true" ||
          element.classList.contains("disabled")
      );

      if (disabled) break;

      const previous = JSON.stringify(matrix);
      await next.click();

      try {
        await page.waitForFunction(
          ({ rowSelector, previous }) => {
            const current = Array.from(
              document.querySelectorAll(rowSelector)
            ).map((row) =>
              Array.from(row.children)
                .filter((cell) => cell.matches("th, td"))
                .map((cell) =>
                  cell.textContent.replace(/\s+/g, " ").trim()
                )
            );

            return current.length > 0 && JSON.stringify(current) !== previous;
          },
          { timeout: 10000 },
          { rowSelector, previous }
        );
      } catch {
        throw new Error(
          `Pagination did not advance after page ${pageNumber}; ` +
            "refusing to save a possibly partial file."
        );
      }
    }

    const records = [...recordsByValue.values()];

    if (!records.length) {
      throw new Error("No rendered records were collected.");
    }

    await new ObjectsToCsv(records).toDisk(outputPath);
    console.log(`Wrote ${records.length} unique rows to ${outputPath}`);
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(`Rendered scrape failed: ${error.message}`);
  process.exitCode = 1;
});

Bei unendlichem Scrollen den Klick auf „Weiter“ durch eine Scroll-Aktion ersetzen und nach jedem Batch die Zeilenanzahl oder die Zeilensignatur vergleichen. Erst anhalten, wenn die Anzahl und die Scrollhöhe bei mehreren Überprüfungen unverändert bleiben, und eine Sicherheitsgrenze für die maximale Anzahl an Durchläufen einhalten.

Sicher anhalten und Datensätze deduplizieren

Beenden Sie den Vorgang, wenn „Weiter“ fehlt oder deaktiviert ist, und halten Sie ein Seitenlimit ein. Wenn das Steuerelement aktiviert bleibt, sich die Zeilensignatur jedoch nicht ändert, melden Sie eine unvollständige Paginierung, anstatt den Vorgang stillschweigend zu speichern.

Das Beispiel entfernt exakte Duplikate mit einem Map. Bevorzugen Sie eine stabile ID oder eine kanonische URL, sofern verfügbar, da Schlüssel für ganze Datensätze legitime wiederholte Zeilen zusammenführen können. Ein Leitfaden zum Web-Scraping mit JavaScript und Node.js kann dabei helfen, Navigation, Extraktion und Speicherung voneinander zu trennen.

Behebung häufiger Fehler beim Tabellenscraping

Wenn Sie HTML-Tabellen in JavaScript auslesen, klassifizieren Sie Fehler nach Abruf, Auswahl, Paginierung oder Export.

Symptom

Praktische Lösung

Axios analysiert null Zeilen

Durchsuchen Sie die gespeicherte Antwort nach einem bekannten Wert; falls dieser fehlt, überprüfen Sie die Netzwerkanfragen oder verwenden Sie einen Browser

Es wird keine Tabelle gefunden

Definieren Sie eine stabile ID oder einen Container und fügen Sie den fehlgeschlagenen Selektor in die Fehlermeldung ein

Spaltenverschiebung

Überprüfen Sie direkte Zellen und fügen Sie eine benutzerdefinierte Behandlung für übergreifende oder verschachtelte Strukturen hinzu

Nur Seite eins wird gespeichert

Warten Sie auf eine geänderte Zeilensignatur, protokollieren Sie den Seitenfortschritt und melden Sie einen Fehler bei einem blockierten Steuerelement

Zelltext ist leer

Lesen Sie den entsprechenden Link, die Eingabe oder das Datenattribut anstelle des reinen Textes

Anfragen geben den Status 403 oder 429 zurück

Stoppen Sie den Vorgang, überprüfen Sie die Zugriffsrechte, reduzieren Sie die Parallelität und wenden Sie gegebenenfalls ein begrenztes Backoff an

CSV-Spalten werden unterbrochen

Verwenden Sie einen getesteten Serializer und Fixtures, die Anführungszeichen, Kommas, Zeilenumbrüche und UTF-8-Text enthalten

Fügen Sie verantwortungsbewusste Sicherheitsvorkehrungen für die Produktion hinzu

Wenn Sie HTML-Tabellen in JavaScript über einen einmaligen Test hinaus auslesen, verwenden Sie eine prägnante Checkliste für den Produktivbetrieb:

  • Überprüfen Sie vor der Datenerfassung die Nutzungsbedingungen, Zugriffsregeln und Robots-Richtlinien. Holen Sie für sensible oder regulierte Anwendungsfälle eine entsprechende Genehmigung ein.
  • Halten Sie die Parallelität konservativ, zwischenspeichern Sie unveränderte Seiten, beachten Sie Retry-After , wo zutreffend, und begrenzen Sie Wiederholungsversuche mit Backoff. Keine Verzögerung ist universell sicher.
  • Erfassen Sie nur notwendige Felder, beschränken Sie den Zugriff, schützen Sie gespeicherte Daten und löschen Sie diese nach Ablauf der Aufbewahrungsfrist.
  • Protokollieren Sie Status, Selektor, Seitenzahl, Zeilenanzahl und Warnungen, wobei Sie Anmeldedaten und sensible Werte ausklammern.

Rechtliche Konsequenzen können je nach Website, Daten, Methode und Rechtsordnung variieren. Betrachten Sie dies als allgemeine technische Richtlinie, nicht als Rechtsberatung. Ein Leitfaden für Web-Scraping ohne Blockierung kann operative Überprüfungen hinzufügen, nachdem die Berechtigung und der Umfang geklärt sind.

Wichtige Erkenntnisse

  • Der schnellste Weg, eine HTML-Tabelle in JavaScript zu scrapen, besteht darin, zunächst die Antwort zu überprüfen und einen Browser nur dann zu verwenden, wenn Skripte oder Interaktionen tatsächlich erforderlich sind.
  • Leiten Sie eindeutige Schlüssel aus Überschriften ab, generieren Sie explizite Fallbacks und kennzeichnen Sie Abweichungen bei der Zellenanzahl, anstatt die Spalten einer Website fest zu codieren.
  • Behalten Sie Rohzeichenfolgen bei, bis die Konvertierungsregeln bekannt sind, und validieren Sie anschließend erforderliche Spalten, nicht leere Ausgaben, Warnungen und CSV-Randfälle.
  • Bei gerenderter Paginierung auf eine messbare Zeilenänderung warten, bei expliziten Signalen anhalten, die Schleife begrenzen und mit einem aussagekräftigen, stabilen Schlüssel deduplizieren.

FAQ

Warum können DevTools Tabellenzeilen anzeigen, die Axios und Cheerio nicht finden können?

DevTools zeigt das Live-DOM an, nachdem der Browser Skripte ausgeführt und das Markup normalisiert hat, während Axios die Antwortbytes des Servers empfängt. Clientseitiger Code kann nach dem Laden Daten abrufen, Zeilen einfügen oder einen Platzhalter ersetzen. Browser können zudem strukturelle Elemente wie <tbody>, vergleichen Sie daher beim Testen von Selektoren einen repräsentativen Wert und berücksichtigen Sie die DOM-Normalisierung.

Benötige ich Puppeteer, wenn eine Tabelle Paginierungssteuerelemente anzeigt?

Nein. Paginierungssteuerelemente können Zeilen ausblenden, die bereits im DOM vorhanden sind, Teile eines im Speicher befindlichen Arrays austauschen oder eine weitere Seite von einem Daten-Endpunkt anfordern. Verwenden Sie einen Browser nur dann, wenn das Wechseln zwischen Seiten eine JavaScript-Interaktion erfordert und kein geeigneter Endpunkt verfügbar ist. Überprüfen Sie den Quellcode, die Anzahl der DOM-Zeilen und die Netzwerkantworten, bevor Sie Browser-Automatisierung einsetzen.

Wählen Sie das Element innerhalb der Zelle aus und lesen Sie die entsprechende Eigenschaft aus. Verwenden Sie mit Cheerio .attr("href") für Links, .attr("data-id") für Datenattribute und .val() für Formularsteuerelemente. Lösen Sie relative Links mit new URL(relativeHref, pageUrl).href. Speichern Sie sichtbaren Text und das Attribut in separaten Feldern, wenn beide nützliche Informationen enthalten.

Kann ich die geparsten Datensätze als JSON speichern oder an eine Datenbank senden, anstatt sie als CSV zu exportieren?

Ja. Schreiben Sie JSON mit JSON.stringify(records, null, 2), senden Sie Datensätze an eine API oder fügen Sie sie in eine Datenbank ein. Validieren Sie zuvor das entsprechende Schema. Verwenden Sie bei Datenbanken gegebenenfalls parametrisierte Operationen, Batches oder Transaktionen sowie einen stabilen eindeutigen Schlüssel, damit bei erneuten Ausführungen Datensätze aktualisiert oder eingefügt werden können, anstatt versehentliche Duplikate zu erzeugen.

Fazit

Ein zuverlässiger Ansatz zum Auslesen von HTML-Tabellen in JavaScript beginnt mit der Diagnose, nicht mit der Wahl einer Bibliothek. Stellen Sie fest, ob die Zeilen in der Antwort vorhanden sind, über eine Netzwerkanfrage eingehen oder einen gerenderten Browser erfordern. Für statisches Markup bieten Axios und Cheerio einen schlanken Ansatz, während ein header-gesteuerter Parser das Ergebnis über herkömmliche Tabellen hinweg wiederverwendbar macht, anstatt sich auf ein festes Spaltenlayout zu beschränken.

Normalisieren Sie vor dem Export nur Darstellungs-Leerzeichen, behalten Sie mehrdeutige Werte als Zeichenfolgen bei, überprüfen Sie Pflichtfelder, prüfen Sie Warnungen bei Nichtübereinstimmungen und testen Sie die CSV-Anführungszeichen mit problematischen Testdaten. Bei dynamischen Tabellen sollten Sie auf Selektoren und Zeilenänderungen warten, explizite Stoppbedingungen verwenden, den Fortschritt der Paginierung aufzeichnen und mit einem Schlüssel, der die Daten widerspiegelt, Duplikate entfernen. Diese Prüfungen unterscheiden eine Demo von einem Scraper, den Sie debuggen können.

Beginnen Sie mit dem statischen Skript und erhöhen Sie die Komplexität erst, wenn die Seite dies erforderlich macht. Wenn Blöcke auf der Anfrageebene, CAPTCHA-Abfragen oder Proxy-Rotation zum Engpass werden, kann die Scraper-API von WebScrapingAPI Roh-HTML zurückgeben und gleichzeitig diese Probleme bewältigen, sodass Sie denselben Cheerio-Parsing- und Validierungscode beibehalten können.

Über den Autor

Mihai Maxim, Full-Stack-Entwickler @ WebScrapingAPI

Mihai Maxim

Full-Stack-Entwickler

Mihai Maxim ist Full-Stack-Entwickler bei WebScrapingAPI, wo er in verschiedenen Bereichen des Produkts mitwirkt und an der Entwicklung zuverlässiger Tools und Funktionen für die Plattform mitarbeitet.

Web-Scraping mit AWS Lambda: Leitfaden für Python und Java 2026
Anleitungen

Web-Scraping mit AWS Lambda: Leitfaden für Python und Java 2026

Kurz gesagt: Web-Scraping mit AWS Lambda funktioniert am besten, wenn jeder Aufruf kurz, begrenzt und unabhängig wiederholbar ist. Beginnen Sie mit direktem HTTP, AWS SAM und S3 und fügen Sie erst dann SQS, Container, Browser-Rendering, Proxys oder eine verwaltete Abrufebene hinzu, wenn sich im Rahmen der Arbeitslast herausstellt, dass diese erforderlich sind.

Suciu Dan26 min read
Artikel lesen
So verwenden Sie GoSpider: Crawlen, URLs bereinigen und Daten extrahieren
Anleitungen

So verwenden Sie GoSpider: Crawlen, URLs bereinigen und Daten extrahieren

TL;DR: GoSpider ist ein Befehlszeilen-Crawler zum Auffinden von URLs, kein vollständiger Scraper für strukturierte Daten. Diese Anleitung zur Verwendung von GoSpider zeigt einen begrenzten Crawl, die saubere Verarbeitung der Ausgabe, die Übergabe von Colly an CSV sowie einen Diagnosepfad für 403-Antworten oder Seiten, die eine JavaScript-Darstellung erfordern.

Suciu Dan20 min read
Artikel lesen
Wie man Redfin scrappt: Python-Leitfaden für Immobiliendaten
Anleitungen

Wie man Redfin scrappt: Python-Leitfaden für Immobiliendaten

TL;DR: Redfin stellt versteckte API-Endpunkte zur Verfügung, die strukturiertes JSON für Immobilienangebote zurückgeben, wodurch das fragile HTML-Parsing vollständig übersprungen werden kann. Diese Anleitung führt Sie durch den Aufbau eines Python-Scrapers, der Miet- und Verkaufsdaten extrahiert, nach Standort sucht, neue Angebote über XML-Sitemaps überwacht und saubere Ergebnisse in CSV oder JSON exportiert.

Suciu Dan11 min read
Artikel lesen

Los geht’s

Sind Sie bereit, Ihre Datenerfassung zu erweitern?

Schließen Sie sich den über 2.000 Unternehmen an, die WebScrapingAPI nutzen, um Webdaten im Unternehmensmaßstab ohne zusätzlichen Infrastrukturaufwand zu extrahieren.