mitmario.dev

Synthese: Abholen und speichern

Python Sandbox 5 Min Lesezeit 4 BeispieleLektion 4 von 5

Ein Programm, das jeden Morgen die Vorhersage holt und als Tabelle ablegt, ist die Sorte Werkzeug, für die man Python lernt. Bis dahin fehlen noch zwei Dinge. Echte Dienste wollen fast immer wissen, wer fragt, und dafür gibt es Schlüssel. Und was abgeholt ist, soll bleiben. Also kommt es in eine Datei.

Ein Schlüssel für den Dienst

Ohne Schlüssel
import requests

import wetterdienst

wetterdienst.starten()
ADRESSE = "http://127.0.0.1:8000"

antwort = requests.get(ADRESSE + "/vorhersage", params={"stadt": "Erfurt", "tage": 3}, timeout=2)
print(antwort.status_code, antwort.text)
antwort.raise_for_status()
print("Hier kommt das Programm nicht mehr hin.")

Das aktuelle Wetter bekommt beim Wetterdienst jeder, die Vorhersage nicht. Ohne Schlüssel kommt Status 401 zurück, und im Rumpf steht, was fehlt, nämlich ein gültiger Schlüssel in der Kopfzeile X-Api-Key. Mit raise_for_status() wird daraus ein HTTPError, 401 Client Error: Unauthorized. Das Programm endet also wie in der letzten Lektion an der richtigen Stelle, mit einer Meldung, in der der Status steht.

So ein Schlüssel (API key) ist ein langer Text aus Buchstaben und Ziffern. Man bekommt ihn, wenn man sich beim Anbieter anmeldet, und am Schlüssel erkennt der Dienst, wer fragt. Davon hängt ab, wie oft man fragen darf und manchmal auch, was es kostet. Wer Unfug treibt, dem wird der Schlüssel gesperrt.

Meistens geht der Schlüssel in einer Kopfzeile der Anfrage mit, so wie hier. Manchmal steht er auch als Parameter in der Adresse. Die Kopfzeile ist die bessere Wahl, weil eine Adresse an vielen Stellen landet, etwa in den Protokollen des Dienstes oder in der Meldung von raise_for_status() oben, in der die ganze Adresse ausgeschrieben ist.

Der Schlüssel gehört nicht in den Code

Der Schlüssel aus der Umgebung
import os

import requests

import wetterdienst

wetterdienst.starten()
ADRESSE = "http://127.0.0.1:8000"

schluessel = os.environ["WETTER_SCHLUESSEL"]
antwort = requests.get(
    ADRESSE + "/vorhersage",
    params={"stadt": "Erfurt", "tage": 3},
    headers={"X-Api-Key": schluessel},
    timeout=2,
)
antwort.raise_for_status()
for tag in antwort.json()["tage"]:
    print(tag["datum"], tag["wetter"], tag["hoechstwert"])

Am einfachsten wäre es, den Schlüssel direkt in main.py zu schreiben. Dann steckt er aber in jeder Kopie der Datei: in der, die du einer Kollegin schickst, in der, die du in einem Forum zeigst, weil etwas nicht klappt, und in jeder Sicherung. Wer ihn findet, fragt auf deine Rechnung. Sicherer ist die Annahme, dass jede Datei mit Code irgendwann jemand anderes liest. Meistens stimmt das auch.

Der Schlüssel kommt deshalb aus einer Umgebungsvariablen (environment variable). Das sind Werte, die das Betriebssystem einem Programm beim Start mitgibt, jeden unter einem Namen. In Python stehen sie in os.environ, einem Dictionary aus dem Modul os, und os.environ["WETTER_SCHLUESSEL"] ist der Wert unter diesem Namen. Im Code steht also nur der Name. Der Schlüssel selbst liegt auf dem Rechner, auf dem das Programm läuft.

Wenn du dieses Beispiel mit Sandbox starten laufen lässt, ist die Variable auf der Maschine schon gesetzt. Im Terminal ist sie es nicht, wie auf jedem Rechner, auf dem das noch niemand getan hat. Dort gibt der erste Befehl der Befehlsleiste, WETTER_SCHLUESSEL=… python main.py, die Variable genau diesem einen Befehl mit. Am zweiten, python main.py, siehst du, was ohne sie passiert.

Die Kopfzeile geht genauso als Dictionary mit wie die Parameter: headers={"X-Api-Key": schluessel}. Wie sie heißen muss, steht in der Beschreibung des Dienstes. Da steht auch, ob er den Schlüssel überhaupt auf diesem Weg haben will.

Eine Variable, die fehlt

Eine Variable, die fehlt
import os

import requests

import wetterdienst

wetterdienst.starten()
ADRESSE = "http://127.0.0.1:8000"

schluessel = os.environ["WETTER_SCHLUESSEL"]
print("Den Schlüssel gibt es, die Anfrage kann losgehen.")

Das ist der erste Fehler, den du zu Hause zu sehen bekommst. Die Variable ist nicht gesetzt, und das Programm endet mit KeyError: 'WETTER_SCHLUESSEL', wie bei jedem Dictionary, in dem ein Schlüssel fehlt.

So soll es auch sein. Ein klarer Abbruch am Anfang ist besser als eine Anfrage ohne Schlüssel und ein 401 später. Mit os.environ.get(...) gäbe es dagegen None, requests ließe die Kopfzeile einfach weg, und der Fehler käme erst beim Dienst an.

Zu Hause setzt du die Variable im Terminal, bevor du das Programm startest. Unter Windows geht das in der PowerShell mit $env:WETTER_SCHLUESSEL = "…", auf dem Mac und unter Linux mit export WETTER_SCHLUESSEL=…. Das gilt für dieses eine Fenster, bis es geschlossen wird. Dauerhaft geht es über die Systemeinstellungen von Windows oder über die Datei, die dein Terminal beim Start liest, auf dem Mac meistens ~/.zshrc.

Abholen und aufheben

Abholen und aufheben
import json

import requests

import wetterdienst

wetterdienst.starten()
ADRESSE = "http://127.0.0.1:8000"

wetter = {}
for stadt in ["Berlin", "Hamburg", "Stuttgart", "Nürnberg"]:
    antwort = requests.get(ADRESSE + "/wetter", params={"stadt": stadt}, timeout=2)
    antwort.raise_for_status()
    wetter[stadt] = antwort.json()["aktuell"]
with open("wetter.json", "w", encoding="utf-8") as datei:
    json.dump(wetter, datei, indent=2, ensure_ascii=False)

# Ab hier braucht das Programm den Dienst nicht mehr.
with open("wetter.json", encoding="utf-8") as datei:
    gespeichert = json.load(datei)
regen = [stadt for stadt, aktuell in gespeichert.items() if aktuell["wetter"] == "Regen"]
print(f"Regen in {len(regen)} von {len(gespeichert)} Städten:", ", ".join(regen))

Was einmal abgeholt ist, muss man nicht noch einmal holen. Hier wird das Wetter in vier Städten abgefragt. Die Antworten landen in einem Dictionary, und das wird mit json.dump aus Lektion 12.5 als wetter.json abgelegt. Alles danach liest nur noch die Datei und sucht die Städte, in denen es regnet. Zwei von vier.

Der zweite Teil liefe genauso, wenn der Dienst inzwischen nicht mehr erreichbar wäre. Was abgelegt ist, zeigt cat wetter.json in der Befehlsleiste.

Bequemlichkeit ist nur ein Grund dafür. Viele Dienste begrenzen, wie oft man am Tag fragen darf, und wenn ein Programm bei jedem Probelauf neu fragt, ist man schnell darüber. Eine abgelegte Antwort lässt sich außerdem am nächsten Tag mit der neuen vergleichen. Und wer lieber in einer Tabelle nachsieht, schreibt die Daten als CSV wie in Lektion 12.3. Das passiert in der Aufgabe mit der Vorhersage.

Zu Hause

Auf deinem Rechner fallen wetterdienst.starten() und der Import weg, und in ADRESSE steht die Adresse eines echten Dienstes. Den Wetterdienst hier gibt es nur in diesem Kurs. Echte Dienste haben ihre eigenen Adressen, Parameter und Antworten, und die stehen in ihrer Beschreibung.

Ein Beispiel ist Bright Sky unter https://api.brightsky.dev. Dort gibt es die offenen Daten des Deutschen Wetterdienstes als JSON, kostenlos und ohne Schlüssel. Gefragt wird nicht mit einem Stadtnamen, sondern mit Koordinaten (lat und lon), und die Antwort sieht anders aus als hier. Der Weg bleibt aber derselbe: Beschreibung lesen, eine Anfrage mit timeout, erst den Status, dann die Daten, und zum Ansehen einmal json.dumps mit indent=2.

Bevor ein Programm jeden Tag von selbst fragt, lohnt noch ein Blick in die Nutzungsbedingungen. Da steht, wie oft man fragen darf und wofür man die Daten verwenden darf. Bei manchen Diensten steht dort auch, dass man sie nennen muss, wenn man die Daten weitergibt.

Was die Aufgabe will

Zum Schluss kommt der ganze Abschnitt in einem Programm zusammen. Es geht von den Einstellungen in einer Datei über den Schlüssel aus der Umgebung bis zur Tabelle am Ende, und die CSV, die dabei entsteht, kann danach auch eine Tabellenkalkulation lesen.

Zum Mitnehmen

Viele Dienste wollen einen Schlüssel (API key) sehen und antworten ohne ihn mit Status 401. Im Programm kommt der Schlüssel über os.environ aus einer Umgebungsvariablen und geht in einer Kopfzeile mit, nicht in der Adresse. Abgeholte Daten landen als JSON zum Weiterrechnen oder als CSV für die Tabellenkalkulation in einer Datei, und zu Hause steht statt des Wetterdienstes die Adresse eines echten Dienstes im Code.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

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 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.