- 1 Section
- 10 Lessons
- unbegrenzt
- Python Fortgeschritten10
- 1.1Module und Pakete: import, pip, venv
- 1.2Objektorientierung in Python: Klassen, __init__, self
- 1.3Vererbung und Mehrfachvererbung in Python
- 1.4Decorators und Generatoren
- 1.5Fehlerbehandlung vertieft: eigene Exceptions
- 1.6Reguläre Ausdrücke mit re
- 1.7Arbeiten mit APIs: requests, JSON
- 1.8Automatisierung mit os, subprocess, pathlib
- 1.9Unit-Tests in Python: pytest
- 1.10Praxisprojekt: Automatisierungsskript mit Tests
Arbeiten mit APIs: requests, JSON
Eine API (Application Programming Interface) ist eine Schnittstelle, über die zwei Programme miteinander reden. Eine Wetter-App holt sich Vorhersagen von einer Wetter-API, dein E-Mail-Programm liest deine Mails über die API des Providers. Heute werden APIs fast immer als REST-APIs über HTTP realisiert – mit JSON als Datenformat.
Diese Lektion zeigt, wie du in Python APIs ansprichst: das fantastische Paket requests (kein Standardlib, aber De-facto-Standard) für HTTP-Aufrufe, das eingebaute json-Modul für die Datenformat-Konvertierung, Authentifizierung, Fehlerbehandlung mit Status-Codes und ein paar praktische Patterns für stabile API-Integrationen.
1) Was ist eine REST-API?
Eine REST-API stellt Ressourcen unter URLs zur Verfügung. Du sprichst sie mit HTTP-Methoden an:
GET https://api.example.de/users mit Headern (z. B. Auth-Token).Den ganzen HTTP-Teil übernimmt das Paket requests für dich. Du sagst „hol diese URL", und es kümmert sich um Sockets, Verschlüsselung, Header und Encoding.
2) requests installieren
requests ist keine Standardbibliothek – du installierst es per pip. Am besten in einer eigenen virtuellen Umgebung, wie in Module und Pakete: import, pip, venv beschrieben:
python3 -m venv .venv
source .venv/bin/activate # Linux/Mac
pip install requests
Danach steht der Import bereit:
import requests antwort = requests.get("https://api.github.com") print(antwort.status_code) # 200 print(antwort.json()) # Dict mit GitHub-API-Info
3) Die wichtigsten HTTP-Methoden
Eine REST-API nutzt verschiedene HTTP-Methoden, je nach Aktion:
requests.get(url)requests.post(url, json=...)requests.put(url, json=...)requests.patch(url, json=...)requests.delete(url)4) GET-Requests
Der einfachste Fall: Daten abrufen. requests.get nimmt die URL und optional Parameter:
import requests # Einfacher GET r = requests.get("https://jsonplaceholder.typicode.com/posts/1") print(r.json()) # GET mit Query-Parametern (werden zu ?userId=1&_limit=5) r = requests.get( "https://jsonplaceholder.typicode.com/posts", params={"userId": 1, "_limit": 5} ) print(r.url) # zeigt die zusammengebaute URL posts = r.json() print(len(posts)) # 5
Praktisch: du musst nicht selbst URL-Encoding machen. Das Dict bei params wird zur Query-String ?userId=1&_limit=5 – Sonderzeichen automatisch korrekt encoded.
5) POST mit JSON-Body
Neue Daten an die API schicken:
neuer_post = {
"title": "Mein erster Post",
"body": "Inhalt",
"userId": 42
}
r = requests.post(
"https://jsonplaceholder.typicode.com/posts",
json=neuer_post # Python-Dict → automatisch zu JSON
)
print(r.status_code) # 201 Created
print(r.json()) # Server gibt das Erstellte zurück (oft mit neuer ID)
json=neuer_post macht zwei Dinge: serialisiert das Dict zu JSON und setzt den Content-Type-Header auf application/json. Das ist 99 % der Fälle das, was du willst. Mit data=... könntest du stattdessen Form-Daten senden.
6) Response-Objekt
Jeder Aufruf gibt ein Response-Objekt zurück. Die wichtigsten Attribute und Methoden:
r = requests.get("https://api.github.com") r.status_code # 200, 404, 500, ... r.ok # True wenn 2xx oder 3xx r.headers # Dict mit Antwort-Headern r.text # Body als String r.content # Body als bytes (für Binärdaten) r.json() # Body als Python-Dict/Liste (parst JSON) r.encoding # Zeichen-Encoding der Antwort r.elapsed # Dauer der Anfrage r.url # tatsächlich abgerufene URL
Beim Lesen von r.json() kann eine Exception (ValueError / JSONDecodeError) fliegen, falls die Antwort kein gültiges JSON ist – etwa bei einer Fehlerseite. Immer mit try/except absichern (siehe Fehlerbehandlung vertieft in Python).
7) HTTP-Status-Codes
Der Status-Code sagt, wie es lief. Die Hunderter-Gruppen:
Mit r.raise_for_status() wirft Python eine Exception bei 4xx/5xx-Codes – praktisch zur sicheren Fehlererkennung:
try: r = requests.get(url) r.raise_for_status() # wirft HTTPError bei 4xx/5xx daten = r.json() except requests.HTTPError as e: print(f"HTTP-Fehler: {e}") except requests.RequestException as e: print(f"Netzwerk-Fehler: {e}")
8) Headers und Authentifizierung
Viele APIs verlangen ein Authentifizierungs-Token in einem Header:
import os token = os.environ["GITHUB_TOKEN"] # Token aus Umgebung holen headers = { "Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json", "User-Agent": "mein-tool/1.0" } r = requests.get("https://api.github.com/user", headers=headers) print(r.json()["login"])
Andere Auth-Varianten:
# Basic Auth (Username + Passwort) r = requests.get(url, auth=("user", "pass")) # API-Key als Query-Parameter r = requests.get(url, params={"api_key": token}) # API-Key als spezieller Header r = requests.get(url, headers={"X-API-Key": token})
.py-Dateien, kein Commit ins Git. Tokens kommen aus Umgebungsvariablen (os.environ) oder aus Konfigurations-Dateien, die in .gitignore stehen. Wer das ignoriert, verteilt seine Zugänge öffentlich.
9) JSON und Python-Datenstrukturen
JSON (JavaScript Object Notation) ist ein textbasiertes Datenformat. Python hat das eingebaute Modul json für die Konvertierung. requests nutzt es intern.
[] array
"text" string
42 / 3.14 number
true / false boolean
null
list
str
int / float
True / False
None
datetime oder set kennt JSON nicht – die musst du als String darstellen oder eigene Encoder schreiben.import json # Python-Objekt → JSON-String daten = {"name": "Anna", "alter": 28, "tags": ["py", "sql"]} text = json.dumps(daten) print(text) # {"name": "Anna", "alter": 28, "tags": ["py", "sql"]} # JSON-String → Python-Objekt zurueck = json.loads(text) print(zurueck["name"]) # Anna # Mit Datei direkt with open("daten.json", "w") as f: json.dump(daten, f, indent=2) with open("daten.json") as f: daten = json.load(f)
Merkhilfe: dumps/loads arbeiten mit Strings, dump/load mit Datei-Objekten. Das „s" steht für „string".
10) Sessions – Cookies und Header behalten
Wenn du mehrere Aufrufe an dieselbe API machst, lohnt sich eine Session. Sie behält Cookies, Auth-Header und nutzt dieselbe TCP-Verbindung wieder – deutlich schneller:
session = requests.Session() session.headers.update({"Authorization": f"Bearer {token}"}) # Auth-Header gilt für alle weiteren Aufrufe r1 = session.get("https://api.example.de/users") r2 = session.get("https://api.example.de/posts") r3 = session.post("https://api.example.de/posts", json=neu) session.close() # oder mit-with-Statement
Mit with:
with requests.Session() as s: s.headers["User-Agent"] = "mein-tool/1.0" daten = s.get(url).json()
11) Timeouts
Wichtig in der Praxis: setze einen Timeout. Sonst hängt dein Programm beliebig lange, wenn der Server nicht antwortet:
# 5 Sekunden für die ganze Anfrage r = requests.get(url, timeout=5) # 3s zum Verbinden, 10s zum Lesen der Antwort r = requests.get(url, timeout=(3, 10))
Bei Überschreitung wirft requests einen requests.Timeout-Fehler. Das ist ein Subtyp von RequestException, dem Oberbegriff für alle Netzwerk-Probleme. Ohne Timeout kann eine API-Aufruf-Schleife im schlimmsten Fall Tage hängen.
12) Robuste Funktion mit Retry
In der Praxis bauen viele eine kleine Wrapper-Funktion mit Wiederholungs-Logik bei temporären Fehlern:
import time import requests def api_get(url, headers=None, retries=3): for versuch in range(retries): try: r = requests.get(url, headers=headers, timeout=10) if r.status_code == 429: # Rate-Limit warte = int(r.headers.get("Retry-After", 5)) time.sleep(warte) continue if r.status_code >= 500: # Server-Fehler time.sleep(2 ** versuch) # Exponential Backoff continue r.raise_for_status() return r.json() except requests.RequestException: if versuch == retries - 1: raise time.sleep(2 ** versuch) raise RuntimeError("Alle Versuche fehlgeschlagen")
Das Muster Exponential Backoff – jede Wiederholung wartet doppelt so lang – ist Standard für API-Clients. Es entlastet überlastete Server und vermeidet, dass viele Clients gleichzeitig wieder zuschlagen.
13) Beispiel: Wetter-API abfragen
Ein typischer kleiner Anwendungsfall – Wetter abfragen und auswerten:
import requests def wetter_holen(stadt, api_key): url = "https://api.openweathermap.org/data/2.5/weather" params = { "q": stadt, "appid": api_key, "units": "metric", "lang": "de" } r = requests.get(url, params=params, timeout=5) r.raise_for_status() return r.json() daten = wetter_holen("Konstanz", key) print(f"{daten['name']}: {daten['main']['temp']}°C, " f"{daten['weather'][0]['description']}") # Konstanz: 18.3°C, leichter Regen
14) Beispiel: POST mit Datei-Upload
Datei an eine API hochladen – mit dem files-Parameter:
with open("bild.jpg", "rb") as f: r = requests.post( "https://api.example.de/upload", files={"foto": f}, data={"titel": "Mein Urlaub"} # zusätzliche Form-Felder )
requests baut intern eine multipart/form-data-Anfrage – das Standard-Format für Datei-Uploads im Web.
15) Häufige Fehler
- Kein Timeout gesetzt: das Skript hängt bei einem trägen Server stundenlang.
timeout=immer setzen - r.json() ohne try: bei einer Fehlerseite (HTML-Inhalt statt JSON) wirft
r.json()einenJSONDecodeError. Immer absichern - Status-Code ignorieren: r.json() gibt auch bei 404 oder 500 was zurück – nur eben einen Fehler-JSON. Immer
r.raise_for_status()oderr.okprüfen - Token im Code: per Git in die Welt gepostet, später per Rotation entfernen.
os.environoder Vault - data vs json verwechselt:
data=dictist Form-Daten,json=dictist JSON-Body. Bei moderner REST-API fast immerjson= - Sessions nicht genutzt: bei vielen Calls Verbindung jedes Mal neu aufbauen kostet Zeit.
requests.Session()nutzen - Keine Retry-Logik bei 429/5xx: einzelner Fehler bricht das Skript ab, statt es zu wiederholen
- SSL-Warnung ignorieren mit
verify=False: sollte nur lokal in Test-Umgebungen vorkommen – in Produktion ein Sicherheitsrisiko
/swagger oder /docs). Mit curl oder dem Tool httpie auf der Kommandozeile testen, bevor du in Python kämpfst. Die requests-Doku ist eine der besten Python-Docs überhaupt – nachlesen lohnt.
Zusammenfassung
Eine REST-API stellt Ressourcen unter URLs bereit, du sprichst sie mit HTTP-Methoden an: GET liest, POST erstellt, PUT/PATCH ändert, DELETE löscht. Das Paket requests macht HTTP-Aufrufe in Python trivial – Aufruf mit requests.get/post/..., Antwort als Response-Objekt mit status_code, headers, json(). JSON ist das übliche Datenformat – dict ↔ object, list ↔ array, str ↔ string. r.raise_for_status() wirft bei 4xx/5xx. Mit headers setzt du Auth-Token (niemals hardcoden!), mit params Query-Parameter, mit json= JSON-Body. Sessions wiederverwenden Verbindungen und Header. Timeout setzen ist Pflicht. Bei 429/5xx wiederholen mit Exponential Backoff.
