Abschnitt 17 · Lektion 1
Wie ein Programm fragt
In diesem Abschnitt holen Programme Daten aus dem Netz. Das hat hier einen Haken, denn die Maschine, auf der dein Code läuft, hat gar kein Netz. Der Dienst, den die Programme fragen, läuft deshalb auf derselben Maschine.
Er heißt wetterdienst.py und liegt in jedem Beispiel und jeder Aufgabe dieses Abschnitts. Das ist
ein kleiner Wetterdienst in Python, und die Zeile wetterdienst.starten() startet ihn im
Hintergrund. Die Anfragen an ihn sind trotzdem echt. Sie gehen auf demselben Weg raus und kommen
genauso zurück wie bei einem Rechner am anderen Ende der Welt, nur ist die Strecke dazwischen eben
kurz. Auf deinem eigenen Rechner fällt die Zeile mit starten() weg, und statt der Adresse hier
steht dann die eines echten Dienstes.
Adresse, Anfrage, Antwort
Wenn ein Programm einen anderen Rechner etwas fragt, schickt es eine Anfrage (request) an eine Adresse (URL). Zurück kommt eine Antwort (response). Das Verfahren dafür heißt HTTP. Es ist dasselbe, mit dem dein Browser Webseiten holt.
Gefragt wird hier die Adresse http://127.0.0.1:8000/wetter?stadt=Berlin, und jeder Teil davon hat
seine eigene Aufgabe:
http://sagt, wie gefragt wird. Bei Diensten im Internet steht dort fast immerhttps://, die verschlüsselte Fassung.127.0.0.1ist der Rechner. Diese Nummer heißt immer „dieser Rechner selbst“, undlocalhostist ein anderer Name dafür. Sonst steht dort ein Name wieapi.example.com.:8000ist der Port. Auf einem Rechner können viele Dienste laufen, und jeder lauscht an seiner eigenen Nummer. Fehlt sie, gilt beihttpsdie 443 und beihttpdie 80./wetterist der Pfad, also das, was man von dem Dienst will.?stadt=Berlinsind die Parameter, die Angaben zur Frage. Mehrere stehen hintereinander, mit&dazwischen:?stadt=Berlin&tage=3.
Die Antwort hat drei Teile. Ganz vorn steht der Statuscode. Das ist eine Zahl, an der man sieht,
wie es gelaufen ist: 200 heißt alles in Ordnung, 404 heißt „gibt es nicht“, und 500 heißt, beim
Dienst selbst ist etwas schiefgegangen. Danach kommen die Kopfzeilen (headers) mit Angaben über
die Antwort, etwa Content-Type für die Art des Inhalts. Zum Schluss kommt der Rumpf (body).
Das ist der eigentliche Inhalt.
Eine Schnittstelle für Programme
Ein Webserver schickt meistens HTML. Das ist eine Seite für Menschen, und der Browser macht sie hübsch. Ein Dienst für Programme schickt stattdessen Daten, fast immer als JSON wie in Lektion 12.5. So einen Dienst nennt man Schnittstelle, auf Englisch API (application programming interface).
Wetterdienste haben solche Schnittstellen, Banken, Paketdienste, Ämter und Verkehrsbetriebe auch. Wer eine benutzen will, schaut vorher in ihre Beschreibung. Da steht, welche Adressen es gibt, welche Parameter sie nehmen und wie die Antwort aufgebaut ist.
Mit urllib
# Der Wetterdienst für diesen Abschnitt. Die Maschine hier hat kein Netz, deshalb
# läuft der Dienst auf derselben Maschine wie dein Programm, unter der Adresse
# http://127.0.0.1:8000. Die Anfragen an ihn sind trotzdem echt: Sie gehen über
# HTTP hinaus und kommen als Antwort zurück, nur eben nicht durch das Internet.
# Hier musst du nichts ändern.
import hashlib
import json
import threading
from datetime import date, timedelta
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, urlparse
# Das Wetter am 09.10.2026 um 14 Uhr: Grad, Wetter, Wind in km/h.
AKTUELL = {
"Berlin": (14.2, "bewölkt", 12),
"Hamburg": (12.8, "Regen", 24),
"München": (9.6, "Nebel", 3),
"Köln": (15.1, "heiter", 8),
"Frankfurt am Main": (13.4, "bewölkt", 10),
"Stuttgart": (11.9, "Regen", 14),
"Hannover": (13.1, "bewölkt", 16),
"Nürnberg": (10.8, "Nebel", 4),
"Leipzig": (13.7, "heiter", 9),
"Dresden": (12.3, "bewölkt", 11),
"Erfurt": (12.6, "heiter", 7),
}
# Die Vorhersage ab dem 09.10.2026, je Tag: Höchstwert, Tiefstwert, Regen in mm
# und das Wetter.
VORHERSAGE = {
"Leipzig": [
(15.2, 6.1, 0.0, "heiter"),
(17.8, 7.4, 0.0, "sonnig"),
(18.4, 8.0, 0.0, "sonnig"),
(16.9, 9.2, 0.4, "bewölkt"),
(13.5, 8.8, 6.2, "Regen"),
(11.2, 7.5, 9.8, "Regen"),
(12.6, 5.9, 1.1, "bewölkt"),
],
"Dresden": [
(14.6, 5.2, 0.2, "bewölkt"),
(16.3, 6.8, 0.0, "heiter"),
(15.0, 8.1, 0.0, "bewölkt"),
(16.3, 7.7, 0.0, "heiter"),
(19.1, 8.4, 0.0, "sonnig"),
(12.4, 7.0, 5.1, "Regen"),
(10.8, 6.2, 7.6, "Regen"),
],
"Erfurt": [
(13.9, 4.8, 0.0, "heiter"),
(15.6, 5.5, 0.0, "sonnig"),
(14.1, 7.2, 2.3, "Regen"),
(12.0, 6.6, 4.0, "Regen"),
(12.8, 5.1, 0.6, "bewölkt"),
(14.7, 4.3, 0.0, "heiter"),
(13.3, 3.9, 0.0, "heiter"),
],
}
# Für diese Städte klappt etwas nicht, so wie es bei echten Diensten vorkommt.
STOERUNGEN = {
"Bremen": 500, # der Dienst hat selbst einen Fehler
"Kiel": 503, # der Dienst wird gerade gewartet
"Rostock": "keine Antwort", # er nimmt die Anfrage an und antwortet nie
"Lübeck": "Abbruch", # er legt auf, ohne zu antworten
}
# Die Vorhersage gibt es nur mit Schlüssel in der Kopfzeile X-Api-Key. Wie jeder
# ordentliche Dienst kennt dieser den Schlüssel nicht selbst, sondern nur eine
# Prüfsumme davon.
PRUEFSUMME = "0fe6daa0a6754b2239b7022999ec7a5ae2b20fc096afc6d9eead51d07bef68ce"
class Anfrage(BaseHTTPRequestHandler):
def do_GET(self):
adresse = urlparse(self.path)
werte = {name: liste[0] for name, liste in parse_qs(adresse.query).items()}
stadt = werte.get("stadt", "")
stoerung = STOERUNGEN.get(stadt)
if adresse.path not in ("/wetter", "/vorhersage"):
self.sende(404, {"fehler": f"Unbekannte Adresse: {adresse.path}"})
elif stoerung == "keine Antwort":
# Wartet auf etwas, das nie passiert. Der Dienst endet trotzdem mit
# deinem Programm, weil er im Hintergrund läuft.
threading.Event().wait()
elif stoerung == "Abbruch":
self.close_connection = True
elif stoerung is not None:
self.sende(stoerung, self.responses[stoerung][0])
elif adresse.path == "/wetter":
self.wetter(stadt)
else:
self.vorhersage(stadt, werte.get("tage", "7"))
def wetter(self, stadt):
if stadt not in AKTUELL:
self.sende(404, {"fehler": f"Unbekannte Stadt: {stadt}"})
return
grad, wetter, wind = AKTUELL[stadt]
aktuell = {"temperatur": grad, "wetter": wetter, "wind": wind}
self.sende(200, {"stadt": stadt, "zeit": "2026-10-09T14:00", "aktuell": aktuell})
def vorhersage(self, stadt, tage):
schluessel = self.headers.get("X-Api-Key", "")
if hashlib.sha256(schluessel.encode()).hexdigest() != PRUEFSUMME:
self.sende(401, {"fehler": "Ohne gültigen Schlüssel in X-Api-Key gibt es keine Vorhersage"})
elif stadt not in VORHERSAGE:
self.sende(404, {"fehler": f"Keine Vorhersage für: {stadt}"})
elif not tage.isdigit() or not 1 <= int(tage) <= 7:
self.sende(400, {"fehler": "tage geht von 1 bis 7"})
else:
liste = []
for nummer, (hoch, tief, regen, wetter) in enumerate(VORHERSAGE[stadt][: int(tage)]):
liste.append(
{
"datum": (date(2026, 10, 9) + timedelta(days=nummer)).isoformat(),
"wetter": wetter,
"hoechstwert": hoch,
"tiefstwert": tief,
"regen": regen,
}
)
self.sende(200, {"stadt": stadt, "tage": liste})
def sende(self, status, inhalt):
"""Schickt die Antwort: ein Dictionary als JSON, einen Text als Text."""
if isinstance(inhalt, str):
art, rumpf = "text/plain", inhalt
else:
art, rumpf = "application/json", json.dumps(inhalt, ensure_ascii=False)
daten = rumpf.encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", f"{art}; charset=utf-8")
self.send_header("Content-Length", str(len(daten)))
self.end_headers()
self.wfile.write(daten)
def log_message(self, *angaben):
# Sonst stünde jede Anfrage auf dem Fehlerkanal deines Programms.
pass
_server = None
def starten():
"""Startet den Dienst im Hintergrund. Er läuft, bis dein Programm endet."""
global _server
if _server is not None:
return
try:
_server = ThreadingHTTPServer(("127.0.0.1", 8000), Anfrage)
except OSError:
# Port 8000 ist belegt, weil der Dienst aus einem anderen Lauf noch
# antwortet. Dann fragt dein Programm eben den.
return
threading.Thread(target=_server.serve_forever, daemon=True).start() import json
import urllib.request
import wetterdienst
wetterdienst.starten()
ADRESSE = "http://127.0.0.1:8000"
with urllib.request.urlopen(ADRESSE + "/wetter?stadt=Berlin", timeout=2) as antwort:
print(antwort.status)
print(antwort.headers["Content-Type"])
rumpf = antwort.read()
print(rumpf)
daten = json.loads(rumpf)
print(daten["aktuell"]["temperatur"]) Für eine Anfrage braucht Python kein Paket. Alles Nötige steckt im Modul urllib.request der
Standardbibliothek. urlopen schickt die Anfrage los und gibt die Antwort zurück. Dabei bleibt eine
Verbindung offen, deshalb steht das Ganze in einem with-Block wie bei einer Datei. Mit timeout=2
wartet das Programm höchstens zwei Sekunden auf die Antwort. Warum das wichtig ist, kommt in den
nächsten beiden Lektionen.
In der Ausgabe siehst du die drei Teile der Antwort. Zuerst den Status 200, dann die Kopfzeile
Content-Type mit application/json, also JSON, und dahinter die Kodierung.
Der Rumpf kommt als Bytes an. Das erkennt man am b vor den Anführungszeichen. Das ö aus „bewölkt“
steht dort als zwei Bytes, \xc3\xb6, denn so reist Text durchs Netz, als UTF-8 wie in einer Datei.
Mit json.loads wird aus dem Rumpf ein Dictionary. Die Temperatur steht darin eine Ebene tiefer,
unter "aktuell".
Mit requests
# Der Wetterdienst für diesen Abschnitt. Die Maschine hier hat kein Netz, deshalb
# läuft der Dienst auf derselben Maschine wie dein Programm, unter der Adresse
# http://127.0.0.1:8000. Die Anfragen an ihn sind trotzdem echt: Sie gehen über
# HTTP hinaus und kommen als Antwort zurück, nur eben nicht durch das Internet.
# Hier musst du nichts ändern.
import hashlib
import json
import threading
from datetime import date, timedelta
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, urlparse
# Das Wetter am 09.10.2026 um 14 Uhr: Grad, Wetter, Wind in km/h.
AKTUELL = {
"Berlin": (14.2, "bewölkt", 12),
"Hamburg": (12.8, "Regen", 24),
"München": (9.6, "Nebel", 3),
"Köln": (15.1, "heiter", 8),
"Frankfurt am Main": (13.4, "bewölkt", 10),
"Stuttgart": (11.9, "Regen", 14),
"Hannover": (13.1, "bewölkt", 16),
"Nürnberg": (10.8, "Nebel", 4),
"Leipzig": (13.7, "heiter", 9),
"Dresden": (12.3, "bewölkt", 11),
"Erfurt": (12.6, "heiter", 7),
}
# Die Vorhersage ab dem 09.10.2026, je Tag: Höchstwert, Tiefstwert, Regen in mm
# und das Wetter.
VORHERSAGE = {
"Leipzig": [
(15.2, 6.1, 0.0, "heiter"),
(17.8, 7.4, 0.0, "sonnig"),
(18.4, 8.0, 0.0, "sonnig"),
(16.9, 9.2, 0.4, "bewölkt"),
(13.5, 8.8, 6.2, "Regen"),
(11.2, 7.5, 9.8, "Regen"),
(12.6, 5.9, 1.1, "bewölkt"),
],
"Dresden": [
(14.6, 5.2, 0.2, "bewölkt"),
(16.3, 6.8, 0.0, "heiter"),
(15.0, 8.1, 0.0, "bewölkt"),
(16.3, 7.7, 0.0, "heiter"),
(19.1, 8.4, 0.0, "sonnig"),
(12.4, 7.0, 5.1, "Regen"),
(10.8, 6.2, 7.6, "Regen"),
],
"Erfurt": [
(13.9, 4.8, 0.0, "heiter"),
(15.6, 5.5, 0.0, "sonnig"),
(14.1, 7.2, 2.3, "Regen"),
(12.0, 6.6, 4.0, "Regen"),
(12.8, 5.1, 0.6, "bewölkt"),
(14.7, 4.3, 0.0, "heiter"),
(13.3, 3.9, 0.0, "heiter"),
],
}
# Für diese Städte klappt etwas nicht, so wie es bei echten Diensten vorkommt.
STOERUNGEN = {
"Bremen": 500, # der Dienst hat selbst einen Fehler
"Kiel": 503, # der Dienst wird gerade gewartet
"Rostock": "keine Antwort", # er nimmt die Anfrage an und antwortet nie
"Lübeck": "Abbruch", # er legt auf, ohne zu antworten
}
# Die Vorhersage gibt es nur mit Schlüssel in der Kopfzeile X-Api-Key. Wie jeder
# ordentliche Dienst kennt dieser den Schlüssel nicht selbst, sondern nur eine
# Prüfsumme davon.
PRUEFSUMME = "0fe6daa0a6754b2239b7022999ec7a5ae2b20fc096afc6d9eead51d07bef68ce"
class Anfrage(BaseHTTPRequestHandler):
def do_GET(self):
adresse = urlparse(self.path)
werte = {name: liste[0] for name, liste in parse_qs(adresse.query).items()}
stadt = werte.get("stadt", "")
stoerung = STOERUNGEN.get(stadt)
if adresse.path not in ("/wetter", "/vorhersage"):
self.sende(404, {"fehler": f"Unbekannte Adresse: {adresse.path}"})
elif stoerung == "keine Antwort":
# Wartet auf etwas, das nie passiert. Der Dienst endet trotzdem mit
# deinem Programm, weil er im Hintergrund läuft.
threading.Event().wait()
elif stoerung == "Abbruch":
self.close_connection = True
elif stoerung is not None:
self.sende(stoerung, self.responses[stoerung][0])
elif adresse.path == "/wetter":
self.wetter(stadt)
else:
self.vorhersage(stadt, werte.get("tage", "7"))
def wetter(self, stadt):
if stadt not in AKTUELL:
self.sende(404, {"fehler": f"Unbekannte Stadt: {stadt}"})
return
grad, wetter, wind = AKTUELL[stadt]
aktuell = {"temperatur": grad, "wetter": wetter, "wind": wind}
self.sende(200, {"stadt": stadt, "zeit": "2026-10-09T14:00", "aktuell": aktuell})
def vorhersage(self, stadt, tage):
schluessel = self.headers.get("X-Api-Key", "")
if hashlib.sha256(schluessel.encode()).hexdigest() != PRUEFSUMME:
self.sende(401, {"fehler": "Ohne gültigen Schlüssel in X-Api-Key gibt es keine Vorhersage"})
elif stadt not in VORHERSAGE:
self.sende(404, {"fehler": f"Keine Vorhersage für: {stadt}"})
elif not tage.isdigit() or not 1 <= int(tage) <= 7:
self.sende(400, {"fehler": "tage geht von 1 bis 7"})
else:
liste = []
for nummer, (hoch, tief, regen, wetter) in enumerate(VORHERSAGE[stadt][: int(tage)]):
liste.append(
{
"datum": (date(2026, 10, 9) + timedelta(days=nummer)).isoformat(),
"wetter": wetter,
"hoechstwert": hoch,
"tiefstwert": tief,
"regen": regen,
}
)
self.sende(200, {"stadt": stadt, "tage": liste})
def sende(self, status, inhalt):
"""Schickt die Antwort: ein Dictionary als JSON, einen Text als Text."""
if isinstance(inhalt, str):
art, rumpf = "text/plain", inhalt
else:
art, rumpf = "application/json", json.dumps(inhalt, ensure_ascii=False)
daten = rumpf.encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", f"{art}; charset=utf-8")
self.send_header("Content-Length", str(len(daten)))
self.end_headers()
self.wfile.write(daten)
def log_message(self, *angaben):
# Sonst stünde jede Anfrage auf dem Fehlerkanal deines Programms.
pass
_server = None
def starten():
"""Startet den Dienst im Hintergrund. Er läuft, bis dein Programm endet."""
global _server
if _server is not None:
return
try:
_server = ThreadingHTTPServer(("127.0.0.1", 8000), Anfrage)
except OSError:
# Port 8000 ist belegt, weil der Dienst aus einem anderen Lauf noch
# antwortet. Dann fragt dein Programm eben den.
return
threading.Thread(target=_server.serve_forever, daemon=True).start() import requests
import wetterdienst
wetterdienst.starten()
ADRESSE = "http://127.0.0.1:8000"
antwort = requests.get(ADRESSE + "/wetter?stadt=Berlin", timeout=2)
print(antwort.status_code)
print(antwort.headers["Content-Type"])
print(antwort.text)
daten = antwort.json()
print(daten["aktuell"]["temperatur"]) Jetzt dasselbe mit dem Paket requests. requests.get schickt eine Anfrage mit dem Verfahren GET,
das heißt „gib mir“, und gibt die Antwort zurück. Der Status steht in status_code und die
Kopfzeilen in headers. text ist der Rumpf, schon als Text, das ö ist also wieder ein ö. Die
Methode json() macht daraus gleich das Dictionary.
Hier ist der Unterschied noch klein. Ein Import weniger, ein with weniger. Sobald es ernst wird,
wächst er, denn requests setzt Parameter für dich richtig in die Adresse, liest die Kodierung selbst
aus den Kopfzeilen und hat für jede Fehlerart einen eigenen Namen. Im Rest dieses Abschnitts kommt
deshalb nur noch requests vor.
requests ist ein Paket wie tabulate aus Abschnitt 13. Hier liegt es schon bereit, zu Hause
installierst du es mit python -m pip install requests.
Was wetterdienst.py tut
wetterdienst.py ist ein Server. Das ist ein Programm, das an einem Port wartet, bis eine Anfrage
kommt, und dann antwortet. Gebaut ist er mit http.server, das ebenfalls aus der Standardbibliothek
kommt. Wenn du hineinschaust, findest du ganz oben die Daten, die er ausliefert: das Wetter von elf
Städten, eine Vorhersage für drei und ein paar Städte, bei denen er absichtlich nicht mitspielt. Die
werden in der übernächsten Lektion gebraucht.
starten() lässt ihn im Hintergrund laufen, solange dein Programm läuft. Endet das Programm, ist
auch der Server weg. Wie man so einen Server selbst baut, und zwar bequemer als hier, kommt in
Abschnitt 18.
Niemand zu Hause
# Der Wetterdienst für diesen Abschnitt. Die Maschine hier hat kein Netz, deshalb
# läuft der Dienst auf derselben Maschine wie dein Programm, unter der Adresse
# http://127.0.0.1:8000. Die Anfragen an ihn sind trotzdem echt: Sie gehen über
# HTTP hinaus und kommen als Antwort zurück, nur eben nicht durch das Internet.
# Hier musst du nichts ändern.
import hashlib
import json
import threading
from datetime import date, timedelta
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, urlparse
# Das Wetter am 09.10.2026 um 14 Uhr: Grad, Wetter, Wind in km/h.
AKTUELL = {
"Berlin": (14.2, "bewölkt", 12),
"Hamburg": (12.8, "Regen", 24),
"München": (9.6, "Nebel", 3),
"Köln": (15.1, "heiter", 8),
"Frankfurt am Main": (13.4, "bewölkt", 10),
"Stuttgart": (11.9, "Regen", 14),
"Hannover": (13.1, "bewölkt", 16),
"Nürnberg": (10.8, "Nebel", 4),
"Leipzig": (13.7, "heiter", 9),
"Dresden": (12.3, "bewölkt", 11),
"Erfurt": (12.6, "heiter", 7),
}
# Die Vorhersage ab dem 09.10.2026, je Tag: Höchstwert, Tiefstwert, Regen in mm
# und das Wetter.
VORHERSAGE = {
"Leipzig": [
(15.2, 6.1, 0.0, "heiter"),
(17.8, 7.4, 0.0, "sonnig"),
(18.4, 8.0, 0.0, "sonnig"),
(16.9, 9.2, 0.4, "bewölkt"),
(13.5, 8.8, 6.2, "Regen"),
(11.2, 7.5, 9.8, "Regen"),
(12.6, 5.9, 1.1, "bewölkt"),
],
"Dresden": [
(14.6, 5.2, 0.2, "bewölkt"),
(16.3, 6.8, 0.0, "heiter"),
(15.0, 8.1, 0.0, "bewölkt"),
(16.3, 7.7, 0.0, "heiter"),
(19.1, 8.4, 0.0, "sonnig"),
(12.4, 7.0, 5.1, "Regen"),
(10.8, 6.2, 7.6, "Regen"),
],
"Erfurt": [
(13.9, 4.8, 0.0, "heiter"),
(15.6, 5.5, 0.0, "sonnig"),
(14.1, 7.2, 2.3, "Regen"),
(12.0, 6.6, 4.0, "Regen"),
(12.8, 5.1, 0.6, "bewölkt"),
(14.7, 4.3, 0.0, "heiter"),
(13.3, 3.9, 0.0, "heiter"),
],
}
# Für diese Städte klappt etwas nicht, so wie es bei echten Diensten vorkommt.
STOERUNGEN = {
"Bremen": 500, # der Dienst hat selbst einen Fehler
"Kiel": 503, # der Dienst wird gerade gewartet
"Rostock": "keine Antwort", # er nimmt die Anfrage an und antwortet nie
"Lübeck": "Abbruch", # er legt auf, ohne zu antworten
}
# Die Vorhersage gibt es nur mit Schlüssel in der Kopfzeile X-Api-Key. Wie jeder
# ordentliche Dienst kennt dieser den Schlüssel nicht selbst, sondern nur eine
# Prüfsumme davon.
PRUEFSUMME = "0fe6daa0a6754b2239b7022999ec7a5ae2b20fc096afc6d9eead51d07bef68ce"
class Anfrage(BaseHTTPRequestHandler):
def do_GET(self):
adresse = urlparse(self.path)
werte = {name: liste[0] for name, liste in parse_qs(adresse.query).items()}
stadt = werte.get("stadt", "")
stoerung = STOERUNGEN.get(stadt)
if adresse.path not in ("/wetter", "/vorhersage"):
self.sende(404, {"fehler": f"Unbekannte Adresse: {adresse.path}"})
elif stoerung == "keine Antwort":
# Wartet auf etwas, das nie passiert. Der Dienst endet trotzdem mit
# deinem Programm, weil er im Hintergrund läuft.
threading.Event().wait()
elif stoerung == "Abbruch":
self.close_connection = True
elif stoerung is not None:
self.sende(stoerung, self.responses[stoerung][0])
elif adresse.path == "/wetter":
self.wetter(stadt)
else:
self.vorhersage(stadt, werte.get("tage", "7"))
def wetter(self, stadt):
if stadt not in AKTUELL:
self.sende(404, {"fehler": f"Unbekannte Stadt: {stadt}"})
return
grad, wetter, wind = AKTUELL[stadt]
aktuell = {"temperatur": grad, "wetter": wetter, "wind": wind}
self.sende(200, {"stadt": stadt, "zeit": "2026-10-09T14:00", "aktuell": aktuell})
def vorhersage(self, stadt, tage):
schluessel = self.headers.get("X-Api-Key", "")
if hashlib.sha256(schluessel.encode()).hexdigest() != PRUEFSUMME:
self.sende(401, {"fehler": "Ohne gültigen Schlüssel in X-Api-Key gibt es keine Vorhersage"})
elif stadt not in VORHERSAGE:
self.sende(404, {"fehler": f"Keine Vorhersage für: {stadt}"})
elif not tage.isdigit() or not 1 <= int(tage) <= 7:
self.sende(400, {"fehler": "tage geht von 1 bis 7"})
else:
liste = []
for nummer, (hoch, tief, regen, wetter) in enumerate(VORHERSAGE[stadt][: int(tage)]):
liste.append(
{
"datum": (date(2026, 10, 9) + timedelta(days=nummer)).isoformat(),
"wetter": wetter,
"hoechstwert": hoch,
"tiefstwert": tief,
"regen": regen,
}
)
self.sende(200, {"stadt": stadt, "tage": liste})
def sende(self, status, inhalt):
"""Schickt die Antwort: ein Dictionary als JSON, einen Text als Text."""
if isinstance(inhalt, str):
art, rumpf = "text/plain", inhalt
else:
art, rumpf = "application/json", json.dumps(inhalt, ensure_ascii=False)
daten = rumpf.encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", f"{art}; charset=utf-8")
self.send_header("Content-Length", str(len(daten)))
self.end_headers()
self.wfile.write(daten)
def log_message(self, *angaben):
# Sonst stünde jede Anfrage auf dem Fehlerkanal deines Programms.
pass
_server = None
def starten():
"""Startet den Dienst im Hintergrund. Er läuft, bis dein Programm endet."""
global _server
if _server is not None:
return
try:
_server = ThreadingHTTPServer(("127.0.0.1", 8000), Anfrage)
except OSError:
# Port 8000 ist belegt, weil der Dienst aus einem anderen Lauf noch
# antwortet. Dann fragt dein Programm eben den.
return
threading.Thread(target=_server.serve_forever, daemon=True).start() import requests
import wetterdienst
# wetterdienst.starten()
ADRESSE = "http://127.0.0.1:8000"
antwort = requests.get(ADRESSE + "/wetter?stadt=Berlin", timeout=2)
print(antwort.status_code) Hier ist die Zeile mit starten() auskommentiert. An Port 8000 hört also niemand zu. Die Anfrage
geht trotzdem los, und zurück kommt ein sehr langer Traceback. Eigentlich sind es vier
hintereinander, weil der Fehler von Schicht zu Schicht weitergereicht wird, vom Betriebssystem über
urllib3, auf dem requests aufbaut, bis zu requests selbst.
Wichtig ist wie immer die letzte Zeile. Sie fängt mit requests.exceptions.ConnectionError an,
einem Fehler beim Verbinden. Ganz hinten steht der Grund, [Errno 111] Connection refused, die
Verbindung wurde also abgelehnt. Das Max retries exceeded davor klingt nach vielen Versuchen. Es
war aber nur einer. Die Meldung heißt einfach so.
Zu Hause bekommst du denselben Fehler, wenn ein Dienst gerade nicht läuft oder in der Adresse der falsche Port steht. Ohne Internet steht dort etwas anderes, nämlich dass sich der Name des Rechners nicht nachschlagen ließ, wie in Lektion 1.5. Mit solchen Fällen muss ein Programm rechnen, sobald es mit dem Netz arbeitet. Wie man sie abfängt, statt dass alles abbricht, kommt in der übernächsten Lektion.
Zum Mitnehmen
Ein Programm schickt eine Anfrage an eine Adresse und bekommt eine Antwort mit Statuscode, Kopfzeilen und Rumpf zurück. Bei einer Schnittstelle (API) steckt im Rumpf meistens JSON. urllib.request kann das ohne Paket, mit requests geht es bequemer, und gefragt wird hier wetterdienst.py auf derselben Maschine, weil die kein Netz hat.
Jetzt du
Basis Konto, kostenlosIm Editor änderst du die Beispiele dieser Lektion und lässt sie gleich laufen. So merkst du am schnellsten, ob es sitzt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.