Rohe Web-Scraping-Daten landen fast nie in einem Zustand, den ein Modell verwenden kann. Ein paar tausend Produktseiten, Listings oder Artikel ziehen und man erhält duplizierte Zeilen, Preise als Strings gespeichert, Datumsangaben in fünf Formaten, leere Zellen und Text durchsetzt mit HTML-Entities und unregelmäßigen Leerzeichen. Das direkt in einen Trainings-Job einspeisen und das Modell lernt das Rauschen genauso bereitwillig wie das Signal. Die Arbeit, die einen Scrape in ein Dataset verwandelt, ist Datenbereinigung und -strukturierung, und hier wird der Großteil der Genauigkeit einer AI- oder ML-Pipeline tatsächlich gewonnen oder verloren.

Diese Anleitung ist ein praxisnaher Walkthrough, um Web-Daten für AI zu strukturieren und zu bereinigen: einen rohen Scrape laden, ihn deduplizieren, Typen und Formate normalisieren, fehlende Werte behandeln, grundlegende Textbereinigung und Tokenisierung durchführen, ein Schema entwerfen, auf das der nachgelagerte Code sich verlassen kann, und das Ergebnis validieren, bevor es jemals ein Modell erreicht. Jeder Schritt enthält ausführbares Python, das man in ein Notebook einfügen kann. Am Ende zeigen wir, wo Crawlbase saubere, strukturierte Ausgabe von vornherein liefern kann, sodass man weniger davon von Hand erledigen muss.

Warum Bereinigung und Strukturierung die Modellqualität bestimmen

Modelle denken nicht über Daten nach, wie man es tut. Eine duplizierte Zeile ist zusätzliches Gewicht auf einem Beispiel. Ein als "$1,299.00" gespeicherter Preis ist ein String, den das Modell nicht mit 1299.0 vergleichen kann. Ein Datum, das in manchen Zeilen als "03/04/2025" und in anderen als "2025-04-03" geschrieben ist, wird zu zwei nicht verwandten Tokens. Keines dieser Dinge wirft einen Fehler, was genau der Grund ist, warum sie gefährlich sind: Die Pipeline läuft, die Metriken sehen plausibel aus, und das Modell lernt still aus einer verfälschten Sicht der Welt.

Bereinigung behebt den offensichtlichen Schaden (Duplikate, fehlende Werte, inkonsistente Formate) und Strukturierung verhängt einen Vertrag: Jede Spalte hat einen Typ, eine Einheit und eine Bedeutung. Dieser Vertrag erlaubt es, dasselbe Dataset heute einem Klassifizierer und nächstes Quartal einem anderen Modell zuzuspeisen, ohne Überraschungen. Dieselbe Disziplin gilt, ob man Web-Scraping für maschinelles Lernen mit einigen tausend Zeilen oder einen Web-Scraping in großem Maßstab bis in die Millionen betreibt.

Mit einem realistischen rohen Scrape beginnen

Um die Schritte konkret zu machen, nehmen wir an, man hat eine Reihe von E-Commerce-Produktlisting gescrapt und in raw_products.csv geschrieben. Ein echter Scrape ist unordentlich, also ist die Datei unten es auch: duplizierte Zeilen, Währungssymbole und Tausendertrennzeichen in price, gemischte Datumsformate, leere Zellen und Rezensionstext mit HTML-Entities und unregelmäßigen Leerzeichen.

python
import pandas as pd

df = pd.read_csv("raw_products.csv")

# A first look at the damage before touching anything
print(df.shape)
print(df.dtypes)
print(df.isna().sum())
print(df.head())

Die dtypes-Ausgabe ist das verräterische Zeichen. Wenn price als object statt als numerischer Typ zurückkommt, konnte pandas es nicht parsen, was bedeutet, dass nicht-numerischer Müll in der Spalte steckt. isna().sum() früh auszuführen sagt einem, welche Spalten fehlende Werte haben und wie schlimm es ist, sodass man entscheiden kann, was zu beheben ist, bevor man eine einzige Transformation schreibt.

Zuerst deduplizieren

Deduplizierung kommt vor allem anderen, weil Duplikate jede spätere Statistik aufblähen. Exakte Duplikate sind der einfache Fall: identische Zeilen von einem Scraper, der dieselbe URL erneut besucht oder über ein überschneidendes Fenster paginiert hat.

python
# Drop fully identical rows
df = df.drop_duplicates()

# Deduplicate on a business key, keeping the most recent capture
df = (df.sort_values("scraped_at")
        .drop_duplicates(subset=["product_id"], keep="last"))

Das zweite Muster ist in der Praxis wichtiger. Zwei Erfassungen derselben product_id sind keine identischen Zeilen mehr, sobald sich der Preis geändert hat, aber normalerweise möchte man einen Datensatz pro Produkt, nicht zwei. Nach Erfassungszeit sortieren und "last" behalten gibt die aktuellste Version. Den Schlüssel wählen, der tatsächlich eine Entität in der eigenen Domäne identifiziert (eine Produkt-ID, eine URL, eine SKU), statt sich auf vollständige Zeilengleichheit zu verlassen.

Vor der Imputation deduplizieren

Die Reihenfolge ist hier keine Äußerlichkeit. Wenn man zuerst fehlende Werte füllt und dann dedupliziert, werden die imputierten Durchschnitte über aufgeblähte Zählungen berechnet und zu den am häufigsten duplizierten Entitäten hin verzerrt. Immer Duplikate löschen, bevor man irgendeine Statistik (Mittelwert, Median, Modus) berechnet, von der spätere Schritte abhängen.

Typen und Formate normalisieren

Mit einer Zeile pro Entität jede Spalte zu einem einzigen, vorhersehbaren Typ machen. Das ist der Schritt, der "$1,299.00" in 1299.0 und fünf Datumsformate in eines verwandelt. Währungsstrings brauchen ihre Symbole und Trennzeichen entfernt, bevor sie als Zahlen geparst werden können; Datumsangaben brauchen einen einzigen Parser mit errors="coerce", sodass nicht parsbarer Müll zu NaT wird statt den Lauf zum Absturz zu bringen.

python
# Strip currency symbols and separators, then parse to float
df["price"] = (df["price"]
    .astype("string")
    .str.replace(r"[^\d.]", "", regex=True))
df["price"] = pd.to_numeric(df["price"], errors="coerce")

# Parse mixed date formats into one datetime type
df["listed_on"] = pd.to_datetime(
    df["listed_on"], errors="coerce"
)

# Normalize a categorical column: trim and lowercase
df["category"] = df["category"].str.strip().str.lower()

Die Normalisierung von Kategorien ist hier der stille Gewinn. Scrapes liefern routinemäßig "Electronics", "electronics " und "ELECTRONICS" als drei verschiedene Werte für eine Kategorie. Trimmen und Kleinschreibung kollabiert sie zu einem, was sauberere Group-by-Ergebnisse und ein Feature statt drei nach der Kodierung bedeutet. Dieselbe Standardisierung auf Einheiten anwenden: wenn einige Preise in Dollar und andere in Cent sind, jetzt auf eine einzige Einheit umrechnen, solange man noch weiß, was was ist.

Fehlende Werte bewusst behandeln

Es gibt keine universelle Regel für fehlende Daten, nur Abwägungen. Zeilen löschen ist sicher, wenn die Lücken selten sind und die Zeile ohne das Feld nutzlos ist. Imputation behält die Zeile, erfindet aber einen Wert, sodass sie nur Sinn macht, wenn die Spalte benötigt wird und eine vernünftige Schätzung existiert. Pro Spalte entscheiden, statt einen pauschalen Aufruf auf den gesamten Frame anzuwenden.

python
# Drop rows missing a field the record cannot exist without
df = df.dropna(subset=["product_id", "price"])

# Impute a numeric column with its median (robust to outliers)
df["rating"] = df["rating"].fillna(df["rating"].median())

# Fill a categorical with an explicit sentinel, not a guess
df["brand"] = df["brand"].fillna("unknown")

Median schlägt Mittelwert bei der Imputation numerischer Spalten, weil einige extreme Ausreißer (ein falsch gescrapter Preis von 99999) den Mittelwert ziehen, aber den Median kaum bewegen. Für Kategorien ist ein explizites "unknown"-Sentinel ehrlich: Es sagt dem Modell, dass der Wert fehlte, anstatt so zu tun, als gehörte er zur häufigsten Kategorie. Einen nachgelagerten Encoder nie stillschweigend NaN als echte Kategorie behandeln lassen.

Text bereinigen und tokenisieren

Wenn das Dataset Freitext enthält (Produktbeschreibungen, Rezensionen, Artikeltexte), braucht es seinen eigenen Durchlauf. Gescrapter Text kommt mit HTML-Entities (&, '), übriggebliebenen Tags, URLs und inkonsistenten Leerzeichen an. Vor dem Tokenisieren bereinigen oder die Tokens werden voller Müll sein. Tokenisierung bedeutet hier das Aufteilen von Text in die Einheiten, die ein Modell verbraucht; das folgende Beispiel macht Leerzeichen-Tokenisierung, was ausreicht, um die Bereinigung zu veranschaulichen, die zuerst kommen muss.

python
import re
import html

def clean_text(value):
    if pd.isna(value):
        return ""
    text = html.unescape(str(value))   # & -> &
    text = re.sub(r"<[^>]+>", " ", text)   # strip tags
    text = re.sub(r"http\S+", " ", text)   # strip URLs
    text = re.sub(r"\s+", " ", text)         # collapse whitespace
    return text.strip().lower()

df["review_clean"] = df["review"].apply(clean_text)
df["tokens"] = df["review_clean"].str.split()

Die Reihenfolge innerhalb von clean_text ist bewusst: zuerst Entities unescapen, damit &amp;amp; zu & wird, bevor ein Regex läuft, dann Tags und URLs entfernen, zuletzt Leerzeichen kollabieren, damit die durch die vorherigen Entfernungen entstandenen Lücken geschlossen werden. Kleinschreibung am Ende hält Token-Zählungen korrekt ("Fast" und "fast" werden zu einem Token). Für echte NLP-Arbeit würde man das abschließende Split durch einen geeigneten Tokenizer ersetzen, aber die Bereinigung oben ist der Teil, den gescrapte Daten immer brauchen.

Crawlbase Crawling API

Der Großteil des Entity-Strippings oben existiert, weil rohe HTML-Scrapes verrauscht sind. Die Crawling API kann saubere, strukturierte Ausgabe zurückgeben (einschließlich einer Markdown-Ansicht der Seite), sodass der Text ohne Tags und Boilerplate ankommt und der Bereinigungsschritt auf Typnormalisierung reduziert wird. Auf dem kostenlosen Tarif auf eine öffentliche Seite zeigen und die Ausgabe mit einem rohen Abruf vergleichen.

Ein Schema entwerfen und durchsetzen

Bis hierher war die Bereinigung reaktiv. Ein Schema macht daraus einen Vertrag: eine deklarierte Menge von Spalten, jede mit einem Typ, die jeder Batch erfüllen muss. Das Schema als Typen zu kodieren, zu denen man castet (und als Assertions, die man prüft), bedeutet, dass der nächste Scrape entweder konform ist oder laut scheitert, anstatt still in dasselbe Chaos zu driften, das man gerade bereinigt hat.

python
# A schema is just a column -> dtype contract
schema = {
    "product_id": "string",
    "category":   "category",
    "price":      "float64",
    "rating":     "float64",
    "brand":      "string",
    "listed_on":  "datetime64[ns]",
}

# Keep only schema columns, in order, and cast each one
df = df[list(schema.keys())].astype(schema)

list(schema.keys()) auswählen löscht alle Streuspalten, die der Scraper hinzugefügt hat, und fixiert die Spaltenreihenfolge, sodass jeder Export dieselbe Form hat. Der astype(schema)-Aufruf castet jede Spalte und löst einen Fehler aus, wenn ein Wert nicht gecastet werden kann, was das gewünschte Verhalten ist: besser ein lauter Fehler jetzt als eine beschädigte Spalte, die nach einem Trainings-Lauf entdeckt wird. Den category-Dtype für Felder mit niedriger Kardinalität wie category zu verwenden schrumpft auch den Speicher und beschleunigt Group-bys bei großen Frames.

Vor dem Export validieren

Validierung ist das Tor zwischen "sieht sauber aus" und "ist sauber." Eine Handvoll Assertions fängt die Fehler ab, die ein Modell still vergiften: überlebende Duplikate, Zahlen außerhalb des Bereichs, Nulls in Spalten, die vollständig sein sollten. Sie bei jedem Batch ausführen und die Pipeline stoppen, wenn eine fehlschlägt.

python
def validate(frame):
    assert frame["product_id"].is_unique, "duplicate product_id"
    assert frame["price"].between(0, 100000).all(), "price out of range"
    assert frame["rating"].between(0, 5).all(), "rating out of range"
    assert frame[["product_id", "price"]].notna().all().all(), "unexpected nulls"
    return frame

df = validate(df)

Bereichsprüfungen verdienen ihren Platz: ein Rating von 50 auf einer 0-bis-5-Skala oder ein negativer Preis ist fast immer ein Parse-Fehler aus einem früheren Schritt, und die Assertion macht ihn sichtbar, bevor die Daten ein Modell erreichen. Wenn man über handgeschriebene Asserts hinauswächst, drückt eine Schema-Validierungsbibliothek wie Pandera oder Great Expectations dieselben Regeln deklarativ aus, aber die Asserts oben reichen aus, um eine Pipeline vertrauenswürdig zu machen.

Das bereinigte Dataset exportieren

Mit dedupliziertem, normalisiertem, imputiertem, schema-gecastetem und validiertem Frame in einem Format ausgeben, das die Typen erhält. CSV ist portabel, aber stringly typed; Parquet behält Dtypes, komprimiert gut und lädt schneller, was wichtig wird, wenn man AI-Modelltraining über das Ergebnis durchführt.

python
# Parquet preserves dtypes and is fast to reload
df.to_parquet("clean_products.parquet", index=False)

# CSV if you need maximum portability
df.to_csv("clean_products.csv", index=False)

Diese Datei ist jetzt ein Dataset, kein Scrape: eine Zeile pro Entität, ein Typ pro Spalte, keine überlebenden Duplikate, fehlende Werte absichtlich behandelt, und jeder Wert innerhalb seines deklarierten Bereichs. Von hier aus ist der Weg zu einem Modell der vertraute der Feature-Entwicklung und eines Train/Test-Splits, und die Daten darunter werden keine Überraschungen bereithalten.

Die Quelle sauberere Daten liefern lassen

Der schnellste Bereinigungsschritt ist der, den man überspringt, weil die Daten sauber ankamen. Viel der obigen Arbeit (Entity-Unescaping, Tag-Stripping, Leerzeichen-Kollabierung) existiert nur, weil man rohes HTML gescrapt hat. Die Crawling API kann eine saubere, Markdown-artige Ansicht einer Seite zurückgeben, sodass der Text ohne Tags und Boilerplate kommt, und die Crawling API parsed viele populäre Sites automatisch in strukturierte JSON-Felder, was das Selector-Schreiben und den Großteil der Typ-Raterei entfernt, bevor pandas die Daten überhaupt sieht. Wenn man rotierende Residential-IPs ohne Verwaltung eines Pools benötigt, deckt der Smart AI Proxy diese Seite ab.

Nichts davon entfernt die Notwendigkeit, zu deduplizieren, zu validieren und ein Schema durchzusetzen (das sind Eigenschaften des Datasets, nicht der Seite), aber es schrumpft den verrauschten ersten Teil der Arbeit. Wo dieses Dataset als nächstes hingeht, zeigt Wie AI-Datenextraktion funktioniert und für hochvolumige Erfassung die Muster in E-Commerce-Web-Scraping.

Zusammenfassung

Wichtigste Erkenntnisse

  • Zuerst deduplizieren. Duplikate löschen, bevor man Statistiken berechnet, sonst werden imputierte Durchschnitte über aufgeblähte, verzerrte Zählungen berechnet.
  • Typen und Formate normalisieren. Währungssymbole zu Floats stripping, gemischte Datumsangaben mit errors="coerce" parsen, und Kategorien trimmen sowie kleinschreiben.
  • Fehlende Werte pro Spalte behandeln. Zeilen löschen, wenn ein wesentliches Feld fehlt, Numerika mit dem Median imputieren, und Kategorien mit einem expliziten "unknown" füllen.
  • Text vor dem Tokenisieren bereinigen. Entities unescapen, Tags und URLs stripping, dann Leerzeichen kollabieren, in dieser Reihenfolge.
  • Ein Schema definieren und validieren. Jede Spalte auf einen deklarierten Typ casten und bei jedem Batch auf Eindeutigkeit, Bereiche und Nicht-Null prüfen, damit schlechte Daten laut scheitern.
  • Sauberere Eingabe bedeutet weniger Arbeit. Crawlbase kann saubere oder Markdown-Ausgabe und automatisch geparste JSON zurückgeben, was den Bereinigungsschritt schrumpft, bevor pandas läuft.

Häufig gestellte Fragen

Warum ist Datenbereinigung so wichtig vor dem Training eines AI-Modells?

Weil Modelle alles lernen, was in den Daten steht, einschließlich der Fehler. Duplikate übergewichten manche Beispiele, String-typisierte Preise können nicht verglichen werden, gemischte Datumsformate fragmentieren zu nicht verwandten Werten, und fehlende Zellen werden von Encodern falsch interpretiert. Keines davon wirft Fehler, also läuft die Pipeline und das Modell trainiert still auf einer verfälschten Sicht. Bereinigung entfernt diesen Schaden, damit das Modell das Signal statt das Rauschen lernt.

Soll ich zuerst deduplizieren oder fehlende Werte behandeln?

Zuerst deduplizieren. Wenn man vor dem Löschen von Duplikaten imputiert, wird jeder Durchschnitt, mit dem man füllt, über aufgeblähte Zählungen berechnet und zu den am häufigsten duplizierten Entitäten hin verzerrt. Exakte Duplikate löschen und auf einem Business-Key kollabieren (die aktuellste Erfassung behalten), dann die Mediane und Modi berechnen, die man zur Imputation verwendet.

Wie entscheide ich zwischen Zeilen löschen und fehlende Werte imputieren?

Pro Spalte entscheiden. Die Zeile löschen, wenn das fehlende Feld eines ist, ohne das der Datensatz nicht existieren kann, etwa ein Primärschlüssel oder der Zielwert, und die Lücken selten sind. Imputieren, wenn die Spalte nachgelagert benötigt wird und eine vertretbare Schätzung existiert: Median für Numerika, weil er gegenüber Ausreißern robust ist, und ein explizites "unknown"-Sentinel für Kategorien, damit die Abwesenheit aufgezeichnet statt geraten wird.

Was ist die minimale Textbereinigung, die gescrapte Daten brauchen?

HTML-Entities unescapen, übriggebliebene Tags und URLs stripping, und Leerzeichen-Läufe zu einzelnen Leerzeichen kollabieren, in dieser Reihenfolge, dann kleinschreiben. Gescrapter Text enthält routinemäßig &amp;, verirrtes Markup und unregelmäßige Abstände, die sonst zu verrauschten Tokens werden. Dieser Durchlauf reicht vor der Leerzeichen-Tokenisierung; für Produktions-NLP würde man danach einen dedizierten Tokenizer einsetzen.

Warum lohnt sich die Durchsetzung eines Schemas, wenn die Daten bereits sauber sind?

Weil der nächste Batch nicht sauber sein wird, es sei denn, etwas zwingt ihn dazu. Ein Schema deklariert einen Typ pro Spalte und castet jeden Batch darauf, sodass ein Scrape, der driftet (eine neue Spalte, ein Preis, der sich plötzlich nicht mehr parsen lässt), laut scheitert statt still das Chaos wieder einzuführen, das man gerade entfernt hat. Es verwandelt Bereinigung von einer Einmaligkeit in einen wiederholbaren Vertrag.

Kann Crawlbase den Bereinigungsaufwand reduzieren?

Ja, für den verrauschten ersten Teil. Die Crawling API kann eine saubere, Markdown-artige Ansicht einer Seite zurückgeben, sodass Text ohne Tags oder Boilerplate ankommt, und die Scraper API parsed viele populäre Sites automatisch in strukturiertes JSON, was Selector-Schreiben und den meisten Typ-Raterei entfernt. Deduplizieren, validieren und ein Schema durchsetzen muss man selbst, weil das Eigenschaften des Datasets statt der Quellseite sind.

Jetzt loslegen

Crawlen Sie jede Website im großen Maßstab, ohne gegen die Infrastruktur zu kämpfen.

Crawlbase übernimmt Proxys, Fingerprints und CAPTCHAs, damit Ihr Team Datenpipelines ausliefert, statt Crawl-Infrastruktur zu pflegen. 1.000 Anfragen kostenlos, keine Karte erforderlich.

Self-Service · Kein Verkaufsgespräch erforderlich · Enterprise-Crawl-Volumen verfügbar