Zum Inhalt springen
Python Fortgeschritten 5 min

Projekt: Web Scraper mit Python

Lerne Daten von Webseiten zu extrahieren mit requests und BeautifulSoup - inklusive ethischer Richtlinien und praktischer Beispiele

Aktualisiert:

Projekt: Web Scraper mit Python

In diesem Projekt lernst du, wie man mit Python automatisiert Daten von Webseiten extrahiert. Web Scraping ist eine der gefragtesten Python-Fähigkeiten und wird in Datenanalyse, Forschung und Automatisierung eingesetzt.

Projektübersicht und Ziele

Unser Web Scraper wird folgende Funktionen bieten:

  • HTTP-Anfragen senden: Webseiten herunterladen mit requests
  • HTML parsen: Inhalte analysieren mit BeautifulSoup
  • Daten extrahieren: Gezielte Informationen aus HTML-Elementen lesen
  • Daten speichern: Ergebnisse als CSV und JSON exportieren
  • Mehrere Seiten scrapen: Automatisch durch Seitennavigation blättern
  • Fehlerbehandlung: Robuster Code für den Umgang mit Netzwerkproblemen
  • OOP-Struktur: Wiederverwendbarer, sauberer Code

Was ist Web Scraping?

Web Scraping bedeutet, automatisiert Daten von Webseiten zu extrahieren. Statt Informationen manuell zu kopieren, schreibst du ein Programm, das das für dich erledigt.

Typische Anwendungsfälle:

  • Preisvergleiche (Produkte auf verschiedenen Shops überwachen)
  • Nachrichtensammlung (Schlagzeilen von mehreren Quellen)
  • Datenanalyse (Statistiken von Sportseiten, Wetterdaten)
  • Forschung (wissenschaftliche Daten sammeln)
  • Job-Suche (Stellenangebote aggregieren)

So funktioniert es im Überblick:

  1. Dein Programm sendet eine HTTP-Anfrage an eine Webseite
  2. Der Server antwortet mit dem HTML-Code der Seite
  3. Dein Programm analysiert das HTML und extrahiert die gewünschten Daten
  4. Die Daten werden in einem strukturierten Format gespeichert

Rechtliche Hinweise

Bevor du mit Web Scraping beginnst, musst du einige wichtige rechtliche und ethische Aspekte beachten:

robots.txt prüfen: Jede Webseite kann eine robots.txt-Datei haben (z.B. https://example.com/robots.txt), die festlegt, welche Bereiche von automatisierten Programmen besucht werden dürfen.

# Beispiel robots.txt
User-agent: *
Disallow: /private/
Disallow: /admin/
Allow: /public/

Grundregeln:

  • Lies immer die Nutzungsbedingungen der Webseite
  • Respektiere die robots.txt
  • Sende nicht zu viele Anfragen in kurzer Zeit (Rate Limiting)
  • Scrape keine persönlichen Daten ohne Erlaubnis
  • Verwende die Daten nicht kommerziell ohne Genehmigung
  • Wenn eine API verfügbar ist, nutze sie statt Web Scraping

Für dieses Tutorial verwenden wir die Übungsseite http://quotes.toscrape.com, die speziell zum Üben von Web Scraping erstellt wurde.


Schritt 1: requests installieren und nutzen

Die requests-Bibliothek macht HTTP-Anfragen in Python einfach. Installiere sie zuerst:

pip install requests

Dann können wir eine Webseite herunterladen:

import requests

# Eine Webseite abrufen
url = "http://quotes.toscrape.com"
antwort = requests.get(url)

# Status prüfen
print(f"Status-Code: {antwort.status_code}")  # 200 = Erfolg
print(f"Encoding: {antwort.encoding}")

# Die ersten 500 Zeichen des HTML-Codes anzeigen
print(antwort.text[:500])

Wichtige Status-Codes:

CodeBedeutung
200Erfolg
301Seite verschoben (Weiterleitung)
403Zugriff verweigert
404Seite nicht gefunden
429Zu viele Anfragen
500Server-Fehler

Anfrage mit zusätzlichen Optionen:

# User-Agent setzen (identifiziert sich als Browser)
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36"
}

antwort = requests.get(url, headers=headers, timeout=10)

Der User-Agent-Header teilt dem Server mit, wer die Anfrage stellt. Ein Timeout verhindert, dass dein Programm ewig auf eine Antwort wartet.


Schritt 2: BeautifulSoup installieren

BeautifulSoup ist die beliebteste Bibliothek zum Parsen von HTML in Python:

pip install beautifulsoup4

Grundlegende Verwendung:

from bs4 import BeautifulSoup

# HTML-Code parsen
html = """
<html>
<head><title>Meine Seite</title></head>
<body>
    <h1 class="titel">Willkommen</h1>
    <p id="beschreibung">Dies ist ein Beispiel.</p>
    <ul>
        <li>Punkt 1</li>
        <li>Punkt 2</li>
        <li>Punkt 3</li>
    </ul>
</body>
</html>
"""

soup = BeautifulSoup(html, "html.parser")

# Verschiedene Elemente finden
print(soup.title.text)           # "Meine Seite"
print(soup.h1.text)              # "Willkommen"
print(soup.h1["class"])          # ["titel"]
print(soup.p.text)               # "Dies ist ein Beispiel."
print(soup.find("p", id="beschreibung").text)

BeautifulSoup erstellt aus dem HTML-Text einen Baum, den wir nach Elementen durchsuchen können.


Schritt 3: HTML-Struktur verstehen

Um Daten zu scrapen, musst du die HTML-Struktur der Zielseite verstehen. Öffne http://quotes.toscrape.com im Browser und nutze die Entwicklertools (F12), um die Struktur zu analysieren.

Typische HTML-Elemente:

<!-- Tags -->
<div class="quote">
    <span class="text">"Der Weg ist das Ziel."</span>
    <small class="author">Konfuzius</small>
    <div class="tags">
        <a class="tag" href="/tag/weisheit">weisheit</a>
        <a class="tag" href="/tag/leben">leben</a>
    </div>
</div>

Wichtige Selektoren:

  • Tag-Name: div, span, a, p, h1
  • Klasse (class): Elemente mit class="quote" finden
  • ID: Einzigartige Elemente mit id="haupttitel" finden
  • Attribute: Andere Attribute wie href, src, data-*
# Elemente finden mit BeautifulSoup
soup.find("div", class_="quote")          # Erstes Element mit Klasse
soup.find_all("div", class_="quote")      # Alle Elemente mit Klasse
soup.find("span", {"class": "text"})      # Alternative Syntax
soup.select("div.quote span.text")        # CSS-Selektor
soup.select_one("#haupttitel")            # Element mit ID

Schritt 4: Daten extrahieren

Jetzt scrapen wir echte Daten von der Übungsseite:

import requests
from bs4 import BeautifulSoup

def zitate_scrapen(url):
    """Scrapet Zitate von einer Seite."""
    antwort = requests.get(url)
    antwort.raise_for_status()  # Wirft Fehler bei HTTP-Fehlern

    soup = BeautifulSoup(antwort.text, "html.parser")

    zitate = []
    quote_elemente = soup.find_all("div", class_="quote")

    for element in quote_elemente:
        # Text des Zitats
        text = element.find("span", class_="text").text

        # Autor
        autor = element.find("small", class_="author").text

        # Tags
        tag_elemente = element.find_all("a", class_="tag")
        tags = [tag.text for tag in tag_elemente]

        zitat = {
            "text": text,
            "autor": autor,
            "tags": tags
        }
        zitate.append(zitat)

    return zitate


# Zitate abrufen und anzeigen
url = "http://quotes.toscrape.com"
zitate = zitate_scrapen(url)

print(f"\n{len(zitate)} Zitate gefunden:\n")
for i, zitat in enumerate(zitate, 1):
    print(f"{i}. {zitat['text']}")
    print(f"   -- {zitat['autor']}")
    print(f"   Tags: {', '.join(zitat['tags'])}")
    print()

Verschiedene Methoden zum Finden von Elementen:

# find() -- Erstes Treffer-Element
erster = soup.find("div", class_="quote")

# find_all() -- Alle Treffer als Liste
alle = soup.find_all("div", class_="quote")

# select() -- CSS-Selektor (wie in CSS/JavaScript)
alle_css = soup.select("div.quote")

# select_one() -- Erster Treffer mit CSS-Selektor
erster_css = soup.select_one("div.quote > span.text")

# Text und Attribute extrahieren
link = soup.find("a")
print(link.text)           # Sichtbarer Text
print(link["href"])        # href-Attribut
print(link.get("class"))   # class-Attribut (als Liste)

Schritt 5: Daten in CSV und JSON speichern

Die extrahierten Daten wollen wir strukturiert abspeichern:

import json
import csv

def als_json_speichern(daten, dateipfad):
    """Speichert Daten als JSON-Datei."""
    with open(dateipfad, "w", encoding="utf-8") as f:
        json.dump(daten, f, indent=2, ensure_ascii=False)
    print(f"  {len(daten)} Einträge in '{dateipfad}' gespeichert.")

def als_csv_speichern(daten, dateipfad):
    """Speichert Daten als CSV-Datei."""
    if not daten:
        print("  Keine Daten zum Speichern.")
        return

    with open(dateipfad, "w", newline="", encoding="utf-8-sig") as f:
        # Tags als kommaseparierten String speichern
        felder = ["text", "autor", "tags"]
        writer = csv.DictWriter(f, fieldnames=felder, delimiter=";")
        writer.writeheader()

        for eintrag in daten:
            zeile = eintrag.copy()
            zeile["tags"] = ", ".join(zeile["tags"])  # Liste zu String
            writer.writerow(zeile)

    print(f"  {len(daten)} Einträge in '{dateipfad}' gespeichert.")


# Verwendung
zitate = zitate_scrapen("http://quotes.toscrape.com")
als_json_speichern(zitate, "zitate.json")
als_csv_speichern(zitate, "zitate.csv")

Die JSON-Ausgabe sieht so aus:

[
  {
    "text": "\u201cThe world as we have created it...\u201d",
    "autor": "Albert Einstein",
    "tags": ["change", "deep-thoughts", "thinking", "world"]
  }
]

Schritt 6: Mehrere Seiten scrapen (Pagination)

Die Übungsseite hat mehrere Seiten mit Zitaten. Wir scrapen alle automatisch:

import time

def alle_seiten_scrapen(basis_url):
    """Scrapet alle Seiten einer paginierten Webseite."""
    alle_zitate = []
    seite = 1
    url = basis_url

    while url:
        print(f"  Scrape Seite {seite}: {url}")

        try:
            antwort = requests.get(url, timeout=10)
            antwort.raise_for_status()
        except requests.RequestException as e:
            print(f"  Fehler bei {url}: {e}")
            break

        soup = BeautifulSoup(antwort.text, "html.parser")

        # Zitate dieser Seite extrahieren
        quote_elemente = soup.find_all("div", class_="quote")
        for element in quote_elemente:
            text = element.find("span", class_="text").text
            autor = element.find("small", class_="author").text
            tags = [t.text for t in element.find_all("a", class_="tag")]

            alle_zitate.append({
                "text": text,
                "autor": autor,
                "tags": tags
            })

        # Nächste Seite finden
        naechste = soup.find("li", class_="next")
        if naechste:
            naechster_link = naechste.find("a")["href"]
            url = basis_url + naechster_link
            seite += 1

            # Höfliche Pause zwischen Anfragen
            time.sleep(1)
        else:
            url = None  # Keine weitere Seite

    print(f"\n  Fertig! {len(alle_zitate)} Zitate von {seite} Seiten.")
    return alle_zitate


# Alle Zitate sammeln
alle_zitate = alle_seiten_scrapen("http://quotes.toscrape.com")

Wichtig: time.sleep(1) — Wir warten eine Sekunde zwischen den Anfragen, um den Server nicht zu überlasten. Das ist gute Praxis und verhindert, dass du geblockt wirst.


Schritt 7: Fehlerbehandlung

Netzwerkanfragen können aus vielen Gründen fehlschlagen. Robuste Fehlerbehandlung ist beim Web Scraping unverzichtbar:

import requests
from requests.exceptions import (
    ConnectionError, Timeout, HTTPError, RequestException
)

def sichere_anfrage(url, max_versuche=3, timeout=10):
    """Sendet eine HTTP-Anfrage mit Wiederholungslogik."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36"
    }

    for versuch in range(1, max_versuche + 1):
        try:
            antwort = requests.get(url, headers=headers, timeout=timeout)
            antwort.raise_for_status()
            return antwort

        except Timeout:
            print(f"  Timeout bei Versuch {versuch}/{max_versuche}")
        except ConnectionError:
            print(f"  Verbindungsfehler bei Versuch {versuch}/{max_versuche}")
        except HTTPError as e:
            print(f"  HTTP-Fehler: {e}")
            if antwort.status_code == 404:
                return None  # Seite existiert nicht
            if antwort.status_code == 429:
                print("  Zu viele Anfragen! Warte 30 Sekunden...")
                time.sleep(30)
        except RequestException as e:
            print(f"  Anfrage-Fehler: {e}")

        # Warten vor dem nächsten Versuch
        if versuch < max_versuche:
            wartezeit = versuch * 2  # Exponentielles Warten
            print(f"  Warte {wartezeit} Sekunden...")
            time.sleep(wartezeit)

    print(f"  Alle {max_versuche} Versuche fehlgeschlagen für {url}")
    return None


def sicheres_extrahieren(element, selektor, attribut=None, standard=""):
    """Extrahiert Daten sicher mit Fallback-Wert."""
    try:
        gefunden = element.select_one(selektor)
        if gefunden is None:
            return standard
        if attribut:
            return gefunden.get(attribut, standard)
        return gefunden.text.strip()
    except (AttributeError, KeyError):
        return standard

Die Funktion sichere_anfrage versucht die Anfrage mehrmals und wartet dazwischen. sicheres_extrahieren gibt einen Standardwert zurück, wenn ein Element nicht gefunden wird, anstatt das Programm abstürzen zu lassen.


Schritt 8: Daten mit OOP strukturieren

Jetzt bauen wir einen wiederverwendbaren Scraper mit Klassen:

import requests
from bs4 import BeautifulSoup
import json
import csv
import time
import os


class Zitat:
    """Repräsentiert ein einzelnes Zitat."""

    def __init__(self, text, autor, tags=None):
        self.text = text
        self.autor = autor
        self.tags = tags or []

    def to_dict(self):
        return {
            "text": self.text,
            "autor": self.autor,
            "tags": self.tags
        }

    def __str__(self):
        return f'"{self.text}" -- {self.autor}'

    def __repr__(self):
        return f"Zitat(autor='{self.autor}')"


class WebScraper:
    """Ein wiederverwendbarer Web Scraper."""

    def __init__(self, basis_url, pause=1.0):
        self.basis_url = basis_url
        self.pause = pause
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                          "AppleWebKit/537.36"
        })
        self.daten = []

    def seite_abrufen(self, url):
        """Ruft eine Webseite ab mit Fehlerbehandlung."""
        try:
            antwort = self.session.get(url, timeout=10)
            antwort.raise_for_status()
            return BeautifulSoup(antwort.text, "html.parser")
        except requests.RequestException as e:
            print(f"  Fehler beim Abrufen von {url}: {e}")
            return None

    def zitate_extrahieren(self, soup):
        """Extrahiert Zitate aus einer geparsten Seite."""
        zitate = []
        elemente = soup.find_all("div", class_="quote")

        for element in elemente:
            text_el = element.find("span", class_="text")
            autor_el = element.find("small", class_="author")
            tag_els = element.find_all("a", class_="tag")

            if text_el and autor_el:
                zitat = Zitat(
                    text=text_el.text,
                    autor=autor_el.text,
                    tags=[t.text for t in tag_els]
                )
                zitate.append(zitat)

        return zitate

    def naechste_seite_finden(self, soup):
        """Findet den Link zur nächsten Seite."""
        naechste = soup.find("li", class_="next")
        if naechste:
            link = naechste.find("a")
            if link:
                return self.basis_url + link["href"]
        return None

    def alle_scrapen(self):
        """Scrapet alle verfügbaren Seiten."""
        url = self.basis_url
        seite = 1
        self.daten = []

        print(f"\nStarte Scraping von {self.basis_url}")
        print(f"{'=' * 50}")

        while url:
            print(f"  Seite {seite}: {url}")

            soup = self.seite_abrufen(url)
            if soup is None:
                break

            zitate = self.zitate_extrahieren(soup)
            self.daten.extend(zitate)
            print(f"    -> {len(zitate)} Zitate gefunden")

            url = self.naechste_seite_finden(soup)
            seite += 1

            if url:
                time.sleep(self.pause)

        print(f"{'=' * 50}")
        print(f"  Fertig! Gesamt: {len(self.daten)} Zitate von "
              f"{seite - 1} Seiten\n")
        return self.daten

    # --- Speichermethoden ---

    def als_json_speichern(self, dateipfad="zitate.json"):
        """Speichert die Daten als JSON."""
        daten = [z.to_dict() for z in self.daten]
        with open(dateipfad, "w", encoding="utf-8") as f:
            json.dump(daten, f, indent=2, ensure_ascii=False)
        print(f"  {len(daten)} Einträge als JSON gespeichert: {dateipfad}")

    def als_csv_speichern(self, dateipfad="zitate.csv"):
        """Speichert die Daten als CSV."""
        with open(dateipfad, "w", newline="", encoding="utf-8-sig") as f:
            writer = csv.writer(f, delimiter=";")
            writer.writerow(["Text", "Autor", "Tags"])
            for z in self.daten:
                writer.writerow([z.text, z.autor, ", ".join(z.tags)])
        print(f"  {len(self.daten)} Einträge als CSV gespeichert: {dateipfad}")

    # --- Analyse-Methoden ---

    def autoren_zaehlen(self):
        """Zählt Zitate pro Autor."""
        zaehler = {}
        for z in self.daten:
            zaehler[z.autor] = zaehler.get(z.autor, 0) + 1
        return dict(sorted(zaehler.items(), key=lambda x: x[1], reverse=True))

    def tags_zaehlen(self):
        """Zählt die Häufigkeit aller Tags."""
        zaehler = {}
        for z in self.daten:
            for tag in z.tags:
                zaehler[tag] = zaehler.get(tag, 0) + 1
        return dict(sorted(zaehler.items(), key=lambda x: x[1], reverse=True))

    def nach_autor_filtern(self, autor):
        """Filtert Zitate nach Autor."""
        return [z for z in self.daten if autor.lower() in z.autor.lower()]

    def nach_tag_filtern(self, tag):
        """Filtert Zitate nach Tag."""
        return [z for z in self.daten if tag.lower() in
                [t.lower() for t in z.tags]]

    def statistik_anzeigen(self):
        """Zeigt eine Zusammenfassung der gescrapten Daten."""
        print(f"\n{'=' * 50}")
        print(f"  SCRAPING-STATISTIK")
        print(f"{'=' * 50}")
        print(f"  Gesamte Zitate:    {len(self.daten)}")

        autoren = self.autoren_zaehlen()
        print(f"  Verschiedene Autoren: {len(autoren)}")

        tags = self.tags_zaehlen()
        print(f"  Verschiedene Tags:    {len(tags)}")

        print(f"\n  Top 5 Autoren:")
        for autor, anzahl in list(autoren.items())[:5]:
            print(f"    {autor}: {anzahl} Zitate")

        print(f"\n  Top 5 Tags:")
        for tag, anzahl in list(tags.items())[:5]:
            print(f"    #{tag}: {anzahl} Mal")

        print(f"{'=' * 50}")

Vollständiger Code — Hauptprogramm

Hier ist das komplette Programm, das alles zusammenfügt:

import requests
from bs4 import BeautifulSoup
import json
import csv
import time


class Zitat:
    """Repräsentiert ein einzelnes Zitat."""

    def __init__(self, text, autor, tags=None):
        self.text = text
        self.autor = autor
        self.tags = tags or []

    def to_dict(self):
        return {"text": self.text, "autor": self.autor, "tags": self.tags}

    def __str__(self):
        return f'{self.text}\n   -- {self.autor}'


class WebScraper:
    """Web Scraper für quotes.toscrape.com"""

    def __init__(self, basis_url, pause=1.0):
        self.basis_url = basis_url
        self.pause = pause
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (kompatibel; PythonScraper/1.0)"
        })
        self.daten = []

    def seite_abrufen(self, url):
        try:
            antwort = self.session.get(url, timeout=10)
            antwort.raise_for_status()
            return BeautifulSoup(antwort.text, "html.parser")
        except requests.RequestException as e:
            print(f"  Fehler: {e}")
            return None

    def zitate_extrahieren(self, soup):
        zitate = []
        for el in soup.find_all("div", class_="quote"):
            text_el = el.find("span", class_="text")
            autor_el = el.find("small", class_="author")
            tag_els = el.find_all("a", class_="tag")
            if text_el and autor_el:
                zitate.append(Zitat(
                    text=text_el.text,
                    autor=autor_el.text,
                    tags=[t.text for t in tag_els]
                ))
        return zitate

    def naechste_seite(self, soup):
        nxt = soup.find("li", class_="next")
        if nxt and nxt.find("a"):
            return self.basis_url + nxt.find("a")["href"]
        return None

    def alle_scrapen(self):
        url = self.basis_url
        seite = 1
        self.daten = []

        print(f"\nStarte Scraping: {self.basis_url}")
        print("-" * 50)

        while url:
            print(f"  Seite {seite}...")
            soup = self.seite_abrufen(url)
            if not soup:
                break

            zitate = self.zitate_extrahieren(soup)
            self.daten.extend(zitate)

            url = self.naechste_seite(soup)
            seite += 1
            if url:
                time.sleep(self.pause)

        print(f"-" * 50)
        print(f"  {len(self.daten)} Zitate gesammelt.\n")
        return self.daten

    def als_json_speichern(self, pfad="zitate.json"):
        with open(pfad, "w", encoding="utf-8") as f:
            json.dump([z.to_dict() for z in self.daten], f,
                      indent=2, ensure_ascii=False)
        print(f"  Gespeichert: {pfad}")

    def als_csv_speichern(self, pfad="zitate.csv"):
        with open(pfad, "w", newline="", encoding="utf-8-sig") as f:
            writer = csv.writer(f, delimiter=";")
            writer.writerow(["Text", "Autor", "Tags"])
            for z in self.daten:
                writer.writerow([z.text, z.autor, ", ".join(z.tags)])
        print(f"  Gespeichert: {pfad}")

    def autoren_zaehlen(self):
        z = {}
        for d in self.daten:
            z[d.autor] = z.get(d.autor, 0) + 1
        return dict(sorted(z.items(), key=lambda x: x[1], reverse=True))

    def tags_zaehlen(self):
        z = {}
        for d in self.daten:
            for tag in d.tags:
                z[tag] = z.get(tag, 0) + 1
        return dict(sorted(z.items(), key=lambda x: x[1], reverse=True))

    def statistik(self):
        autoren = self.autoren_zaehlen()
        tags = self.tags_zaehlen()
        print(f"\n{'=' * 50}")
        print(f"  STATISTIK")
        print(f"{'=' * 50}")
        print(f"  Zitate: {len(self.daten)}")
        print(f"  Autoren: {len(autoren)}")
        print(f"  Tags: {len(tags)}")
        print(f"\n  Top 5 Autoren:")
        for a, n in list(autoren.items())[:5]:
            print(f"    {a}: {n}")
        print(f"\n  Top 5 Tags:")
        for t, n in list(tags.items())[:5]:
            print(f"    #{t}: {n}")
        print(f"{'=' * 50}")


# ============================================
# HAUPTPROGRAMM
# ============================================

def main():
    scraper = WebScraper("http://quotes.toscrape.com", pause=1.0)

    while True:
        print(f"\n{'=' * 40}")
        print(f"    WEB SCRAPER")
        print(f"{'=' * 40}")
        print("  1. Alle Zitate scrapen")
        print("  2. Statistik anzeigen")
        print("  3. Als JSON speichern")
        print("  4. Als CSV speichern")
        print("  5. Zitate nach Autor suchen")
        print("  6. Zitate nach Tag suchen")
        print("  7. Alle Zitate anzeigen")
        print("  0. Beenden")
        print(f"{'=' * 40}")

        wahl = input("Wahl: ").strip()

        if wahl == "1":
            scraper.alle_scrapen()

        elif wahl == "2":
            if scraper.daten:
                scraper.statistik()
            else:
                print("  Noch keine Daten. Zuerst scrapen (Option 1)!")

        elif wahl == "3":
            if scraper.daten:
                scraper.als_json_speichern()
            else:
                print("  Noch keine Daten.")

        elif wahl == "4":
            if scraper.daten:
                scraper.als_csv_speichern()
            else:
                print("  Noch keine Daten.")

        elif wahl == "5":
            if not scraper.daten:
                print("  Noch keine Daten.")
                continue
            autor = input("  Autor suchen: ").strip()
            treffer = [z for z in scraper.daten
                       if autor.lower() in z.autor.lower()]
            print(f"\n  {len(treffer)} Zitate von '{autor}':")
            for z in treffer:
                print(f"    {z}")

        elif wahl == "6":
            if not scraper.daten:
                print("  Noch keine Daten.")
                continue
            tag = input("  Tag suchen: ").strip()
            treffer = [z for z in scraper.daten
                       if tag.lower() in [t.lower() for t in z.tags]]
            print(f"\n  {len(treffer)} Zitate mit Tag '{tag}':")
            for z in treffer:
                print(f"    {z}")

        elif wahl == "7":
            for i, z in enumerate(scraper.daten, 1):
                print(f"\n  {i}. {z}")
                print(f"     Tags: {', '.join(z.tags)}")

        elif wahl == "0":
            print("\nAuf Wiedersehen!")
            break


if __name__ == "__main__":
    main()

Ethical Scraping Best Practices

Beim Web Scraping ist verantwortungsvolles Handeln essenziell. Halte dich an diese Regeln:

  1. robots.txt respektieren: Prüfe immer zuerst, ob Scraping erlaubt ist
  2. Rate Limiting: Warte zwischen Anfragen (mindestens 1 Sekunde)
  3. User-Agent setzen: Identifiziere deinen Scraper ehrlich
  4. Caching nutzen: Speichere Ergebnisse lokal, um wiederholte Anfragen zu vermeiden
  5. Fehler behandeln: Reagiere sinnvoll auf Statuscodes wie 429 (Too Many Requests)
  6. APIs bevorzugen: Wenn eine Webseite eine API anbietet, nutze diese
  7. Daten schützen: Speichere keine persönlichen Daten ohne Erlaubnis
  8. Nutzungsbedingungen lesen: Manche Seiten verbieten Scraping ausdrücklich
# Beispiel: robots.txt prüfen
def robots_txt_pruefen(basis_url):
    """Prüft und zeigt die robots.txt einer Webseite an."""
    url = basis_url.rstrip("/") + "/robots.txt"
    try:
        antwort = requests.get(url, timeout=5)
        if antwort.status_code == 200:
            print(f"\nrobots.txt von {basis_url}:")
            print("-" * 40)
            print(antwort.text)
        else:
            print(f"  Keine robots.txt gefunden (Status {antwort.status_code})")
    except requests.RequestException as e:
        print(f"  Fehler: {e}")

Erweiterungsideen

  • Automatisierung mit schedule: Den Scraper regelmäßig automatisch laufen lassen
  • E-Mail-Benachrichtigung: Bei neuen Daten per E-Mail benachrichtigt werden (mit smtplib)
  • Datenbank-Speicherung: Daten in SQLite statt JSON/CSV speichern
  • Selenium für dynamische Seiten: JavaScript-gerenderte Seiten mit Selenium scrapen
  • Proxy-Unterstützung: Über verschiedene Proxys scrapen, um IP-Sperren zu vermeiden
  • Dashboard: Gescrapte Daten visualisieren mit matplotlib oder einer Web-Oberfläche
  • Verschiedene Webseiten: Den Scraper so erweitern, dass er unterschiedliche Seitenstrukturen unterstützt
  • Benachrichtigungen: Preis-Alerts bei bestimmten Schwellenwerten

Was du gelernt hast

In diesem Projekt hast du folgende Konzepte praktisch angewendet:

  • HTTP-Anfragen — Mit requests Webseiten herunterladen
  • HTML-Parsing — Mit BeautifulSoup Daten aus HTML extrahieren
  • CSS-Selektoren — Elemente gezielt mit find, find_all und select finden
  • Dateioperationen — Ergebnisse als JSON und CSV speichern
  • Fehlerbehandlung — Robuster Code mit try/except für Netzwerkfehler
  • OOP — Wiederverwendbare Klassen Zitat und WebScraper
  • Sessionsrequests.Session() für effiziente Mehrfachanfragen
  • Ethisches Scraping — Verantwortungsvoller Umgang mit fremden Webseiten

Pro-Tipp: Wenn du Web Scraping professionell einsetzen möchtest, lohnt es sich, Scrapy kennenzulernen. Scrapy ist ein vollständiges Web-Scraping-Framework für Python, das Funktionen wie paralleles Scraping, automatisches Rate-Limiting, robuste Fehlerbehandlung und Pipeline-basierte Datenverarbeitung mitbringt. Für kleine Projekte reichen requests und BeautifulSoup völlig aus, aber für große Scraping-Aufgaben ist Scrapy das Werkzeug der Profis.

pip install scrapy
scrapy startproject mein_projekt
Zurück zum Python Kurs