mitmario.dev

Wie ein Programm fragt

Python Sandbox 5 Min Lesezeit 3 BeispieleLektion 1 von 5

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 immer https://, die verschlüsselte Fassung.
  • 127.0.0.1 ist der Rechner. Diese Nummer heißt immer „dieser Rechner selbst“, und localhost ist ein anderer Name dafür. Sonst steht dort ein Name wie api.example.com.
  • :8000 ist der Port. Auf einem Rechner können viele Dienste laufen, und jeder lauscht an seiner eigenen Nummer. Fehlt sie, gilt bei https die 443 und bei http die 80.
  • /wetter ist der Pfad, also das, was man von dem Dienst will.
  • ?stadt=Berlin sind 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

Eine Anfrage mit urllib
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

Dieselbe Anfrage mit requests
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

Niemand zu Hause
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, kostenlos

Im 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.