Gescrapte Daten mit pandas bereinigen: Python-Anleitung

Veröffentlicht:

14 Min. Lesezeit

Acar Diveroli
Autor: Acar Diveroli
Großes NaN zwischen Typ-Hilfslinien, ein blaues, wie eine Tabelle liniertes Band; Karten: ein roher Preistext wird zu 51.77.

Ihr Scraper ist über Nacht fertig geworden, und die CSV-Datei sieht auf den ersten Blick gut aus. Dann wollen Sie den Durchschnittspreis berechnen, und pandas verweigert den Dienst, weil jeder Preis ein Text wie £51.77 ist. Dasselbe Buch taucht zweimal auf, weil es auf einer Katalogseite und auf einer Kategorieseite stand. Ein Titel lautet Shakespeare’s, sieben Bücher stecken in einer Kategorie namens „Default“, und eine Zeile hat überhaupt keine Beschreibung. Nichts davon ist ein Fehler des Scrapers. So sehen rohe Webdaten aus, und bevor jemand daraus ein Diagramm zeichnet, brauchen sie einen Durchgang zur Datenbereinigung.

Dieser Leitfaden zeigt diesen Durchgang mit pandas an einem echten Datensatz, gescrapt von books.toscrape.com, einer Übungsseite, die eigens für Scraping gebaut wurde. Wir behandeln die Reparatur von Text und Unicode, fehlende Werte, die Umwandlung von Preistexten in Zahlen, weitere Zahlen, die in Text versteckt sind, Datumswerte, Kategorielabels, Duplikate, Ausreißer, Validierungsprüfungen und das Speichern des Ergebnisses. Das vollständige Skript lief am 28. September 2026 mit pandas 3.0.6, Requests 2.34.2, beautifulsoup4 4.15.0, pyarrow 25.0.1 und Python 3.13.

Was bedeutet „sauber“ bei gescrapten Daten?

Saubere Daten haben eine Zeile pro realem Objekt, einen Typ pro Spalte und eine Schreibweise pro Wert. Eine Preisspalte enthält Zahlen, keinen Text mit Währungszeichen. Ein zweimal gescraptes Buch erscheint einmal. Eine fehlende Beschreibung wird als fehlend erfasst, nicht als leerer String, der vorhanden wirkt. Eine Kategorie namens „Default“ bekommt einen Namen, der dem Analysten sagt, dass sie keine Information trägt.

Sauber heißt nicht vollständig oder in jedem Detail korrekt. Eine Beschreibung, die die Seite nie hatte, können Sie nicht zurückholen, und ein Bereinigungsskript sollte keine erfinden. Seine Aufgabe ist es, jedes Problem sichtbar und jeden verbleibenden Wert vertrauenswürdig zu machen, damit der nächste Schritt (ein Diagramm, ein Modell, ein Preisalarm) mit Fakten arbeitet statt mit Parsing-Unfällen.

Warum sind gescrapte Daten unordentlich?

Webseiten werden für Menschen geschrieben, und ein Scraper liest die Teile, die fürs Auge gedacht sind. Das meiste Durcheinander stammt aus wenigen Quellen:

  • Alles ist Text. HTML kennt keine Typen. £51.77, In stock (22 available) und Three (eine CSS-Klasse für eine Sternebewertung) kommen alle als Strings an.
  • Kodierungsschätzungen gehen schief. books.toscrape.com sendet Content-Type: text/html ohne Charset. In diesem Fall folgt Requests der alten HTTP/1.1-Regel und dekodiert die Seite als ISO-8859-1 (Requests-Dokumentation zu Kodierungen), sodass aus dem UTF-8-Pfundzeichen £ wird. Den Mechanismus erklärt der Beitrag Python-Unicode-Kodierungsfehler.
  • Derselbe Artikel ist über mehrere Wege erreichbar. Ein Produkt steht im Katalog, in seiner Kategorie und manchmal in einer „Neu“- oder „Angebote“-Liste. Crawlen Sie mehr als eine davon, erfassen Sie es doppelt. Paginierung, die sich zwischen Läufen verschiebt, bewirkt dasselbe.
  • Vorlagen unterscheiden sich. Einer von tausend Seiten fehlt ein Block, den die anderen haben, und Ihr Selektor liefert None.
  • Die Daten der Website sind unvollkommen. Platzhalterkategorien, überzählige Leerzeichen und „...more“-Anhängsel gehören zur Quelle und sind nicht Ihre Fehler.

Wie läuft ein Bereinigungsdurchgang ab?

Die Reihenfolge zählt, denn jeder Schritt baut auf dem vorigen auf:

  1. Rohdaten behalten. Speichern Sie genau das, was der Scraper gesammelt hat, in books_raw.csv. Die Bereinigung arbeitet auf einer Kopie, sodass Sie sie mit neuen Regeln wiederholen können, ohne erneut zu scrapen.
  2. Text normalisieren. Leerzeichen an beiden Enden entfernen, leere Strings in fehlende Werte umwandeln, fehlerhafte Kodierung reparieren und Unicode-Normalisierung anwenden.
  3. Fehlende Werte zählen. Halten Sie sie in einem Bericht fest und fügen Sie Markierungsspalten hinzu; füllen Sie sie nicht mit Vermutungen.
  4. Preise umwandeln. Ziehen Sie das Währungssymbol in eine eigene Spalte und wandeln Sie den Rest in eine Gleitkommazahl um.
  5. Weitere Zahlen extrahieren. Lagerbestände aus In stock (22 available), Bewertungen aus Wörtern, Rezensionszahlen aus Text.
  6. Datumswerte parsen und in Zeitstempel mit Zeitzone umwandeln.
  7. Kategorien normalisieren, sodass ein Begriff genau ein Label hat.
  8. Duplikate entfernen, anhand eines stabilen Schlüssels und erst, wenn die Typen stimmen.
  9. Ausreißer markieren, mit einer einfachen Regel, die Sie erklären können.
  10. Validieren und speichern. Bei einer fehlgeschlagenen Prüfung anhalten, andernfalls CSV und Parquet schreiben.

Die Deduplizierung kommt bewusst spät. Zwei Zeilen, die sich nur durch ein angehängtes Leerzeichen oder einen falsch dekodierten Apostroph unterscheiden, sind dasselbe Buch, und drop_duplicates erkennt das erst nach den Schritten 2 bis 7.

Roh- und bereinigte Werte im Vergleich

Das sind echte Werte aus dem Scraping und das, was das Skript daraus macht:

SpalteRohwertProblemBereinigter Wertpandas-Werkzeug
price£51.77Text, fehlerhafte Kodierung51.77 (float64) + currency = £str.extract, pd.to_numeric
availabilityIn stock (22 available)Zahl in einem Satzin_stock = 22 (Int64)str.extract
ratingThreeWort statt Zahl3 (Int64)map
titleShakespeare’s GlobeMojibake (Zeichensalat)Shakespeare’s Globeencode/decode, unicodedata
categoryDefaultPlatzhalterlabelUncategorized (category)replace, astype("category")
description… and their affair begins. ...moreSeitenelement im Text… and their affair begins.str.removesuffix
scraped_at2026-09-28T14:51:11+00:00Stringdatetime64[us, UTC]pd.to_datetime
upcderselbe Code in zwei ZeilenDoppeltes Bucheine Zeile, neueste Kopiedrop_duplicates

Was hat sich in pandas 3.0 geändert?

pandas 3.0.0 erschien am 21. Januar 2026, aktuell ist Version 3.0.6. Drei Änderungen aus den Release Notes zu pandas 3.0 betreffen Bereinigungscode direkt:

  • Strings haben einen eigenen Typ. Textspalten werden jetzt als str statt als object erkannt, und fehlende Werte darin sind NaN, wie bei den anderen Standardtypen. Deshalb wählt das Skript Textspalten mit select_dtypes(include=["object", "string"]) aus: Das funktioniert unter pandas 2 und 3.
  • Copy-on-Write ist der einzige Modus. Verkettete Zuweisungen wie df["price"][0] = 10 ändern df nicht mehr; pandas 3.0.6 gibt nur eine Warnung ChainedAssignmentError aus. Verwenden Sie df.loc[0, "price"] = 10. Die alte SettingWithCopyWarning ist verschwunden, und die vorsorglichen .copy()-Aufrufe, mit denen man sie zum Schweigen brachte, sind nicht mehr nötig.
  • Geparste Datumswerte haben standardmäßig Mikrosekunden. pd.to_datetime liefert für Strings jetzt datetime64[us] statt Nanosekunden, und genau das sehen Sie in der Ausgabe unten.

pandas 3 setzt außerdem Python 3.11 oder neuer voraus. pd.to_numeric akzeptiert nur errors="raise" oder errors="coerce" (Referenz zu to_numeric); ältere Code-Schnipsel mit errors="ignore" schlagen fehl.

Das vollständige Skript: books.toscrape.com scrapen und bereinigen

Installieren Sie die Bibliotheken in einer virtuellen Umgebung. pyarrow wird für die Parquet-Datei benötigt:

bash
python -m venv .venv
.venv/bin/pip install pandas requests beautifulsoup4 pyarrow   # Windows: .venv\Scripts\pip

Der Scraper liest zwei Katalogseiten und zwei Kategorieseiten (Poetry und Classics), insgesamt 78 Bücher, mit einer Pause von einer Sekunde zwischen den Anfragen und automatischen Wiederholungen bei 429- und 5xx-Antworten. Das HTML-Parsing folgt dem Ansatz aus unserem BeautifulSoup-Tutorial. Der zweite Lauf liest books_raw.csv und wiederholt nur die Bereinigung.

python
"""books.toscrape.com scrapen und das Ergebnis mit pandas bereinigen."""
import time
import unicodedata
from datetime import datetime, timezone
from pathlib import Path
from urllib.parse import urljoin

import pandas as pd
import requests
from bs4 import BeautifulSoup
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

BASE = "https://books.toscrape.com/"
START_URLS = [
    BASE + "catalogue/page-1.html",                             # Hauptkatalog
    BASE + "catalogue/category/books/poetry_23/index.html",     # eine Kategorie
    BASE + "catalogue/category/books/classics_6/index.html",    # noch eine
]
MAX_PAGES = 2          # Listenseiten pro Start-URL
DELAY = 1.0            # Sekunden zwischen Anfragen
RAW_FILE = Path("books_raw.csv")

session = requests.Session()
session.headers["User-Agent"] = "pandas-cleaning-tutorial/1.0 (contact: you@example.com)"
retry = Retry(total=4, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504])
session.mount("https://", HTTPAdapter(max_retries=retry))
# session.proxies = {"https": "http://user:pass@pr.proxynet.io:8000"}


def get(url):
    time.sleep(DELAY)
    resp = session.get(url, timeout=30)
    resp.raise_for_status()
    return resp.text   # absichtlich mit der Schätzung von Requests dekodiert: siehe Schritt 2


def parse_book(url):
    soup = BeautifulSoup(get(url), "html.parser")
    table = {th.get_text(): td.get_text() for th, td in
             zip(soup.select("table.table th"), soup.select("table.table td"))}
    desc = soup.select_one("#product_description + p")
    return {
        "url": url,
        "title": soup.h1.get_text(),
        "category": soup.select("ul.breadcrumb a")[-1].get_text(),
        "price": table.get("Price (incl. tax)"),
        "availability": table.get("Availability"),
        "rating": soup.select_one("p.star-rating")["class"][1],
        "upc": table.get("UPC"),
        "reviews": table.get("Number of reviews"),
        "description": desc.get_text() if desc else None,
        "scraped_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
    }


def scrape():
    rows = []
    for start in START_URLS:
        url = start
        for _ in range(MAX_PAGES):
            soup = BeautifulSoup(get(url), "html.parser")
            for a in soup.select("article.product_pod h3 a"):
                rows.append(parse_book(urljoin(url, a["href"])))
            nxt = soup.select_one("li.next a")
            if not nxt:
                break
            url = urljoin(url, nxt["href"])
    df = pd.DataFrame(rows)
    df.to_csv(RAW_FILE, index=False, encoding="utf-8")
    return df


def clean(raw):
    df = raw.copy()
    report = {"raw_rows": len(df)}

    # 1. Leerzeichen in jeder Textspalte entfernen; leere Strings werden zu fehlenden Werten
    text_cols = df.select_dtypes(include=["object", "string"]).columns
    for col in text_cols:
        df[col] = df[col].str.strip().replace("", pd.NA)

    # 2. Unicode: Mojibake reparieren, dann auf NFKC normalisieren
    def fix_text(s):
        if pd.isna(s):
            return s
        try:
            s = s.encode("latin-1").decode("utf-8")   # "£" -> "£", "é" -> "é"
        except (UnicodeEncodeError, UnicodeDecodeError):
            pass                                        # bereits korrekt
        return unicodedata.normalize("NFKC", s)

    for col in ["title", "category", "description", "price"]:
        df[col] = df[col].map(fix_text)

    # 3. Fehlende Werte: zählen, markieren, nicht erfinden
    report["missing_before"] = df.isna().sum()[lambda s: s > 0].to_dict()
    df["description"] = df["description"].str.removesuffix("...more").str.strip()
    df["has_description"] = df["description"].notna()

    # 4. Preistexte in Zahlen umwandeln
    df["currency"] = df["price"].str.extract(r"([£$€])", expand=False)
    df["price"] = pd.to_numeric(
        df["price"].str.replace(r"[^\d.]", "", regex=True), errors="coerce"
    )

    # 5. Weitere Zahlen, die im Text stecken
    df["in_stock"] = pd.to_numeric(
        df["availability"].str.extract(r"(\d+)\s+available", expand=False),
        errors="coerce",
    ).astype("Int64")
    df["rating"] = df["rating"].map(
        {"One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5}
    ).astype("Int64")
    df["reviews"] = pd.to_numeric(df["reviews"], errors="coerce").astype("Int64")

    # 6. Datumswerte
    df["scraped_at"] = pd.to_datetime(df["scraped_at"], utc=True, errors="coerce")

    # 7. Kategorien: eine Schreibweise pro Wert, Platzhalterlabels zusammengeführt
    df["category"] = (df["category"]
                      .replace({"Default": "Uncategorized",
                                "Add a comment": "Uncategorized"})
                      .astype("category"))

    # 8. Duplikate: gleiche UPC = gleiches Buch; die neueste Kopie behalten
    report["duplicate_rows"] = int(df.duplicated("upc").sum())
    df = (df.sort_values("scraped_at")
            .drop_duplicates("upc", keep="last")
            .reset_index(drop=True))

    # 9. Ausreißer: markieren, nicht löschen
    q1, q3 = df["price"].quantile([0.25, 0.75])
    iqr = q3 - q1
    df["price_outlier"] = ~df["price"].between(q1 - 1.5 * iqr, q3 + 1.5 * iqr)
    report["price_outliers"] = int(df["price_outlier"].sum())

    df = df.drop(columns=["availability"])
    report["clean_rows"] = len(df)
    return df, report


def validate(df):
    problems = []
    if df["upc"].duplicated().any():
        problems.append("duplicate UPCs")
    if df["price"].isna().any():
        problems.append("prices that did not parse")
    if not df["price"].dropna().between(0, 1000).all():
        problems.append("prices outside 0-1000")
    if not df["rating"].between(1, 5).fillna(False).all():
        problems.append("ratings missing or outside 1-5")
    if df["title"].isna().any():
        problems.append("rows without a title")
    if problems:
        raise ValueError("Validation failed: " + ", ".join(problems))


if __name__ == "__main__":
    raw = pd.read_csv(RAW_FILE) if RAW_FILE.exists() else scrape()
    clean_df, report = clean(raw)
    validate(clean_df)
    clean_df.to_csv("books_clean.csv", index=False, encoding="utf-8-sig")
    clean_df.to_parquet("books_clean.parquet", index=False)
    print(report)
    print(clean_df.dtypes)
    print(clean_df[["title", "category", "price", "rating", "in_stock"]].head())

Der erste Lauf dauerte etwa zwei Minuten, das meiste davon die höfliche Pause. Die ausgegebene Berichtszeile:

text
{'raw_rows': 78, 'missing_before': {'description': 1}, 'duplicate_rows': 5, 'price_outliers': 0, 'clean_rows': 73}

Fünf Poetry-Bücher standen sowohl auf den Katalogseiten als auch auf der Poetry-Seite, deshalb wurden aus 78 Zeilen 73. Ein Classics-Titel, Alice in Wonderland, hat auf seiner Seite keinen Beschreibungsblock. Sieben Bücher trugen die Kategorie „Default“. Die Preise reichten von 12,84 bis 58,63, daher markierte die Ausreißerregel nichts, und das ist für diese Website die richtige Antwort. Die Ausgabe von dtypes zeigt str für Text, float64 für den Preis, Int64 für Zählwerte, category für die Kategorie und datetime64[us, UTC] für den Zeitstempel.

Was jeder Schritt tut und warum

Text und Unicode

str.strip() entfernt die Leerzeichen und Zeilenumbrüche, die das HTML-Layout um Werte herum hinterlässt. Leere Strings "" in fehlende Werte umzuwandeln ist wichtiger, als es aussieht: isna() zählt leere Strings nicht mit, ohne diesen Schritt gilt ein leeres Feld also als gefüllt.

Die Kodierungsreparatur macht die falsche Schätzung rückgängig. s.encode("latin-1").decode("utf-8") nimmt die Zeichen, die Requests erzeugt hat, wandelt sie in die ursprünglichen Bytes zurück und dekodiert diese Bytes als UTF-8. Ein String, der bereits korrekt war, bleibt entweder unverändert (reines ASCII) oder löst in einem der beiden Aufrufe einen Fehler aus und wird dann in Ruhe gelassen. Die sauberere Lösung setzt an der Quelle an: Übergeben Sie response.content an BeautifulSoup oder setzen Sie response.encoding = "utf-8", bevor Sie response.text lesen. Die Reparatur lohnt sich trotzdem für bereits gespeicherte Dateien. Danach führt unicodedata.normalize("NFKC", s) Kompatibilitätszeichen wie Ligaturen und geschützte Leerzeichen auf ihre einfachen Formen zurück (Dokumentation zu unicodedata), sodass zwei Schreibweisen desselben Titels als gleich gelten.

Fehlende Werte

Das Skript zählt fehlende Werte pro Spalte und fügt eine Markierung has_description hinzu. Es schreibt nicht „No description“ in die Lücke, denn dieser String sähe später wie echter Text aus. Bei Zahlen gehört das Auffüllen mit Mittelwert oder Median in den Analyseschritt, wo die Analystin es im Diagramm kenntlich machen kann, nicht in die Bereinigung.

Zahlen, Bewertungen und Datumswerte

pd.to_numeric(..., errors="coerce") macht aus allem, was es nicht parsen kann, NaN, statt abzubrechen. Das ist der richtige Standard für einen Bereinigungsdurchgang, sofern eine Validierungsprüfung diese NaN-Werte anschließend zählt. Der nullbare Typ Int64 (mit großem I) hält ganze Zahlen ganzzahlig, auch wenn ein Wert fehlt; das einfache int64 kann kein NaN aufnehmen und würde stillschweigend zu float. Datumswerte werden mit utc=True geparst, damit Zeitstempel aus verschiedenen Läufen und von verschiedenen Rechnern korrekt vergleichbar sind.

Kategorien und Duplikate

astype("category") speichert jedes Label einmal und hält pro Zeile nur einen kleinen Code. Neben der Speicherersparnis macht das die Liste der Labels explizit: df["category"].cat.categories zeigt auf einen Blick, ob „Poetry“ und „poetry “ beide überlebt haben.

Wählen Sie für Duplikate einen Schlüssel, der das Objekt identifiziert, nicht die Zeile. Hier ist das die UPC, die auf jeder Buchseite steht. Die URL würde auf dieser Website auch funktionieren, doch in echten Shops hat dasselbe Produkt oft mehrere URLs (Tracking-Parameter, Kategoriepfade). Wenn Sie vor drop_duplicates(keep="last") nach scraped_at sortieren, bleibt die neueste Kopie erhalten, und genau das wollen Sie, wenn sich Preise zwischen Läufen ändern.

Ausreißer und Validierung

Die Regel des Interquartilsabstands (IQR) markiert Werte, die mehr als das 1,5-Fache des IQR unter dem ersten oder über dem dritten Quartil liegen. Sie ist leicht zu erklären und setzt keine Normalverteilung voraus. Das Skript fügt eine Spalte price_outlier hinzu, statt Zeilen zu löschen: Ein Preis von 0,99 in einem echten Shop kann ein echtes Sonderangebot sein oder ein Parsing-Fehler, der 0.99 aus 10.99 gelesen hat. Bevor etwas gelöscht wird, sollte ein Mensch draufschauen.

validate() ist die letzte Verteidigungslinie. Als wir einen Rohpreis auf N/A und eine Bewertung auf Six änderten, brach der Lauf mit Validation failed: prices that did not parse, ratings missing or outside 1-5 ab, statt eine fehlerhafte Datei zu schreiben. Orientieren Sie diese Prüfungen an den tatsächlichen Regeln Ihrer Daten; eine Prüfung, die nie fehlschlagen kann, schützt nichts.

Wo bereinigte Scraping-Daten eingesetzt werden

  • Preisüberwachung: Numerische Preise mit Zeitstempel sind das, was eine Preishistorie und ein Alarm brauchen. Siehe Preisüberwachung und Wettbewerberpreise verfolgen.
  • Marktforschung: Kategorieanteile, Verteilungen von Bewertungen und Lagerbestände über einen ganzen Katalog. Siehe Marktforschung.
  • Data Mining und Modellierung: Modelle brauchen typisierte, deduplizierte Eingaben. Hintergrund im Beitrag Was ist Data Mining?.
  • Pipelines: Dieses Skript ist der Transform-Schritt eines kleinen ETL-Jobs; das vollständige Muster steht in Was ist ETL?.
  • Speicherung: Sobald die Spalten typisiert sind, hilft Gescrapte Daten als CSV, JSON und in SQLite speichern bei der Wahl zwischen CSV, JSON und SQLite.
  • Datenerfassung im großen Maßstab: Größere Scraping-Projekte und die Infrastruktur dafür finden Sie auf unserer Seite zu Data Scraping.

Häufige Fehler

  • Die einzige Kopie bereinigen. Wer die Rohdatei überschreibt, muss bei jeder Regeländerung neu scrapen.
  • Zuerst deduplizieren. drop_duplicates vergleicht exakte Werte, daher überleben "Olio" und "Olio " beide, wenn Sie es vor dem Entfernen der Leerzeichen ausführen.
  • astype(float) auf Preistexte. Das scheitert am ersten £. Entfernen Sie nicht numerische Zeichen, verwenden Sie dann pd.to_numeric mit errors="coerce" und zählen Sie die entstehenden NaN-Werte.
  • Lücken mit null füllen. Ein fehlender Lagerbestand ist kein Bestand von null. Verwenden Sie Int64 und lassen Sie den Wert fehlen.
  • Verkettete Zuweisung. Unter pandas 3 ändert df[df.price > 50]["flag"] = True nichts und gibt nur eine Warnung aus. Verwenden Sie df.loc[df.price > 50, "flag"] = True.
  • Für Excel ohne BOM speichern. Excel unter Windows liest eine einfache UTF-8-CSV mit einer veralteten Codepage, und das £ geht wieder kaputt; encoding="utf-8-sig" verhindert das.
  • Härter scrapen statt besser bereinigen. Fehlen Daten, weil Anfragen fehlgeschlagen sind, beheben Sie das bei der Erfassung: Beachten Sie die robots.txt, drosseln Sie bei HTTP 429 das Tempo und nutzen Sie eine offizielle API, wo es eine gibt.

Entscheidungshilfe

BedarfEmpfehlung
Als Text gespeicherte ZahlenSymbole entfernen, pd.to_numeric(errors="coerce"), dann NaN-Werte zählen
Ganze Zahlen mit LückenNullbares Int64, nicht int64 oder float
Text mit £, ’ oder éDekodierung an der Quelle korrigieren; gespeicherte Dateien per latin-1-Rundreise reparieren
Derselbe Artikel von mehreren Seitendrop_duplicates auf einer stabilen ID, neueste Kopie behalten
Einige auffällige PreiseMit der IQR-Regel markieren und prüfen; nicht automatisch löschen
Wiederkehrende Labels wie Kategorienastype("category") und eine explizite Zuordnung für Platzhalter
Zeitstempel aus verschiedenen Läufenpd.to_datetime(..., utc=True)
Typen zwischen Schritten erhaltenParquet neben der CSV speichern
Große oder regelmäßige Scraping-LäufeRate Limits, Wiederholungen und, wo das Volumen es erfordert, Rotierender Proxy über pr.proxynet.io:8000

Häufige Fragen

Sollte ich Daten beim Scrapen oder danach bereinigen?

Erledigen Sie beim Scrapen nur das Nötigste (Text übernehmen, Leerzeichen entfernen) und den Rest danach in pandas. Solange Sie die Rohdatei behalten, können Sie die Bereinigungsregeln ändern und in Sekunden neu anwenden, ohne eine einzige neue Anfrage an die Website zu senden.

Warum zeigt meine Preisspalte £ statt £?

Der Server hat kein Charset angegeben, deshalb hat Requests UTF-8-Bytes als ISO-8859-1 dekodiert. Übergeben Sie response.content an Ihren Parser oder setzen Sie response.encoding = "utf-8". Bei bereits gespeicherten Daten macht s.encode("latin-1").decode("utf-8") den Fehler rückgängig.

Ist dropna() der richtige Umgang mit fehlenden Werten?

Nur wenn eine Zeile ohne dieses Feld für Ihre Fragestellung nutzlos ist. dropna() ohne Argumente verwirft eine Zeile, sobald irgendeine Spalte fehlt, und kann so wegen eines einzigen optionalen Felds den Großteil eines gescrapten Datensatzes löschen. Verwenden Sie dropna(subset=[...]) für die Spalten, die Sie wirklich brauchen, und markieren Sie den Rest.

Wie wähle ich die Spalte zum Entfernen von Duplikaten?

Verwenden Sie eine Kennung, die auf der Seite steht, etwa eine UPC, SKU, ISBN oder Produkt-ID. URLs funktionieren nur auf Websites, auf denen jeder Artikel genau eine URL hat. Gibt es keine ID, kombinieren Sie mehrere normalisierte Felder, zum Beispiel Titel und Marke.

Funktioniert dieser Code mit pandas 2?

Getestet haben wir ihn nur mit pandas 3.0.6, aber jeder verwendete Aufruf existiert auch in pandas 2.x, und die Auswahl der Textspalten deckt sowohl den alten Typ object als auch den neuen Typ str ab. Rechnen Sie mit kleinen Unterschieden in der Ausgabe, etwa object statt str und Zeitstempeln in Nanosekunden statt Mikrosekunden. Was sich zwischen beiden Versionen wirklich unterschiedlich verhält, ist Code, der auf verketteter Zuweisung beruht.

Brauche ich einen Proxy, um Daten zu bereinigen?

Nein. Die Bereinigung läuft auf Ihrem Rechner und erzeugt keinen Netzwerkverkehr. Ein Proxy spielt nur bei der Erfassung eine Rolle, und auch dann erst ab einem Volumen, bei dem eine einzelne IP-Adresse die Rate Limits einer Website überschreiten würde. Der Bereinigungscode bleibt gleich, egal über welches Netzwerk die Seiten kamen.

Fazit

Gescrapte Daten kommen als Text an, der für menschliche Leser geformt ist. Ein verlässlicher Bereinigungsdurchgang behält die Rohdatei, korrigiert zuerst Text und Kodierung, wandelt jede Spalte in einen echten Typ um, entfernt Duplikate anhand eines stabilen Schlüssels, markiert auffällige Werte, statt sie zu löschen, und hält bei einer fehlgeschlagenen Prüfung an, bevor gespeichert wird. Das Skript oben erledigt all das für 78 echte Zeilen in weniger als einer Sekunde, sobald die Seiten heruntergeladen sind. Wenn Ihre Datenerfassung über eine Übungsseite hinauswächst, decken die Tarife auf unserer Proxy-Seite die Netzwerkseite ab.

ChatGPT fragenClaude fragen