Sitzungen und Cookies in Python: Login mit requests

Veröffentlicht:

19 Min. Lesezeit

Acar Diveroli
Autor: Acar Diveroli
Von der set-cookie-Zeile im Terminal führt eine Linie zum sessionid-Chip im Cookie-Glas und zur gespeicherten .json-Datei

Jeden Morgen melden Sie sich von Hand im Dashboard Ihres eigenen Unternehmens an und laden einen Bericht herunter. Das Dashboard hat einen Export-Button, keine offizielle API, und dieselben fünf Klicks wiederholen sich jeden Tag. Sobald Sie das an ein Python-Skript übergeben wollen, steht die Anmeldeseite als erste Mauer im Weg: Die Seite, die requests.get() herunterlädt, ist nicht der Bericht, sondern das Anmeldeformular. HTTP ist ein zustandsloses Protokoll, und der Server erkennt Sie nur an einem Cookie, das Sie jeder Anfrage beilegen.

In diesem Beitrag erklären wir, was Cookies und Sitzungen sind, warum das CSRF-Token bei jedem Lauf neu gelesen wird und wie ein Login-Ablauf mit requests.Session aufgebaut wird. Danach geht es darum, die Sitzung auf die Festplatte zu schreiben, sie nach Ablauf zu erneuern, Zugangsdaten aus dem Code herauszuhalten und während der gesamten Sitzung auf derselben Ausgangs-IP zu bleiben. Am Ende steht Playwrights Methode storage_state für Seiten, die ihr Formular per JavaScript abschicken. Die Codebeispiele wurden gegen quotes.toscrape.com getestet.

Für welche Logins gilt dieser Beitrag?

Alles hier beruht auf einer einzigen Annahme: Das Konto, in das Sie sich anmelden, gehört Ihnen, oder sein Inhaber hat Ihnen schriftlich die Erlaubnis gegeben. Das Verwaltungspanel Ihres eigenen Unternehmens, Ihr eigenes Shop-Konto, ein Kundensystem, bei dem der Kunde schriftlich darum gebeten hat, „diesen Bericht täglich für uns abzurufen“. Alles andere ist nicht Thema dieses Beitrags.

Drei Fragen, bevor Sie Code schreiben:

  1. Gibt es eine offizielle API oder einen Export? Wenn ja, automatisieren Sie nicht die Oberfläche. Ein API-Schlüssel ist stabil und bricht nicht, wenn sich die Oberfläche ändert; Login-Automatisierung ist der Weg, auf den Sie zurückgreifen, wenn es keinen anderen Zugang zu den Daten gibt. Den rechtlichen Rahmen haben wir in Ist Web Scraping legal? Ein Überblick behandelt.
  2. Was sagen die Nutzungsbedingungen der Seite? Verbietet eine Klausel den automatisierten Zugriff, kann Ihr Konto gesperrt werden. Technisch möglich heißt nicht erlaubt.
  3. Liegt Ihre Erlaubnis schriftlich vor? Bei Kundenprojekten reicht eine mündliche Zusage nicht. Mit welchem Konto, von welchen Seiten und in welchem Takt Sie Daten abrufen, gehört in einen Vertrag oder eine E-Mail.

Vier Dinge werden hier bewusst nicht erklärt:

  • Kein Durchprobieren von Passwörtern. Code, der eine Liste nacheinander in das Formularfeld schreibt (Credential Stuffing), oder Code, der Kombinationen erzeugt (Brute Force), kommt hier nicht vor. Tun Sie das nicht: Der unbefugte Zugriff auf ein fremdes Konto ist in jedem Land eine Straftat, und die technische Möglichkeit ändert daran nichts.
  • Kein Umgehen von Zwei-Faktor-Authentifizierung oder CAPTCHA. Diese Kontrollen schützen das Konto; sie mit einem Skript aushebeln zu wollen, senkt die Sicherheit genau des Kontos, mit dem Sie arbeiten. Ist in Ihrem eigenen Konto 2FA aktiv, führt der richtige Weg über den API-Schlüssel oder das App-Passwort des Anbieters; gibt es beides nicht, wird diese Aufgabe eben nicht automatisiert.
  • Keine fremden Konten. „Das Konto eines Freundes“, „das Konto eines ausgeschiedenen Mitarbeiters“ und im Internet geteilte Zugangsdaten gehören ebenfalls dazu.
  • Kein Stehlen oder Verschieben von Sitzungscookies. Eine Sitzung, die mit einem aus dem Browser einer anderen Person kopierten Cookie geöffnet wird, ist eine Identitätsübernahme. Die Cookie-Dateien in diesem Beitrag entstehen ausschließlich durch Ihre eigene Anmeldung und bleiben auf Ihrem eigenen Rechner.

Eine Abgrenzung ist wegen der Namensähnlichkeit nötig: Sich gegenüber einem Proxy-Server zu authentifizieren (user:pass oder IP-Freigabe) und sich bei einer Zielseite anzumelden, sind zwei verschiedene Dinge. Das Erste haben wir in Proxy-Authentifizierung: User:Pass oder IP-Whitelist beschrieben; hier geht es um das Zweite.

Was sind Cookies und Sitzungen?

HTTP ist zustandslos: Der Server erinnert sich an nichts, was zwei Anfragen miteinander verbindet. Diese Lücke füllen Cookies. Der Server setzt einen Set-Cookie-Header in seine Antwort, der Client speichert den Wert und schickt ihn bei späteren Anfragen an dieselbe Domain mit einem Cookie-Header zurück. Alle Attribute des Headers sind einzeln auf der Set-Cookie-Seite von MDN beschrieben.

Eine „Sitzung“ ist das, was diesem Cookie eine Bedeutung gibt. Zwei Bauweisen sind verbreitet:

  • Serverseitige Sitzung. Im Cookie steht nur eine zufällige Kennung (sessionid, PHPSESSID, JSESSIONID); wer der Nutzer ist, liegt in einem Datensatz auf dem Server. Löscht der Server diesen Datensatz, endet die Sitzung, auch wenn Sie das Cookie noch haben.
  • Signiertes Cookie. Die Nutzerinformation steckt im Cookie selbst, und der Server signiert sie mit einem geheimen Schlüssel. Das Standard-session-Cookie von Flask funktioniert so.

Wie lange ein Cookie lebt, legen Expires und Max-Age fest. Abschnitt 4.1.2.2 von RFC 6265 sagt: Hat ein Cookie weder Max-Age noch Expires, behält der Client es, bis „die aktuelle Sitzung vorbei ist“. Im Browser heißt das „bis Sie den Browser schließen“, in einem Python-Skript „solange der Prozess lebt“. Wundern Sie sich also nicht, wenn sich Ihr Skript bei jedem Lauf neu anmeldet: Das gespeicherte Cookie war ohnehin nicht dauerhaft.

Welchen Anfragen ein Cookie beigelegt wird, entscheiden diese Attribute des Set-Cookie-Headers:

AttributWas es tutBedeutung im Skript
DomainAn welche Domain das Cookie gehtEin Cookie für panel.beispiel.de wird einer Anfrage an beispiel.de nicht beigelegt
PathUnter welchem Pfad es giltEin Cookie auf /bericht wird bei einer Anfrage an / nicht gesendet
SecureNur über HTTPS gesendetEine Anfrage, die auf HTTP fällt, trägt kein Cookie
HttpOnlyJavaScript auf der Seite kann es nicht lesenIn der Browser-Konsole sehen Sie es mit document.cookie nicht, ein HTTP-Client schon
SameSiteOb es Anfragen von anderen Seiten beigelegt wirdBei Strict wirkt die erste Anfrage aus einem externen Link abgemeldet

Diese Tabelle ist keine Theorie, sondern eine Diagnoseliste: Die meisten Meldungen der Art „die Anmeldung sieht erfolgreich aus, aber die nächste Anfrage landet wieder auf der Anmeldeseite“ gehen auf eine Abweichung bei Domain oder Path zurück.

Was ist ein CSRF-Token und warum wird es jedes Mal gelesen?

Während Ihre Sitzung auf einer Bankseite offen ist, kann ein verstecktes Formular auf einer anderen Seite in Ihrem Namen eine Anfrage senden. Weil der Browser das Cookie automatisch anhängt, hält der Server das für Ihre Anfrage. Das nennt man Cross-Site Request Forgery (CSRF).

Die verbreitetste Abwehr ist das Verfahren, das auf der CSRF-Seite von OWASP „Synchronizer Token“ heißt: Der Server bettet in jede Formularseite einen zufälligen, an diese Sitzung gebundenen Wert als verstecktes Feld ein. Beim Absenden muss dieser Wert zu seinem Gegenstück im Sitzungscookie passen. Ein Formular auf einer anderen Seite kann das Token Ihrer Sitzung nicht kennen, also passt es nicht.

Für die Skriptseite hat das drei Folgen:

  • Das Token ist nicht fest. Kopieren Sie es einmal aus dem Browser in den Code, funktioniert es am nächsten Tag nicht mehr. Bei jedem Lauf müssen Sie die Anmeldeseite laden und das Feld dort auslesen.
  • Das Token hängt an der Sitzung. Das Token in einer Anfrage und das Formular über eine andere Verbindung zu schicken, bringt nichts. Beides muss durch dasselbe Session-Objekt laufen; genau das ist der erste Grund, überhaupt eine Session zu benutzen.
  • Der Feldname unterscheidet sich je nach Seite. csrf_token, csrfmiddlewaretoken (Django), authenticity_token (Rails) und _token (Laravel) erfüllen denselben Zweck. Sehen Sie sich den Quelltext des Formulars an, bevor Sie Code schreiben.

Erwartet eine Anwendung das Token statt im versteckten Feld in einem Header (X-CSRF-Token), lesen Sie den Wert weiterhin aus der Seite, legen ihn aber nicht in den Körper, sondern in das headers-Dictionary.

Welchen Weg sollten Sie wählen?

Für eine Aufgabe hinter einer Anmeldung gibt es drei Wege, und das Anmeldeformular der Seite bestimmt die Wahl.

WegWannKostenSchwachstelle
requests.SessionDas Formular ist reines HTML, die Felder stehen in der SeiteAm niedrigsten, kein BrowserSieht per JavaScript erzeugte Felder nicht
Playwright storage_stateDas Formular wird per JavaScript gesendet oder das Token per Skript erzeugtHoch, ein echter Browser startetSpeicher und Zeit, Einrichtungsaufwand
Hybrid: mit dem Browser anmelden, per HTTP weitermachenDie Anmeldung ist komplex, die Datenseiten sind reines HTMLEinmal Browser, danach günstigCookies wandern zwischen zwei Umgebungen, IP-Konsistenz ist Pflicht

Treffen Sie die Wahl nicht nach Gefühl, sondern anhand des Quelltexts: Sehen Sie in einem <form method="post"> input-Felder mit einem name, reicht requests. Sind die Felder leer oder wird das Formular mit fetch() gesendet, gehen Sie zum zweiten oder dritten Weg über.

Wie meldet man sich mit requests.Session an?

Es gibt fünf Schritte, und die Reihenfolge zählt: Die ersten vier sind die Anmeldung selbst, der fünfte ist für spätere Läufe.

  1. Laden Sie die Anmeldeseite per GET. Diese Antwort bringt zweierlei: ein noch anonymes Sitzungscookie und das CSRF-Token im Formular. Die Session legt das Cookie in ihren eigenen Speicher.
  2. Lesen Sie die Formularfelder wirklich aus. Füllen Sie das Formular im Browser aus, senden Sie es ab und sehen Sie sich im Netzwerk-Tab der Entwicklerwerkzeuge den Körper der Anfrage an. Feldnamen werden nicht geraten, sondern von dort kopiert; gibt es ein verstecktes next- oder redirect-Feld, senden Sie es mit.
  3. Senden Sie das POST mit derselben Session. Das Cookie wird von selbst angehängt. Zeigt das action-Attribut des Formulars auf einen anderen Pfad, schicken Sie die Anfrage dorthin.
  4. Prüfen Sie die Anmeldung am Inhalt. Der Statuscode täuscht: Viele Anwendungen liefern auch bei falschem Passwort eine 200 und zeigen das Formular mit einer Fehlermeldung erneut. Das Kriterium ist ein Element, das nur bei offener Sitzung erscheint: ein Abmeldelink, der Benutzername, ein Kontomenü.
  5. Speichern Sie die Cookies. Der nächste Lauf überspringt den Anmeldeschritt.

Laut dem Abschnitt Session Objects der Requests-Dokumentation hält eine Session nicht nur die Cookies über die Lebensdauer der Instanz, sondern nutzt für Anfragen an denselben Host auch die TCP-Verbindung erneut. Das Muster ist in allen drei Bibliotheken aus HTTPX, Requests und AIOHTTP im Vergleich gleich, nur der Klassenname ändert sich.

Ein lauffähiges Beispiel: quotes.toscrape.com

Der folgende Code wurde gegen quotes.toscrape.com/login getestet. Das ist eine offene Übungsseite für Scraping, und sie akzeptiert jeden Benutzernamen und jedes Passwort, das Sie eingeben; sie prüft keine Identität, sondern bildet nur den Ablauf nach. Den Mechanismus erst dort zu sehen, erspart Ihnen eine Reihe fehlgeschlagener Anmeldungen im eigenen Konto.

Zuerst der Kernablauf in vier Schritten:

python
import os

import requests
from bs4 import BeautifulSoup

BASE_URL = "https://quotes.toscrape.com"

session = requests.Session()

# 1) Anmeldeformular anfordern: Sitzungscookie und CSRF-Token kommen mit dieser Antwort
page = session.get(f"{BASE_URL}/login", timeout=20)
page.raise_for_status()
token = BeautifulSoup(page.text, "html.parser").select_one('input[name="csrf_token"]')["value"]

# 2) Zugangsdaten stehen nicht im Code, sie kommen aus Umgebungsvariablen
payload = {
    "csrf_token": token,
    "username": os.environ["SITE_USERNAME"],
    "password": os.environ["SITE_PASSWORD"],
}

# 3) Formular mit derselben Session senden; das Cookie hängt die Session selbst an
result = session.post(f"{BASE_URL}/login", data=payload, timeout=20)
result.raise_for_status()

# 4) Nicht am Statuscode, sondern am Seiteninhalt prüfen
if BeautifulSoup(result.text, "html.parser").select_one('a[href="/logout"]') is None:
    raise SystemExit("Anmeldung konnte nicht bestätigt werden")

print("Angemeldet, Cookies:", list(session.cookies.keys()))

Legen Sie die Zugangsdaten vor dem Start in die Umgebung; ein Passwort in den Code zu schreiben heißt, es auch in die Versionsgeschichte zu schreiben:

bash
export SITE_USERNAME="benutzer"
export SITE_PASSWORD="passwort"
python login.py

In der Windows PowerShell lautet die Form $env:SITE_USERNAME = "benutzer". Wie sich Umgebungsvariablen für Kommandozeilenwerkzeuge dauerhaft setzen lassen, haben wir in Proxy-Nutzung mit wget: Befehle und Beispiele beschrieben.

Wie speichert man eine Sitzung und wann erneuert man sie?

Sich bei jedem Lauf neu anzumelden, füllt das Sicherheitsprotokoll Ihres Kontos mit überflüssigen Einträgen; in manchen Anwendungen lösen kurz aufeinanderfolgende Anmeldungen zusätzlich eine weitere Prüfung aus. Die Lösung besteht darin, den Cookie-Speicher in eine Datei zu schreiben und beim nächsten Lauf zurückzuladen.

Das folgende vollständige Skript tut genau das: Es lädt gespeicherte Cookies, wenn es welche gibt, meldet sich sonst an, prüft bei jeder Anfrage, ob die Sitzung noch offen ist, und meldet sich einmal neu an, wenn sie abgebrochen ist. Dass es nur einmal geschieht, ist wichtig: Eine Schleife ohne Bedingung wird zu endlosen Anmeldeversuchen, wenn das Problem bei den Zugangsdaten liegt.

python
import json
import os
import time
from pathlib import Path

import requests
from bs4 import BeautifulSoup

BASE_URL = "https://quotes.toscrape.com"
COOKIE_FILE = Path("session_cookies.json")
DELAY_SECONDS = 2.0


class LoginError(Exception):
    """Die Anmeldung wurde nicht abgeschlossen; prüfen Sie die Ursache, statt es erneut zu versuchen."""


def build_session():
    session = requests.Session()
    session.headers["User-Agent"] = "bericht-skript/1.0 (kontakt: sie@beispiel.de)"
    proxy_url = os.environ.get("PROXY_URL")  # z. B. http://user:pass@pr.proxynet.io:8000
    if proxy_url:
        session.proxies = {"http": proxy_url, "https": proxy_url}
    return session


def save_cookies(session, path=COOKIE_FILE):
    cookies = [
        {"name": c.name, "value": c.value, "domain": c.domain, "path": c.path,
         "expires": c.expires, "secure": c.secure}
        for c in session.cookies
    ]
    path.write_text(json.dumps(cookies), encoding="utf-8")
    try:
        path.chmod(0o600)  # die Datei ist so sensibel wie ein Passwort; unter Windows begrenzte Wirkung
    except OSError:
        pass


def load_cookies(session, path=COOKIE_FILE):
    if not path.exists():
        return False
    try:
        cookies = json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        return False
    now = time.time()
    for c in cookies:
        if c["expires"] and c["expires"] < now:
            continue  # ein abgelaufenes Cookie gar nicht erst laden
        session.cookies.set(c["name"], c["value"], domain=c["domain"], path=c["path"],
                            expires=c["expires"], secure=c["secure"])
    return True


def is_logged_in(html):
    return BeautifulSoup(html, "html.parser").select_one('a[href="/logout"]') is not None


def login(session):
    page = session.get(f"{BASE_URL}/login", timeout=20)
    page.raise_for_status()
    field = BeautifulSoup(page.text, "html.parser").select_one('input[name="csrf_token"]')
    if field is None:
        raise LoginError("Kein Feld csrf_token im Formular; die Seitenstruktur hat sich geändert")
    payload = {
        "csrf_token": field["value"],
        "username": os.environ["SITE_USERNAME"],
        "password": os.environ["SITE_PASSWORD"],
    }
    result = session.post(f"{BASE_URL}/login", data=payload, timeout=20)
    result.raise_for_status()
    if not is_logged_in(result.text):
        raise LoginError("Anmeldung nicht bestätigt; prüfen Sie Zugangsdaten und Formularfelder")
    save_cookies(session)


def get_page(session, path):
    """Holt die Seite; ist die Sitzung abgebrochen, wird nur einmal neu angemeldet."""
    for attempt in range(2):
        response = session.get(f"{BASE_URL}{path}", timeout=20)
        response.raise_for_status()
        if is_logged_in(response.text):
            return response
        if attempt == 0:
            print("Sitzung ungültig, neue Anmeldung läuft")
            session.cookies.clear()
            login(session)
    raise LoginError("Auch nach der zweiten Anmeldung keine offene Sitzung; anhalten und Ursache prüfen")


def main():
    session = build_session()
    if load_cookies(session):
        print("Gespeicherte Cookies geladen")
    else:
        print("Keine gespeicherte Sitzung, Anmeldung läuft")
        login(session)

    for number in range(1, 4):
        response = get_page(session, f"/page/{number}/")
        quotes = BeautifulSoup(response.text, "html.parser").select("div.quote")
        print(f"Seite {number}: {len(quotes)} Einträge")
        time.sleep(DELAY_SECONDS)


if __name__ == "__main__":
    main()

Der erste Lauf sagt „Keine gespeicherte Sitzung“ und meldet sich an, der zweite sagt „Gespeicherte Cookies geladen“ und geht direkt zu den Daten. Drei Punkte verdienen Aufmerksamkeit:

  • Die Cookie-Datei ist ein Geheimnis. Der Wert darin ersetzt für diese Sitzung das Passwort. Nehmen Sie die Datei in .gitignore auf und halten Sie sie aus Backups und geteilten Ordnern heraus; die Playwright-Dokumentation gibt für ihre Zustandsdatei dieselbe Warnung aus.
  • Laden Sie kein abgelaufenes Cookie. load_cookies sortiert solche anhand des Feldes expires aus; sonst startet das Skript mit einer ungültigen Sitzung.
  • Erkennen Sie am Inhalt, dass die Sitzung abgebrochen ist. Manche Anwendungen leiten mit 302 auf die Anmeldeseite um, andere liefern das Formular mit 200. Ein einziges Kriterium wie is_logged_in fängt beide Fälle.

Warum brauchen Sie während der Sitzung dieselbe IP?

Ein Teil der Anwendungen bindet die geöffnete Sitzung an die IP-Adresse, von der die Anmeldung kam, oder an deren Netz. Kommt die nächste Anfrage von einer anderen Adresse, wird die Sitzung geschlossen, der Nutzer auf die Anmeldeseite geleitet oder eine zusätzliche Prüfung verlangt. Das ist eine Sicherheitsentscheidung, und ihr Zweck ist, die Verwendung eines gestohlenen Sitzungscookies an einem anderen Ort zu erschweren.

Für ein Skript mit Proxy heißt das: Ein rotierender Pool zerstört die Sitzung bei Aufgaben, die eine Anmeldung brauchen. Ein rotierender Proxy wechselt die Ausgangs-IP bei jeder Anfrage oder in kurzen Abständen; die Adresse, von der Sie sich angemeldet haben, und die Adresse, von der Sie den Bericht laden, sind dann verschieden, und die Anwendung erkennt Sie nicht. Wie Rotation funktioniert und für welche Aufgaben sie richtig ist, haben wir in Was ist IP-Rotation und wie funktioniert sie? beschrieben.

Der richtige Aufbau ist eine von zwei Optionen:

  • Mit Sticky-Proxy bleiben Sie für eine selbst gewählte Dauer von 1 bis 60 Minuten auf derselben Ausgangs-IP. Ist die Sitzung kurz und geben Sie die Verbindung nach getaner Arbeit frei, reicht das.
  • Mit ISP-Proxy bleibt die Adresse auch von Lauf zu Lauf gleich. Bei Systemen, die den Zugang zum Panel auf bestimmte IPs beschränken, ist das der einzige Weg, denn Sie melden die Adresse der Gegenseite einmal und lassen sie in die Liste eintragen.

Der Fall, eine Adresse in die Freigabeliste einer API eintragen zu lassen, steht in Statische IP für APIs: IP-Autorisierungsfehler beheben, die allgemeine Begründung für eine feste IP im Abschnitt „Sticky IP, wo eine Sitzung nötig ist“ von Web Scraping ohne Sperren: Daten regelkonform erfassen.

Einem Missverständnis sei vorgebeugt: Eine feste IP ist kein Werkzeug, um eine Sicherheitsprüfung zu umgehen. Sie sorgt dafür, dass Ihr eigener Verkehr in einem Konto, für das Sie ohnehin berechtigt sind, konsistent aussieht. Gehört das Konto nicht Ihnen, erzeugt auch eine feste IP keinen rechtmäßigen Zugang.

Rate-Limit: das Skript höflich halten

Ein angemeldetes Skript ist sichtbarer als ein anonymes: Ihre Anfragen werden jetzt nicht einer IP-Adresse, sondern direkt Ihrem Konto zugeschrieben. Das Rate-Limit einzuhalten, ist hier keine technische Vorliebe, sondern Teil des Schutzes Ihres Kontos.

Wenn die Anmeldung per JavaScript läuft: Playwright storage_state

In manchen Anwendungen sendet das Anmeldeformular kein klassisches POST: JavaScript liest die Felder, der Browser erzeugt das Token, die Antwort kommt über einen API-Aufruf. Auf einer solchen Seite scheitert ein mit requests gesendetes Formular stillschweigend. An dieser Stelle brauchen Sie einen echten Browser.

Playwright kann den Sitzungszustand in eine einzige JSON-Datei schreiben. Laut der Authentifizierungs-Dokumentation von Playwright trägt diese Datei Cookies und localStorage gemeinsam; sie hilft also auch bei Anwendungen, die die Sitzung nicht im Cookie, sondern im localStorage halten. Speichert die Anwendung das Token in IndexedDB, müssen Sie das gesondert anfordern: context.storage_state(path=..., indexed_db=True).

python
import os
from pathlib import Path

from playwright.sync_api import sync_playwright

BASE_URL = "https://quotes.toscrape.com"
STATE_FILE = Path("storage_state.json")
PROXY = {"server": "http://pr.proxynet.io:8000", "username": "user", "password": "pass"}

with sync_playwright() as p:
    browser = p.chromium.launch(proxy=PROXY)
    if STATE_FILE.exists():
        context = browser.new_context(storage_state=STATE_FILE)  # mit der gespeicherten Sitzung öffnen
    else:
        context = browser.new_context()
    page = context.new_page()
    page.goto(BASE_URL)

    if page.locator('a[href="/logout"]').count() == 0:
        page.goto(f"{BASE_URL}/login")
        page.fill("#username", os.environ["SITE_USERNAME"])
        page.fill("#password", os.environ["SITE_PASSWORD"])
        page.click('input[type="submit"]')
        page.wait_for_selector('a[href="/logout"]')  # Beleg für die Anmeldung
        context.storage_state(path=STATE_FILE)  # Cookies und localStorage in einer Datei
        print("Angemeldet, Zustand gespeichert")
    else:
        print("Sitzung mit gespeichertem Zustand offen")

    browser.close()

Der erste Lauf meldet sich an und schreibt die Datei, der zweite sieht den Anmeldeschritt gar nicht. Playwrights Proxy-Einstellung und die Browser-Installation haben wir ausführlich in Was ist Playwright und wie nutzt man es mit Proxy? beschrieben; das Gegenstück auf der Selenium-Seite steht in Selenium mit Proxy: Einrichtung in Python und Java.

Daraus ergibt sich das hybride Muster: einmal mit dem Browser anmelden und storage_state.json erzeugen, dann die Cookies in eine requests.Session übertragen und die Daten auf dem günstigen Weg holen.

python
import json
from pathlib import Path

import requests

state = json.loads(Path("storage_state.json").read_text(encoding="utf-8"))
session = requests.Session()
for c in state["cookies"]:
    session.cookies.set(c["name"], c["value"], domain=c["domain"], path=c["path"])

Dieses Muster hat eine einzige Bedingung: Sowohl der Browser als auch der nachfolgende HTTP-Client müssen über dieselbe Ausgangs-IP hinausgehen. Geben Sie beiden verschiedene Proxys, bricht die Sitzung bei der ersten Anfrage ab. Welche Browser-Automatisierung zu welcher Aufgabe passt, haben wir in Playwright und Selenium im Vergleich: Was passt wozu? gegenübergestellt.

Einsatzbereiche

  • Täglichen Bericht aus dem eigenen Panel laden. Hinter dem Export-Button steckt meist ein schlichter Dateilink; solange das Sitzungscookie hält, lädt requests ihn herunter. Der allgemeine Aufbau steht auf unserer Seite Datenerfassung.
  • Einen Ablauf hinter der Anmeldung überwachen. Um die Anmelde- und Warenkorbschritte der eigenen Anwendung nach jedem Deployment zu prüfen; der Aufbau steht auf unserer Seite App-Tests.
  • Datenübertragung aus einem Kundensystem. Mit schriftlicher Erlaubnis und einer festen Ausgangs-IP, damit die Gegenseite diese Adresse in ihre eigene Liste aufnehmen kann.
  • Daten aus einer mehrseitigen Liste sammeln. Nach der Anmeldung ist es eine gewöhnliche Paginierungsaufgabe; das Muster haben wir in Paginierung beim Scraping: mehrseitige Listen erfassen beschrieben.

Häufige Fehler und Diagnose

  • Endlose Weiterleitung auf die Anmeldeseite. Kehrt die Anfrage mit 302 zu /login zurück, wird das Cookie entweder gar nicht gesendet oder ist serverseitig ungültig. Sehen Sie zuerst in session.cookies nach, vergleichen Sie dann das Feld Domain des Cookies mit der aufgerufenen Adresse.
  • requests.get statt Session benutzen. Das requests.get() auf Modulebene öffnet bei jedem Aufruf eine neue Verbindung und einen leeren Cookie-Speicher. In einem Anmeldeablauf funktioniert das nicht.
  • Die Anmeldung am Statuscode prüfen. Auch bei falschem Passwort kann eine 200 kommen. Suchen Sie einen Beleg im Inhalt.
  • Das CSRF-Token in den Code schreiben. Einen Tag lang geht es gut, am nächsten kommt der Fehler „ungültiges Token“.
  • Die IP mitten in der Sitzung wechseln. Sich über einen rotierenden Pool anzumelden und dann Daten zu holen, ist der häufigste Grund für einen Sitzungsabbruch.
  • Nach einer fehlgeschlagenen Anmeldung in eine Schleife geraten. Ist das Passwort falsch, hilft ein erneuter Versuch nicht; in manchen Systemen sperrt er das Konto. Das Skript muss beim ersten Fehlschlag anhalten.

Entscheidungshilfe

SituationWas zu tun ist
Die Seite hat eine offizielle APIGar nicht erst automatisiert anmelden, API-Schlüssel nutzen
Formular ist reines HTML, Felder stehen in der SeiteMit requests.Session anmelden, Cookies in eine Datei speichern
Im Formular steckt ein verstecktes Feld wie csrf_tokenBei jedem Lauf aus der Seite lesen, nicht in den Code schreiben
Das Formular wird per JavaScript gesendetMit Playwright anmelden, die Datei storage_state aufbewahren
Anmeldung komplex, Datenseiten schlichtHybrid: mit dem Browser anmelden, Cookies zu requests übertragen
Die Sitzung bricht mittendrin abAusgangs-IP festhalten: Sticky-Proxy
Das Panel ist nur für bestimmte IPs offenFeste Adresse mit ISP-Proxy
Das Konto hat Zwei-Faktor-AuthentifizierungAPI-Schlüssel oder App-Passwort anfragen, die Prüfung nicht aushebeln
Das Konto gehört nicht IhnenAnhalten; schriftliche Erlaubnis des Inhabers einholen

Häufige Fragen

Ein Cookie ist ein kleines Datenstück, das der Server in Ihrem Browser oder Client ablegt und das bei jeder Anfrage zurückgeschickt wird. Die Sitzung ist der Zustand, auf den dieses Cookie zeigt: wer Sie sind, seit wann Sie angemeldet sind, welche Rechte Sie haben. Auf der Python-Seite ist requests.Session das Objekt, das beides verbindet; es speichert Cookies und trägt sie zwischen den Anfragen.

Ich habe mich angemeldet, aber die nächste Anfrage landet wieder auf der Anmeldeseite, warum?

Dafür gibt es drei häufige Gründe. Erstens haben Sie die Anmeldeanfrage außerhalb der Session gesendet und das Cookie ist verloren gegangen. Zweitens passt das Feld Domain oder Path des Cookies nicht zur Adresse Ihrer Anfrage. Drittens hat die Anwendung die Sitzung an eine IP gebunden und die zweite Anfrage kam wegen eines rotierenden Proxys von einer anderen Adresse.

Kann ich das CSRF-Token einmal holen und aufbewahren?

Nein. Das Token hängt an der Sitzung und ändert sich, sobald die Sitzung erneuert wird. Bei jedem Lauf müssen Sie die Anmeldeseite laden und das Feld dort auslesen. Aufzubewahren ist nicht das Token, sondern das Sitzungscookie, das nach der Anmeldung kommt.

Ist es strafbar, Daten von einer passwortgeschützten Seite zu holen?

Entscheidend ist nicht, dass die Seite ein Passwort verlangt, sondern ob Sie zum Zugriff auf dieses Konto berechtigt sind. Die eigenen Daten aus dem eigenen Konto zu holen, ist gewöhnliche Nutzung; sich mit fremden Zugangsdaten anzumelden, ist unbefugter Zugriff und in jedem Land eine Straftat. Verbieten die Nutzungsbedingungen automatisierten Zugriff, verstoßen Sie selbst im eigenen Konto gegen den Vertrag. Einzelheiten stehen in Ist Web Scraping legal? Ein Überblick.

Wie lange bleibt ein Sitzungscookie gültig?

Das hängt von der Anwendung ab. Ein Cookie ohne Max-Age oder Expires endet, wenn der Client geschlossen wird. Solche mit Laufzeit können von einigen Stunden bis zu einigen Wochen leben, aber wenn der Server seinen Datensatz löscht, endet die Sitzung, auch wenn Sie das Cookie noch haben. Deshalb prüft ein Skript nicht „ist es abgelaufen“, sondern „ist die Sitzung noch offen“.

Kann ich den Proxy nach der Anmeldung wechseln?

Besser nicht. Sind die IP, die die Sitzung geöffnet hat, und die IP der späteren Anfragen verschieden, kann die Anwendung die Sitzung schließen oder eine zusätzliche Prüfung verlangen. Verwenden Sie in derselben Aufgabe sowohl einen Browser als auch einen HTTP-Client, geben Sie beiden denselben Proxy.

Fazit

Der technische Kern einer Aufgabe hinter einer Anmeldung ist klein: requests.Session trägt die Cookies, das von der Anmeldeseite gelesene CSRF-Token kommt ins Formular, das Ergebnis wird am Seiteninhalt statt am Statuscode geprüft, und die Sitzung wird in eine Datei geschrieben und an den nächsten Lauf übergeben. Auf Seiten, die ihr Formular per JavaScript senden, übernimmt Playwrights Datei storage_state dieselbe Aufgabe. Damit die Sitzung nicht abbricht, darf die Ausgangs-IP währenddessen nicht wechseln; dafür stehen Sticky-Proxy oder eine feste Adresse bereit. Die Voraussetzung bleibt unverändert: Das Konto gehört Ihnen oder es liegt die schriftliche Erlaubnis des Inhabers vor, und gibt es eine offizielle API, wird zuerst diese versucht. Die passenden Proxy-Typen finden Sie in unseren Proxy-Diensten.

ChatGPT fragenClaude fragen