---
title: "Was ist ETL? Extrahieren, Transformieren, Laden mit Python"
description: "ETL heißt: Daten aus einer Quelle extrahieren, in eine feste Form bringen und in eine Datenbank laden. Ablauf, ETL vs. ELT und ein getesteter Python-Job."
url: https://proxynet.io/de/blog/what-is-etl
date: 2026-09-28
author: "Acar Diveroli"
category: "Web Scraping, Proxy 101"
lang: de
---

# Was ist ETL? Extrahieren, Transformieren, Laden mit Python

Ein Scraper, der letzten Monat noch problemlos lief, füllt jetzt eine Tabelle mit Preisen wie `£51.77`, demselben Buch in dreifacher Ausführung und einer Spalte, in der „In stock“ neben „In stock (19 available)“ steht. Daraus lässt sich kein Diagramm erstellen. Der Scraper hat seine Aufgabe erfüllt; was fehlt, ist alles danach: rohen Seitentext in typisierte, deduplizierte Zeilen umzuwandeln und sie an einem Ort abzulegen, der den nächsten Lauf übersteht. Dieser fehlende Teil hat einen Namen, und er ist älter als Web Scraping: ETL.

In diesem Beitrag erklären wir, was ETL bedeutet, was jeder der drei Schritte leistet und wie eine ETL-Pipeline vom Auslöser bis zur fertigen Tabelle abläuft. Wir vergleichen ETL mit ELT, betrachten die zwei Eigenschaften, die ein Skript von einer Pipeline unterscheiden (sichere Wiederholbarkeit und Monitoring), und bauen einen vollständigen, getesteten Python-Job, der Buchdaten aus einer Scraping-Sandbox extrahiert, bereinigt und in SQLite lädt. Die letzten Abschnitte behandeln Anwendungsfälle, häufige Fehler und eine kurze Entscheidungshilfe.

> **Hinweis: Kurzantwort**
>
> ETL steht für **Extract, Transform, Load**, also **Extrahieren, Transformieren, Laden**. Beim Extrahieren werden Rohdaten aus einer Quelle wie einer Website, einer API oder einer Datei geholt. Beim Transformieren erhalten sie eine feste Form: korrekte Datentypen, keine Duplikate, und Zeilen, die die Validierung nicht bestehen, werden aussortiert. Beim Laden werden die sauberen Zeilen in ein Ziel wie SQLite, PostgreSQL oder ein Data Warehouse geschrieben. Bei ELT ändert sich die Reihenfolge: Die Rohdaten werden zuerst geladen und im Ziel transformiert. Ein guter ETL-Job lässt sich zweimal ausführen, ohne Duplikate zu erzeugen, und meldet sich, wenn er fehlschlägt.

## Was ist ETL?

ETL ist ein Prozess der Datenintegration: Daten kommen aus einer oder mehreren Quellen, werden nach von Ihnen festgelegten Regeln umgeformt und landen in einem einzigen Speicher, in dem sie abgefragt werden können. Der Architekturleitfaden von Microsoft beschreibt ETL als Prozess, der [Daten aus unterschiedlichen Quellen in einem einheitlichen Datenspeicher zusammenführt](https://learn.microsoft.com/de-de/azure/architecture/data-guide/relational-data/etl), wobei die Transformation vor dem Laden nach Geschäftsregeln erfolgt.

Die Idee stammt aus dem Data Warehousing und passt ebenso gut zum Scraping. Eine Website ist eine Quelle wie jede andere, nur unordentlicher: Ihre Daten sind für Menschen formatiert, auf viele Seiten verteilt und können sich ohne Vorwarnung ändern. Wenn Sie schon einmal einen Scraper geschrieben haben und danach ein zweites Skript, um dessen Ausgabe zu korrigieren, haben Sie bereits zwei Drittel einer ETL-Pipeline gebaut.

## Was leistet jeder Schritt?

**Extrahieren** sammelt die Rohdaten und verändert sie so wenig wie möglich. Bei Webdaten bedeutet das: Anfragen senden, der Paginierung folgen und die benötigten Felder aus dem HTML ziehen. Das Ergebnis ist immer noch Text: `"£51.77"`, `"Three"`, `"\n In stock\n"`. Ein bewusst einfacher Extraktionsschritt hat einen Vorteil: Wenn etwas kaputtgeht, erkennen Sie, ob sich die Quelle geändert hat oder Ihre Bereinigungsregeln.

**Transformieren** ist der Schritt, in dem die Regeln stecken. Typische Operationen sind:

- Datentypen umwandeln (Preistext in eine Zahl, ein Bewertungswort in eine Ganzzahl, einen Datums-String in ein Datum)
- Text normalisieren (Leerzeichen, Unicode-Formen, Groß- und Kleinschreibung)
- anhand eines stabilen Schlüssels wie der Produkt-URL deduplizieren
- validieren (ein Preis muss positiv sein, eine Bewertung zwischen 1 und 5 liegen) und fehlerhafte Zeilen aussortieren
- anreichern oder verknüpfen (Währung, Kategorie, Zeitstempel der Erfassung hinzufügen)

Das HTML einer Seite in Felder zu zerlegen, ist selbst ein Parsing-Schritt; die Parser-Typen und ihre Schwachstellen erklären wir in [Was ist Data Parsing?](/de/blog/what-is-data-parsing). Eine vollständige Bereinigung mit pandas zeigt [Gescrapte Daten mit pandas bereinigen](/de/blog/clean-scraped-data-with-pandas).

**Laden** schreibt die sauberen Zeilen ins Ziel und macht das Ergebnis in einem Stück sichtbar. Für ein kleines Projekt ist das eine SQLite-Datei, für ein Team PostgreSQL oder ein Cloud Data Warehouse. Beim Laden entstehen auch die meisten Fehler mit doppelten Zeilen, weshalb die Methode wichtiger ist als das Ziel. Die Vor- und Nachteile von CSV, JSON und SQLite behandeln wir in [Gescrapte Daten als CSV, JSON und in SQLite speichern](/de/blog/save-scraped-data-csv-json-sqlite).

## Wie läuft eine ETL-Pipeline ab?

Ein einzelner Lauf eines geplanten Scraping-ETL-Jobs durchläuft diese Schritte:

1. **Ein Auslöser startet den Lauf.** Ein Cron-Eintrag, ein Orchestrator wie Apache Airflow oder eine Person, die das Skript ausführt.
2. **Der Extraktionsschritt ruft die Quelle ab.** Seiten werden in maßvollem Tempo angefragt, fehlgeschlagene Anfragen nach einer Pause wiederholt und die Rohfelder gesammelt.
3. **Der Transformationsschritt bereinigt jeden Datensatz.** Datentypen werden umgewandelt, Duplikate entfernt, und Datensätze, die gegen eine Regel verstoßen, werden gezählt und protokolliert, statt stillschweigend durchzurutschen.
4. **Eine Qualitätsprüfung entscheidet.** Hat die Extraktion nichts geliefert oder wurden zu viele Zeilen abgelehnt, bricht der Lauf ab, bevor er das Ziel berührt. Eine Layoutänderung auf der Website darf die guten Daten von gestern nicht überschreiben.
5. **Das Laden erfolgt in einer Transaktion.** Die Zeilen werden per Upsert anhand ihres Schlüssels geschrieben; entweder wird der ganze Stapel übernommen oder gar nichts.
6. **Der Lauf erstattet Bericht.** Die Zahl der extrahierten, sauberen, abgelehnten und geladenen Zeilen geht in ein Log oder eine Warnung, sodass ein stiller Fehler sichtbar wird.

Bei großen Jobs können sich die Phasen überlappen, wobei die Transformation bereits auf den schon eingetroffenen Daten beginnt. Bei einigen Tausend Zeilen ist die Ausführung nacheinander leichter zu debuggen.

## ETL vs. ELT: Was ist der Unterschied?

ELT (Extract, Load, Transform) behält dieselben drei Schritte bei, verlagert die Transformation aber ins Ziel. Die Rohdaten werden unverändert geladen und später per SQL oder von der Engine des Data Warehouse umgeformt. Der Leitfaden von Microsoft formuliert den Unterschied klar: Bei ELT findet die Transformation im Zieldatenspeicher statt.

| | ETL | ELT |
|---|---|---|
| Reihenfolge | Extrahieren, transformieren, dann laden | Extrahieren, laden, dann transformieren |
| Wo die Bereinigung läuft | In Ihrem Skript oder einer separaten Engine | In der Datenbank oder im Data Warehouse (SQL) |
| Was das Ziel enthält | Nur saubere, validierte Zeilen | Rohe Zeilen plus bereinigte Tabellen oder Views |
| Bleiben Rohdaten erhalten? | Nur wenn Sie sie separat speichern | Ja, konzeptbedingt |
| Anforderungen an das Ziel | Jeder Speicher, sogar eine SQLite-Datei | Ein Ziel, das auch große Mengen transformieren kann |
| Einen Bereinigungsfehler beheben | Neu extrahieren oder aus gespeicherten Rohdateien neu ausführen | SQL umschreiben und aus den Rohtabellen neu aufbauen |
| Gut geeignet für | Kleine bis mittlere Scraping-Jobs, strikte Schemas | Große Datenmengen, explorative Analysen, wechselnde Regeln |

Für Scraping gibt es einen nützlichen Mittelweg: Speichern Sie das rohe HTML oder rohe JSON jedes Laufs auf der Festplatte (in einem Staging-Bereich) und transformieren Sie aus diesen Dateien. Stellt sich eine Bereinigungsregel als falsch heraus, bauen Sie die Tabelle neu auf, ohne eine einzige neue Anfrage an die Website zu senden.

## Warum muss ein ETL-Job zweimal laufen können?

Jobs brechen mittendrin ab. Die Verbindung reißt auf Seite 38 ab, der Rechner startet neu, ein Scheduler versucht es erneut. Entscheidend ist, wie das Ziel nach dem zweiten Versuch aussieht. Der Best-Practices-Leitfaden von Apache Airflow beantwortet das direkt: Behandeln Sie eine Task wie eine [Transaktion in einer Datenbank](https://airflow.apache.org/docs/apache-airflow/stable/best-practices.html), erzeugen Sie nie Teilergebnisse und ersetzen Sie ein einfaches `INSERT` durch einen Upsert, weil ein erneuter Lauf mit `INSERT` doppelte Zeilen hinterlassen kann.

Diese Eigenschaft heißt **Idempotenz**: Ob der Job einmal oder dreimal mit denselben Eingaben läuft, das Ergebnis bleibt gleich. In der Praxis entsteht sie aus drei Gewohnheiten:

- **Ein natürlicher Schlüssel.** Jede Zeile hat eine Kennung, die zwischen den Läufen gleich bleibt. Bei einer Produktseite eignet sich die URL, eine automatisch hochgezählte ID dagegen nicht.
- **Upsert statt Insert.** Upsert ist ein Kunstwort aus „Update“ und „Insert“: Fehlt die Zeile, wird sie eingefügt, sonst aktualisiert. SQLite unterstützt `INSERT … ON CONFLICT DO UPDATE` seit Version 3.24.0, und der [spezielle Qualifizierer `excluded.`](https://sqlite.org/lang_upsert.html) verweist auf die Werte, die eingefügt worden wären. PostgreSQL verwendet dieselbe Syntax.
- **Eine Transaktion pro Ladevorgang.** Der Stapel wird als Ganzes übernommen oder zurückgerollt, sodass ein Absturz nie eine halbe Tabelle hinterlässt.

## Einen ETL-Job planen und überwachen

Ein Job, der nur läuft, wenn Sie daran denken, ist ein Skript. Für einen Rechner und einen Job genügt Cron (oder die Aufgabenplanung unter Windows). Hängen Jobs voneinander ab (erst scrapen, dann bereinigen, dann einen Bericht erstellen), lohnt sich ein Orchestrator. In Airflow ist eine Pipeline ein DAG, eine Menge von Tasks mit Abhängigkeiten, und das [Argument `schedule`](https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/dags.html) akzeptiert Voreinstellungen wie `@daily`, einen Cron-String oder ein Zeitintervall. Airflow wiederholt außerdem fehlgeschlagene Tasks, ein weiterer Grund, warum Tasks idempotent sein müssen.

Für das Monitoring brauchen Sie am ersten Tag keine Plattform. Protokollieren Sie bei jedem Lauf vier Zahlen (abgerufene Seiten, extrahierte Zeilen, abgelehnte Zeilen, geladene Zeilen) und lösen Sie in zwei Fällen eine Warnung aus: Der Job ist nicht gelaufen, oder eine dieser Zahlen weicht stark von ihrem üblichen Wert ab. Ein Rückgang von 1.000 Zeilen auf 20 bedeutet meist, dass die Website ihr Layout geändert hat, und nicht, dass der Shop ausverkauft ist. Wie Sie auf der Scraping-Seite erkennen, dass sich eine Seite selbst geändert hat, zeigt [Website auf Änderungen überwachen und benachrichtigt werden](/de/blog/website-change-monitoring).

## Wo passen Proxys in ETL?

Nur in den Extraktionsschritt, und nur wenn der Job sie braucht. Wer täglich ein paar Seiten einer öffentlichen Website abruft, braucht sie selten. Wichtig werden sie, wenn Sie Preise erfassen, die sich je nach Land unterscheiden, wenn ein legitimer Job mit vielen Anfragen an die Rate Limits pro IP stößt, die eine Website anwendet, oder wenn die Quelle standortabhängige Inhalte zeigt. Mit einem [Residential-Proxy](https://proxynet.io/de/residential-proxy) ruft der Extraktionsschritt Seiten aus einem gewählten Land und einer gewählten Stadt ab; ein [Rotierender Proxy](https://proxynet.io/de/rotating-proxy) verteilt die Anfragen bei größeren Crawls auf viele IPs.

Ein Proxy ändert nichts an den Regeln. Die robots.txt der Website, ihre Nutzungsbedingungen und eine vernünftige Anfragerate gelten weiterhin, und eine offizielle API oder ein Export hat immer Vorrang, wenn es eine solche Möglichkeit gibt. Die Anfrageseite behandeln wir ausführlicher in [Web Scraping ohne Sperren: Daten regelkonform erfassen](/de/blog/web-scraping-without-getting-blocked) und auf unserer Seite zum [Data Scraping](/de/data-scraping).

## Ein vollständiger ETL-Job in Python

Das folgende Skript ist eine komplette Pipeline in einer Datei. Es extrahiert jede Buchkarte von [books.toscrape.com](https://books.toscrape.com/), einer Sandbox zum Üben von Scraping, transformiert die Felder in typisierte Zeilen und lädt sie per Upsert in SQLite. Bei `429`- und `5xx`-Antworten wiederholt es die Anfrage mit wachsenden Pausen, wartet zwischen den Seiten eine Sekunde, bricht ab, wenn zu viele Zeilen die Validierung nicht bestehen, und leitet den Verkehr nur dann über einen Proxy, wenn Sie `PROXY_URL` setzen.

Installieren Sie zuerst die beiden Abhängigkeiten:

```bash
pip install requests beautifulsoup4
```

Speichern Sie dann Folgendes als `etl_books.py`:

```python
"""Ein kleiner ETL-Job: books.toscrape.com -> saubere Zeilen -> SQLite."""
import argparse
import logging
import os
import sqlite3
import time
import unicodedata
from datetime import datetime, timezone
from urllib.parse import urljoin

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

BASE = "https://books.toscrape.com/catalogue/"
RATINGS = {"One": 1, "Two": 2, "Three": 3, "Four": 4, "Five": 5}
log = logging.getLogger("etl")

def make_session():
    retry = Retry(total=4, backoff_factor=1.5,
                  status_forcelist=(429, 500, 502, 503, 504),
                  respect_retry_after_header=True)
    s = requests.Session()
    s.mount("https://", HTTPAdapter(max_retries=retry))
    s.headers["User-Agent"] = "etl-demo/1.0 (contact: you@example.com)"
    proxy = os.getenv("PROXY_URL")  # z. B. http://user:pass@pr.proxynet.io:8000
    if proxy:
        s.proxies = {"http": proxy, "https": proxy}
    return s

# ---------- EXTRACT: Seiten abrufen, Roh-Strings behalten ----------
def extract(session, max_pages, delay=1.0):
    raw, url, page = [], urljoin(BASE, "page-1.html"), 0
    while url and page < max_pages:
        resp = session.get(url, timeout=15)
        resp.raise_for_status()
        soup = BeautifulSoup(resp.content, "html.parser")
        for card in soup.select("article.product_pod"):
            raw.append({
                "title": card.h3.a["title"],
                "href": card.h3.a["href"],
                "price": card.select_one("p.price_color").get_text(),
                "rating": card.select_one("p.star-rating")["class"][-1],
                "stock": card.select_one("p.availability").get_text(),
                "page_url": url,
            })
        page += 1
        nxt = soup.select_one("li.next a")
        url = urljoin(url, nxt["href"]) if nxt else None
        time.sleep(delay)
    log.info("extract: %d pages, %d raw rows", page, len(raw))
    return raw

# ---------- TRANSFORM: Datentypen, Bereinigung, Deduplizierung, Validierung ----------
def transform(raw):
    clean, rejected, seen = [], 0, set()
    now = datetime.now(timezone.utc).isoformat(timespec="seconds")
    for r in raw:
        try:
            url = urljoin(r["page_url"], r["href"])
            if url in seen:
                continue
            seen.add(url)
            title = " ".join(unicodedata.normalize("NFKC", r["title"]).split())
            price = float(r["price"].strip().lstrip("Â£"))
            rating = RATINGS[r["rating"]]
            in_stock = "in stock" in r["stock"].lower()
            if not title or price <= 0:
                raise ValueError("empty title or bad price")
        except (KeyError, ValueError) as exc:
            rejected += 1
            log.warning("rejected %r: %s", r.get("title"), exc)
            continue
        clean.append((url, title, price, rating, int(in_stock), now))
    log.info("transform: %d clean, %d rejected", len(clean), rejected)
    return clean, rejected

# ---------- LOAD: Upsert in SQLite in einer Transaktion ----------
def load(rows, db_path):
    con = sqlite3.connect(db_path)
    with con:  # Commit bei Erfolg, Rollback bei Fehler
        con.execute("""CREATE TABLE IF NOT EXISTS books (
            url TEXT PRIMARY KEY, title TEXT NOT NULL, price_gbp REAL NOT NULL,
            rating INTEGER, in_stock INTEGER, updated_at TEXT)""")
        con.executemany("""INSERT INTO books VALUES (?, ?, ?, ?, ?, ?)
            ON CONFLICT(url) DO UPDATE SET title=excluded.title,
              price_gbp=excluded.price_gbp, rating=excluded.rating,
              in_stock=excluded.in_stock, updated_at=excluded.updated_at""", rows)
        total = con.execute("SELECT COUNT(*) FROM books").fetchone()[0]
    con.close()
    log.info("load: %d rows upserted, %d rows in table", len(rows), total)
    return total

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--pages", type=int, default=3)
    ap.add_argument("--db", default="books.db")
    args = ap.parse_args()
    logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
    raw = extract(make_session(), args.pages)
    rows, rejected = transform(raw)
    if not rows or rejected > len(raw) * 0.1:  # Schutz: keinen fehlerhaften Stapel laden
        raise SystemExit(f"aborting load: {len(rows)} clean, {rejected} rejected")
    load(rows, args.db)

if __name__ == "__main__":
    main()
```

Führen Sie es mit `python etl_books.py --pages 60` für den gesamten Katalog aus. Die Website hat 50 Seiten, daher endet die Schleife von selbst, sobald der Link „next“ verschwindet. Unser Lauf gab Folgendes aus:

```text
INFO extract: 50 pages, 1000 raw rows
INFO transform: 1000 clean, 0 rejected
INFO load: 1000 rows upserted, 1000 rows in table
```

Führen Sie es ein zweites Mal aus, endet die letzte Zeile weiterhin mit `1000 rows in table`. Der Upsert hat die vorhandenen Zeilen aktualisiert, statt Kopien hinzuzufügen: genau die oben beschriebene Idempotenz. Wir haben auch die Fehlerpfade getestet. Bei einem Verbindungs-Timeout liefen die Wiederholungen, und das Skript beendete sich mit einem Fehler vor dem Ladeschritt, sodass die Tabelle ihre bisherigen Zeilen behielt. Als wir `transform()` einen Preis `£x` und eine Bewertung `Six` übergaben, wurden beide Zeilen mit protokolliertem Grund abgelehnt, und eine wiederholte URL wurde als Duplikat verworfen.

Um den Extraktionsschritt über einen Proxy zu leiten, setzen Sie die Variable vor dem Start; sonst ändert sich nichts:

```bash
export PROXY_URL="http://user:pass@pr.proxynet.io:8000"
python etl_books.py --pages 60
```

Um den Job unter Linux jede Nacht um 3:00 Uhr auszuführen, fügen Sie eine Crontab-Zeile wie `0 3 * * * cd /opt/etl && .venv/bin/python etl_books.py --pages 60 >> etl.log 2>&1` hinzu. Sobald Sie mehrere voneinander abhängige Jobs haben, verlagern Sie sie in einen Orchestrator.

## Wo ETL mit Webdaten eingesetzt wird

- **Preisüberwachung:** Ein nächtlicher Job extrahiert Preise von Wettbewerbern, normalisiert Währungen und lädt sie in eine Verlaufstabelle; siehe unsere Seite zur [Preisüberwachung](/de/price-monitoring) und [Wettbewerberpreise im E-Commerce überwachen](/de/blog/competitor-price-tracking).
- **Marktforschung:** Produktzahlen, Bewertungen und Sortimentsänderungen vieler Shops, geladen in ein gemeinsames Schema; siehe [Marktforschung](/de/market-research).
- **Große Crawls:** Zuerst werden Tausende URLs entdeckt, dann wird aus jeder extrahiert; unsere Seite zum [Web-Crawler](/de/web-crawler) behandelt die Erkennungsseite.
- **Alternative Daten:** Analysten kombinieren Webdaten mit anderen Quellen, und die Qualitätsregeln im Transformationsschritt entscheiden, ob dem Ergebnis zu trauen ist; siehe [Was sind alternative Daten?](/de/blog/what-is-alternative-data).
- **Analyse und Mining:** Eine bereinigte, geladene Tabelle ist die Eingabe, die [Data Mining](/de/blog/what-is-data-mining) braucht.

## Häufige ETL-Fehler

- **Bereinigen im Extraktionsschritt.** Wenn Parsing und Umwandlung in derselben Schleife stattfinden, können Sie eine Änderung der Website nicht von einem Fehler in Ihren Regeln unterscheiden. Halten Sie die Extraktion roh.
- **Einfaches `INSERT` bei jedem Lauf.** Der zweite Lauf verdoppelt die Tabelle. Verwenden Sie einen natürlichen Schlüssel und einen Upsert.
- **Keine Qualitätsprüfung.** Eine Layoutänderung macht jeden Preis zu `None`, und der Job lädt anstandslos 1.000 leere Zeilen über gute Daten.
- **Commit Zeile für Zeile.** Ein Absturz mittendrin hinterlässt eine Tabelle, die weder dem alten noch dem neuen Stand entspricht.
- **Stille Ablehnungen.** Wer fehlerhafte Zeilen verwirft, ohne sie zu zählen, verbirgt Probleme, bis jemand merkt, dass die Zahlen dünn aussehen.
- **Falsche Kodierung.** Bytes mit dem falschen Zeichensatz zu lesen, macht aus `£` ein `Â£`; die Ursache erklärt unser Beitrag [Encoding-Fehler in Python](/de/blog/python-unicode-encoding-errors).
- **Die Grenzen der Quelle ignorieren.** Keine Verzögerung, keine Pause zwischen Wiederholungen, kein Respekt vor `Retry-After`. Die Website antwortet dann mit `429`; siehe [429 Too Many Requests: Was ist ein Rate-Limit-Fehler?](/de/blog/http-429-too-many-requests).

## Entscheidungshilfe

| Ihre Situation | Was Sie einsetzen sollten |
|---|---|
| Eine Quelle, einige Tausend Zeilen, eine Person | Ein einzelnes Python-ETL-Skript mit SQLite, gestartet per Cron |
| Regeln ändern sich oft, Rohdaten sollen erhalten bleiben | ELT: Rohzeilen oder -dateien laden, mit SQL transformieren |
| Mehrere Jobs, die voneinander abhängen | Ein Orchestrator wie Apache Airflow |
| Große Datenmengen, ein Cloud Data Warehouse ist vorhanden | ELT im Data Warehouse |
| Preise oder Inhalte, die sich je nach Land unterscheiden | ETL mit einem Residential Proxy im Extraktionsschritt |
| Die Quelle bietet eine API oder einen Download | Aus der API extrahieren; auf Scraping ganz verzichten |

## Häufige Fragen

### Wofür steht ETL?

Für Extract, Transform, Load, also Extrahieren, Transformieren, Laden. Daten werden aus einer Quelle extrahiert, in eine einheitliche, validierte Form transformiert und in einen Zielspeicher wie eine Datenbank oder ein Data Warehouse geladen.

### Ist Web Scraping dasselbe wie ETL?

Nein. Scraping ist eine Möglichkeit, den Extraktionsschritt umzusetzen. ETL umfasst auch alles danach: die Daten bereinigen, deduplizieren, validieren und an einen Ort laden, an dem sie abgefragt und aktuell gehalten werden können.

### Wird ETL noch verwendet, oder hat ELT es abgelöst?

Beides wird verwendet. ELT ist verbreitet, wenn ein Cloud Data Warehouse die aufwendige Transformation übernehmen kann und Rohdaten erhalten bleiben sollen. ETL bleibt die naheliegende Wahl, wenn das Ziel klein ist, das Schema strikt ist oder Daten vor dem Speichern bereinigt werden müssen.

### Kann ich eine ETL-Pipeline nur mit Python bauen?

Ja. Für kleine und mittlere Jobs reichen `requests`, ein HTML-Parser und das Modul `sqlite3` der Standardbibliothek, wie das Skript oben zeigt. Bibliotheken wie pandas helfen, wenn der Transformationsschritt aufwendiger wird.

### Was ist eine ETL-Pipeline?

Der ETL-Prozess, eingerichtet für die wiederholte Ausführung: ein Auslöser, die drei Schritte, eine Qualitätsprüfung und ein Bericht, meist nach Zeitplan. Aus einem einmaligen Skript wird eine Pipeline, sobald es unbeaufsichtigt laufen und sich von Fehlern erholen kann.

### Brauche ich Airflow für ETL?

Nicht für einen Job auf einem Rechner; dafür reicht Cron. Ein Orchestrator hilft, wenn mehrere Tasks voneinander abhängen, Wiederholungen und einen Laufverlauf brauchen oder von einem Team gemeinsam genutzt werden.

## Fazit

ETL ist der Teil eines Datenprojekts, der nach der Erfassung kommt: Rohdaten extrahieren, sie in typisierte, deduplizierte und validierte Zeilen transformieren und sie in einer Transaktion per Upsert laden, damit ein erneuter Lauf die Tabelle nie verdoppelt. ELT verlagert den Transformationsschritt ins Ziel und passt zu großen Data Warehouses; für die meisten Scraping-Jobs ist ein kleines ETL-Skript mit SQLite der richtige Einstieg. Ergänzen Sie eine Qualitätsprüfung und vier protokollierte Zahlen, planen Sie den Job und setzen Sie einen Proxy nur ein, wenn der Extraktionsschritt einen anderen Standort oder mehr IPs braucht. In diesem Fall finden Sie passende Optionen in unseren [Proxy-Tarifen](/de/proxy).
