httpx-Installation und Beispielanwendung¶
Installation¶
Für die Kommunikation mit solchen REST-APIs ist die httpx-Bibliothek hilfreich. Mit Spack könnt ihr httpx in eurem Kernel bereitstellen:
$ spack env activate python-311
$ spack install py-httpx
Alternativ könnt ihr httpx auch mit anderen Paketmanagern installieren, z.B.
$ uv add httpx
Beispiel OSM Nominatim-API¶
In diesem Beispiel holen wir unsere Daten von der OpenStreetMap Nominatim-API. Diese ist erreichbar über die URL https://nominatim.openstreetmap.org/search?
. Um z.B. Informationen über das Berlin Congress Center in Berlin im JSON-Format zu erhalten, sollte die URL https://nominatim.openstreetmap.org/search.php?q=Alexanderplatz+Berlin&format=json
angegeben werden, und wenn ihr euch den entsprechenden Kartenausschnitt anzeigen
lassen wollt, so müsst ihr einfach nur &format=json
weglassen
Anschließend definieren wir die Such-URL und die Parameter. Nominatim erwartet mindestens die folgenden beiden Parameter
Schlüssel |
Werte |
---|---|
|
Adressabfrage, die folgende Spezifikationen erlaubt: |
|
Format, in dem die Daten zurückgegeben werden. Möglich Werte sind |
Die Abfrage kann dann gestellt werden mit:
[1]:
import httpx
search_url = "https://nominatim.openstreetmap.org/search?"
params = {
"q": "Alexanderplatz, Berlin",
"format": "json",
}
r = httpx.get(search_url, params=params)
[2]:
r.status_code
[2]:
200
[3]:
r.json()
[3]:
[{'place_id': 159000335,
'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright',
'osm_type': 'way',
'osm_id': 783052052,
'lat': '52.5219814',
'lon': '13.413635717448294',
'class': 'place',
'type': 'square',
'place_rank': 25,
'importance': 0.47149825263735834,
'addresstype': 'square',
'name': 'Alexanderplatz',
'display_name': 'Alexanderplatz, Mitte, Berlin, 10178, Deutschland',
'boundingbox': ['52.5201457', '52.5238113', '13.4103097', '13.4160801']},
{'place_id': 159254539,
'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright',
'osm_type': 'node',
'osm_id': 3908141014,
'lat': '52.5215661',
'lon': '13.4112804',
'class': 'railway',
'type': 'station',
'place_rank': 30,
'importance': 0.43609907778808027,
'addresstype': 'railway',
'name': 'Alexanderplatz',
'display_name': 'Alexanderplatz, Dircksenstraße, Mitte, Berlin, 10179, Deutschland',
'boundingbox': ['52.5165661', '52.5265661', '13.4062804', '13.4162804']},
{'place_id': 159367604,
'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright',
'osm_type': 'way',
'osm_id': 346206374,
'lat': '52.5216214',
'lon': '13.4131913',
'class': 'highway',
'type': 'pedestrian',
'place_rank': 26,
'importance': 0.10000999999999993,
'addresstype': 'road',
'name': 'Alexanderplatz',
'display_name': 'Alexanderplatz, Mitte, Berlin, 10178, Deutschland',
'boundingbox': ['52.5216214', '52.5216661', '13.4131913', '13.4131914']},
{'place_id': 159038218,
'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright',
'osm_type': 'way',
'osm_id': 301241483,
'lat': '52.5227066',
'lon': '13.415336',
'class': 'highway',
'type': 'primary',
'place_rank': 26,
'importance': 0.10000999999999993,
'addresstype': 'road',
'name': 'Alexanderstraße',
'display_name': 'Alexanderstraße, Mitte, Berlin, 10178, Deutschland',
'boundingbox': ['52.5226699', '52.5227698', '13.4152008', '13.4154146']}]
Es werden drei verschiedene Orte gefunden, der Platz, eine Bushaltestelle und ein Hotel. Um nun weiter filtern zu können, können wir uns nur den bedeutendsten Ort anzeigen lassen:
[4]:
params = {"q": "Alexanderplatz, Berlin", "format": "json", "limit": "1"}
r = httpx.get(search_url, params=params)
r.json()
[4]:
[{'place_id': 159000335,
'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright',
'osm_type': 'way',
'osm_id': 783052052,
'lat': '52.5219814',
'lon': '13.413635717448294',
'class': 'place',
'type': 'square',
'place_rank': 25,
'importance': 0.47149825263735834,
'addresstype': 'square',
'name': 'Alexanderplatz',
'display_name': 'Alexanderplatz, Mitte, Berlin, 10178, Deutschland',
'boundingbox': ['52.5201457', '52.5238113', '13.4103097', '13.4160801']}]
Clean Code¶
Nachdem wir nun wissen, dass der Code funktioniert, wollen wir alles in eine saubere und flexible Funktion umwandeln.
Um sicherzustellen, dass die Interaktion erfolgreich war, verwenden wir die Methode raise_for_status
von httpx
, die eine Exception auslöst, wenn der HTTP-Statuscode nicht 200 OK
ist:
[5]:
r.raise_for_status()
[5]:
<Response [200 OK]>
Da wir die Lastgrenzen der Nominatim-API nicht überschreiten möchten, werden wir unsere Anforderungen mit der Funktion time.sleep
verzögern:
[6]:
from time import sleep
sleep(1)
r.json()
[6]:
[{'place_id': 159000335,
'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright',
'osm_type': 'way',
'osm_id': 783052052,
'lat': '52.5219814',
'lon': '13.413635717448294',
'class': 'place',
'type': 'square',
'place_rank': 25,
'importance': 0.47149825263735834,
'addresstype': 'square',
'name': 'Alexanderplatz',
'display_name': 'Alexanderplatz, Mitte, Berlin, 10178, Deutschland',
'boundingbox': ['52.5201457', '52.5238113', '13.4103097', '13.4160801']}]
Als nächstes deklarieren wir die Funktion selbst. Als Argumente benötigen wir die Adresse, das Format, das Limit der zurückzugebenden Objekte mit dem Standardwert 1
und weitere kwargs
(keyword arguments), die als Parameter übergeben werden:
[7]:
def nominatim_search(address, format="json", limit=1, **kwargs):
"""Thin wrapper around the Nominatim search API.
For the list of parameters see
https://nominatim.org/release-docs/develop/api/Search/#parameters
"""
search_url = "https://nominatim.openstreetmap.org/search?"
params = {"q": address, "format": format, "limit": limit, **kwargs}
r = httpx.get(search_url, params=params)
# Raise an exception if the status is unsuccessful
r.raise_for_status()
sleep(1)
return r.json()
Nun können wir die Funktion ausprobieren, z.B. mit
[8]:
nominatim_search("Alexanderplatz, Berlin")
[8]:
[{'place_id': 159000335,
'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright',
'osm_type': 'way',
'osm_id': 783052052,
'lat': '52.5219814',
'lon': '13.413635717448294',
'class': 'place',
'type': 'square',
'place_rank': 25,
'importance': 0.47149825263735834,
'addresstype': 'square',
'name': 'Alexanderplatz',
'display_name': 'Alexanderplatz, Mitte, Berlin, 10178, Deutschland',
'boundingbox': ['52.5201457', '52.5238113', '13.4103097', '13.4160801']}]
Caching¶
Falls innerhalb einer Session immer wieder dieselben Abfragen gestellt werden sollen,ist es sinnvoll, diese Daten nur einmal abzurufen und wiederzuverwenden. In Python können wir lru_cache
aus der functools
-Standardbibliothek von Python verwenden. lru_cache
speichert die N letzten Anfragen (Least Recent Used) und sobald das Limit überschritten wird, werden die ältesten Werte verworfen. Um dies für die Methode nominatim_search
zu verwenden, müsst ihr lediglich
einen Import und einen Decorator defnieren:
[9]:
from functools import lru_cache
@lru_cache(maxsize=1000)
def nominatim_search(address, format="json", limit=1, **kwargs):
"""…"""
lru_cache
speichert die Ergebnisse jedoch nur während einer Session. Wenn ein Skript wegen einem Timeout oder einer Exception beendet wird, sind die Ergebnisse verloren. Sollen die Daten dauerhafter gespeichert werden, können Tools wie joblib oder python-diskcache verwendet werden.