Weiter zum Inhalt

Reddit mit Python und BeautifulSoup 4 scrapen

In diesem Tutorial lernst du, wie du Webseiten mit requests abrufst, Seiten im Browser analysierst und Informationen aus Roh-HTML mit BeautifulSoup extrahierst.
Aktualisiert 18. Sept. 2026  · 13 Min. lesen

Mit KI erkunden

ChatGPTClaudePerplexity

Scraping reddit illustration

Ein fertiges, funktionsfähiges Beispielskript findest du hier.

Was ist Web Scraping?

Also, was genau ist Web Scraping? Wie der Name vermuten lässt, ist es eine Methode, Daten von Webseiten zu „scrapen“ bzw. zu extrahieren. Alles, was du im Browser sehen kannst — auch dieses Tutorial —, lässt sich auf deine Festplatte holen.

Web Scraping hat viele Einsatzfelder. Bei jeder Datenanalyse steht am Anfang die Datenerhebung. Das Internet ist ein riesiges Archiv menschlicher Geschichte und Wissens. Du hast die Möglichkeit, gezielt Informationen zu ziehen und sie für deine Zwecke zu nutzen.

In diesem Tutorial verwenden wir Python und das Paket BeautifulSoup 4, um Informationen aus einem Subreddit zu holen. Uns interessiert das Subreddit datascience. Wir wollen die ersten 1000 Posts abrufen und als CSV exportieren. Wir möchten wissen, wer gepostet hat sowie wie viele Likes und Kommentare der Beitrag hat.

Das behandeln wir im Tutorial:

  • Webseiten mit requests abrufen
  • Seiten im Browser nach relevanten Informationen analysieren
  • Informationen aus Roh-HTML mit BeautifulSoup extrahieren

Hinweis: Wir nutzen die ältere Reddit-Oberfläche, weil sie schlanker lädt und deine Maschine weniger belastet.

Voraussetzungen

Dieses Tutorial setzt Folgendes voraus:

  • Du kannst Python-Skripte auf deinem Rechner ausführen.
  • Du kennst die Grundstruktur von HTML.

Die nötigen Kompetenzen lernst du im Python-Einsteigerkurs von DataCamp. Die hier verwendeten Konzepte sind ohnehin sehr überschaubar — du kommst mit minimalen Python-Kenntnissen aus.

Damit ist alles geklärt, wir starten mit dem ersten Teil unseres Web-Scrapers. Genauer: mit dem ersten Schritt in jedem Python-Skript — den Imports.

Wir verwenden in unserem Scraper folgende Pakete:

  • requests
  • beautifulsoup4

Installieren kannst du sie mit pip, zum Beispiel so:

pip install package_name

Nach der Installation importierst du sie in deinen Code.

import requests
import csv
import time
from bs4 import BeautifulSoup4

Zum Schreiben der Ergebnisse in eine CSV-Datei nutzen wir das eingebaute csv-Modul. Vielleicht ist dir oben etwas aufgefallen: Wir installieren ein Paket namens beautifulsoup4, importieren aber aus einem Modul namens bs4. Das ist in Python legal — auch wenn es etwas irritierend wirkt.

Wichtiger Hinweis zu Plattformrichtlinien

Bevor du die Scraping-Techniken aus den nächsten Schritten nutzt, prüfe die aktuellen Nutzungsbedingungen von Reddit oder jeder anderen Website, die du scrapen willst. Richtlinien können sich ändern. Die Reddit User Agreement vom September 2024 untersagt Scraping ohne vorherige schriftliche Zustimmung (siehe Abschnitt 7). Stelle sicher, dass du stets die aktuellen Nutzungsrichtlinien der Seite einhältst, bevor du fortfährst.

Erste Schritte zum Crawlen von Reddit

Die Umgebung steht. Als Nächstes brauchen wir die URL der Seite, die wir scrapen möchten. Für unser Tutorial verwenden wir das Subreddit „datascience“ bei Reddit. Bevor wir mit dem Skript starten, machen wir etwas Vorarbeit im Browser. Öffne einen Browser und rufe das entsprechende Subreddit auf.

Wir nutzen für den Scraper die ältere Subreddit-Version. Die neue Variante versteckt einige wichtige Informationen in tiefen Strukturen der Seite. Es wäre zwar auch dort möglich, sie zu extrahieren, aber der Einfachheit halber nehmen wir die ältere Ansicht, in der alles offenliegt.

Nach dem Öffnen prasseln viele Informationen auf dich ein. Was davon brauchen wir? Beim Durchklicken stellst du fest: Es gibt zwei Typen von Reddit-Posts:

  • Inbound
  • Outbound

Inbound-Links verweisen auf Inhalte innerhalb von Reddit, Outbound-Links auf externe Webseiten. Das ist wichtig, denn wir wollen nur Inbound-Links — also Posts mit von Nutzerinnen und Nutzern geschriebenem Text, nicht nur Links nach außen.

Wie erkennen wir die passenden Posts? Schau dir die Titelzeile an: Dahinter steht in Klammern ein Hinweis. Die für uns interessanten Posts sind mit „(self.datascience)“ gekennzeichnet. Plausibel ist, dass „self“ für das Reddit-Stammverzeichnis steht und „.datascience“ für das Subreddit.

Super, wir können also Inbound- und Outbound-Posts unterscheiden. Jetzt müssen wir sie im DOM identifizieren. Lässt sich dieser Indikator über Tags, Klassen oder IDs im DOM finden? Klar!

Klicke im Browser mit rechts auf einen „self.datascience“-Link und wähle „Element untersuchen“. Moderne Browser haben ein tolles Tool, mit dem Entwickler den relevanten Quellcode-Bereich dynamisch untersuchen können. In Safari musst du die Entwicklerwerkzeuge in den Einstellungen aktivieren. Du siehst dann direkt den Quellcode-Ausschnitt, der für den Indikator „(self.datascience)“ zuständig ist.

Im Panel siehst du, dass der Indikator in einem Anker-Tag geladen wird.

<a href="/r/datascience">self.datascience</a>

Dieser ist wiederum von einem span-Tag umschlossen.

<span class="domain">
"("
<a href="/r/datascience">self.datascience</a>
")"
</span>

Dieses span-Tag enthält den sichtbaren Text „(self.datascience)“ und ist mit der Klasse „domain“ markiert. Prüfe weitere Links — sie folgen demselben Muster.

Die Seite mit BeautifulSoup abrufen

Wir wissen, was wir brauchen. Wie liest Python nun den Seiteninhalt? Im Prinzip so, wie ein Browser es tun würde.

Zuerst fordern wir die Seite mit der Bibliothek „requests“ an.

url = "https://old.reddit.com/r/datascience"
# Headers, um einen Browser-Besuch zu imitieren
headers = {'User-Agent': 'Mozilla/5.0'}

# Liefert ein requests.models.Response-Objekt
page = requests.get(url, headers=headers)

Jetzt haben wir ein Response-Objekt mit dem Rohtext der Seite. Damit können wir noch wenig anfangen — es ist ein einziger großer String mit dem gesamten HTML-Quelltext. Um daraus Struktur zu machen, nutzen wir BeautifulSoup 4.

Die Header helfen uns, einen echten Browser-Besuch zu simulieren. Da Server Bots oft anders antworten als auf Browser, holen wir uns besser die Browser-Variante.

Mit BeautifulSoup finden wir gezielt Tags — über Klassen, IDs oder Tagnamen. Intern wird dazu ein Syntaxbaum gebaut, die Details sind für unser Ziel aber nicht relevant.

Also erzeugen wir den Syntaxbaum.

soup = BeautifulSoup(page.text, 'html.parser')

Die „Soup“ ist ein BeautifulSoup-Objekt, das aus dem Quelltext-String erzeugt wurde. Wichtig: Den HTML-Parser angeben, da BeautifulSoup auch XML parsen kann.

Die passenden Tags finden

Wir wissen, welche Tags wir wollen (span-Tags mit der Klasse „domain“) und haben die Soup. Jetzt traversieren wir die Soup und sammeln alle Vorkommen. Mit BeautifulSoup ist das verblüffend einfach.

domains = soup.find_all("span", class_="domain")

Was passiert hier? Wir rufen find_all auf dem Soup-Objekt auf und suchen nach Tags, die den übergebenen Parametern entsprechen. Wir filtern hier nur nach der Klasse „domain“, könnten aber auch weitere Attribute kombinieren oder nach einer ID suchen.

Wir verwenden „class_=“, weil „class“ in Python ein reserviertes Schlüsselwort ist.

Wenn du mehrere Parameter übergeben willst, kannst du das als Dictionary tun, zum Beispiel so:

soup.find_all("span", {"class": "domain", "height", "100px"})

Jetzt haben wir alle span-Tags in der Liste „domains“, aber uns interessieren nur die mit „(self.datascience)“.

for domain in domains:
    if domain != "(self.datascience)":
        continue

    print(domain.text)

Du solltest nun eine Liste der Beitragstypen sehen, ohne die, die nicht „(self.datascience)“ sind. Damit haben wir im Grunde alle Inbound-Posts referenziert.

Die Informationen finden

Schön und gut, dass wir „(self.datascience)“ ausgeben. Aber unser Ziel ist: Titel, Autor, Likes und Kommentarzahl.

Dafür zurück in den Browser. Öffne erneut den Inspector für unseren Indikator. Du siehst: Das span-Tag steckt in einem div — das wiederum in einem div steckt, und so weiter. Gehst du weiter nach oben, landest du beim Eltern-div des gesamten Posts.

<div class=" thing id-t3_8qccuv even  link self" ...>
    ...
    <div class="entry unvoted">
        <div class="top-matter">
            <p class="title">
                ...
                <span class="domain">
                ...
            </p>
        </div>
    </div>
</div>

Der gemeinsame Parent liegt also vier Ebenen darüber. Die Auslassungen (\u2026) stehen für unwichtiges Beiwerk. Wir brauchen jetzt für jeden Eintrag in „domains“ eine Referenz auf das Post-div. In BeautifulSoup kommen wir über die Parent-Beziehungen dorthin.

for domain in soup.find_all("span", class_="domain"):
    if domain != "(self.datascience)":
        continue

    parent_div = domain.parent.parent.parent.parent
    print(parent_div.text)

Das Skript gibt damit den gesamten Text aller Inbound-Posts aus. Aber ja, das sieht fragil aus — und ist es auch. Sich nur auf die genaue DOM-Struktur zu verlassen, ist riskant und wartungsintensiv.

Stattdessen schauen wir uns das Eltern-div jedes Posts an und prüfen, ob wir Inbound/Outbound direkt dort unterscheiden können. Jedes Parent-div hat ein Attribut „data-domain“ — genau das brauchen wir! Alle Inbound-Posts haben hier „self.datascience“ stehen.

Wie erwähnt, können wir in BeautifulSoup nach einer Kombination von Attributen suchen. Praktischerweise hat Reddit das Parent-div der Posts mit „thing“ klassifiziert.

attrs = {'class': 'thing', 'data-domain': 'self.datascience'}

for post in soup.find_all('div', attrs=attrs):
    print(post.attrs['data-domain'])

Die Variable „attrs“ ist ein Dictionary mit Attributen und ihren Werten, nach denen wir filtern. Das ist deutlich robuster, weil weniger bewegliche Teile im Spiel sind. Da wir „self.datascience“ bereits als Suchkriterium angeben, sparen wir uns das if-Filtering — wir bekommen nur passende Posts zurück.

Jetzt, wo wir die richtigen Post-Container haben, müssen wir nur noch die Informationen aus den Kind-Elementen extrahieren. Das ist so einfach, wie es klingt.

Unsere Informationen aus Reddit extrahieren

Pro Post brauchen wir vier Infos:

  • Titel
  • Autor
  • Likes
  • Kommentare

Ein Blick in die Struktur des Post-divs zeigt, wo alles liegt.

Der Titel

Am einfachsten: In jedem Post-div gibt es einen p-Tag, ein paar Ebenen tief verschachtelt, mit der Klasse „title“.

title = post.find('p', class_="title").text

„post“ stammt aus unserer for-Schleife und ist selbst ein BeautifulSoup-Objekt, das nur die DOM-Struktur des jeweiligen Posts enthält. find gibt genau ein Objekt zurück (im Gegensatz zu find_all). Über die Eigenschaft .text holen wir den reinen Titeltext. Andere Attribute liest du über object.attrs['attribut'] aus.

Der Autor

Ebenfalls simpel: Öffne im Inspector einen Autorennamen unter dem Titel. Er steckt in einem a-Tag mit der Klasse „author“.

author = post.find('a', class_='author').text

Die Kommentare

Ein klein wenig Zusatzarbeit, aber unkompliziert. Wir finden die Kommentare ähnlich wie Titel und Autor.

comments = post.find('a', class_='comments').text

Beim Ausführen bekommst du etwas wie „49 comments“. Besser wäre nur die Zahl. Dafür nutzen wir Python weiter.

Mit str.split() wird der String an Leerzeichen getrennt und als Liste zurückgegeben. Hier also ['49', 'comments']. Perfekt — wir nehmen das erste Element und speichern es. Wir hängen die Aufrufe einfach an unsere Zeile an.

comments = post.find('a', class_='comments').text.split()[0]

Manchmal ist es aber kein Zahl-String, sondern „comment“ — nämlich wenn es noch keine Kommentare gibt. Das wissen wir und ersetzen es durch „0“.

if comments == "comment":
    comments = 0

Die Likes

Die Likes zu finden, ist genauso leicht und folgt derselben Logik.

likes = post.find("div", attrs={"class": "score likes"}).text

Hier kombinieren wir zwei Klassen, weil es mehrere divs mit „score“ oder „likes“ gibt, aber nur eines mit der Kombination „score likes“.

Wenn die Likes-Zahl 0 ist, kommt 0 zurück. Bei ganz neuen Posts kann aber ein „•“ erscheinen. Das ersetzen wir durch „None“, um Missverständnisse zu vermeiden.

if likes == "•":
    likes = "None"

Ergebnisse als CSV schreiben

Wir haben nun eine Schleife, die Titel, Autor, Likes und Kommentare für jeden Post extrahiert. Python bringt dafür mit csv ein schlankes, eingebautes Modul mit. Wir fügen am Ende des Schleifenblocks einfach eine Zeile hinzu, die die Post-Details in eine CSV schreibt.

counter = 1
for post in posts:
    ...
    post_line = [counter, title, author, likes, comments]
    with open('output.csv', 'a') as f:
        writer = csv.writer(f)
        writer.writerow(post_line)

    counter += 1

Ziemlich geradlinig, oder? post_line hält die durch Kommata zu trennenden Elemente. counter zählt mit, wie viele Posts wir bereits gespeichert haben.

Zur nächsten Seite wechseln

Da wir die Anzahl der gespeicherten Posts zählen, packen wir die gesamte Logik in eine weitere Schleife, die neue Seiten anfordert, bis eine bestimmte Anzahl an Posts erreicht ist.

counter = 1

while (counter <= 100):
    for post in posts:
        # Titel, Autor, ... auslesen
        # In CSV schreiben und counter erhöhen ...

    next_button = soup.find("span", class_="next-button")
    next_page_link = next_button.find("a").attrs['href']
    time.sleep(2)
    page = requests.get(next_page_link, headers=headers)
    soup = BeautifulSoup(page.text, 'html.parser')

Hier passiert einiges. Schritt für Schritt: Zuerst wandert counter eine Ebene nach oben, damit die while-Schleife darauf zugreifen kann.

Die for-Schleife kennst du. Danach suchen wir in next_button den „Weiter“-Button, holen uns den Link über das enthaltene a-Tag und dessen Attribut href und speichern ihn in next_page_link.

Mit diesem Link fordern wir die nächste Seite an, speichern sie in page und erzeugen eine neue Soup.

Das Beispiel oben stoppt nach 100 Beiträgen, du kannst die Grenze beliebig setzen.

Verantwortungsvoll scrapen

Die Zeile, über die wir noch nicht gesprochen haben, ist time.sleep(2). Sie verdient ein eigenes Kapitel. Jeder Webserver hat begrenzte Ressourcen. Es liegt an uns, sie nicht zu überlasten. Hunderte Seiten in Sekunden anzufordern, führt nicht nur zu Bans, es ist auch schlicht unfreundlich.

Behalte im Hinterkopf: Viele Websites können Bots nach 10 bis 20 Requests erkennen oder dich anhand der von Python gesendeten Request-Objekte identifizieren. Dass du oft trotzdem scrapen kannst, ist ein Entgegenkommen. Also verhalte dich rücksichtsvoll und verlangsame deinen Bot.

Die Crawl-Policy einer Website findest du in ihrer robots.txt, in der Regel im Root der Domain (z. B. http://www.reddit.com/robots.txt).

Wie geht es weiter?

Ganz wie du willst. Wie gezeigt, lässt sich alles, was du im Web siehst, scrapen und lokal speichern. Die gewonnenen Informationen kannst du vielseitig einsetzen. Schon mit den vier extrahierten Angaben lassen sich spannende Schlüsse ziehen.

Mit weiterer Analyse kannst du zum Beispiel herausfinden, welche Reddit-Posts die meisten Likes bekommen, welche Wörter häufig vorkommen oder wer besonders oft postet. Oder du baust einen „Kontroversen-Index“, indem du das Verhältnis von Likes zu Kommentaren analysierst.

Ein Beitrag mit vielen Kommentaren, aber wenigen Likes deutet vermutlich auf Reibung hin — er bewegt, gefällt aber nicht unbedingt.

Es gibt viele Möglichkeiten — du entscheidest, wie du die Informationen nutzt.

Wenn dir dieses Tutorial gefallen hat, schau dir auch unsere Anleitung zum Scrapen von Amazon mit Python an und entdecke unsere Python-Kurse.

Themen
Python

Erfahre mehr über Python

Kurs

Einführung in Python

4 Std.
7M
Lerne in nur vier Stunden die Grundlagen der Datenanalyse mit Python und entdecke beliebte Python-Pakete.
Details anzeigenRight Arrow
Kurs Starten
Mehr anzeigenRight Arrow