geopy: GeocoderInsufficientPrivileges, GeocoderRateLimited und ConfigurationError
Was jede Ausnahme von geopy bedeutet, warum der Nominatim-Geocoder manche User-Agents ablehnt, warum sich eine geocodierte Spalte mit None füllt, und Einstellungen für RateLimiter, die ein Skript in den Limits halten. Geprüft mit geopy 2.5.0.
Vom HTTP-Status zur Ausnahme
geopy macht aus jeder Antwort ausser 200 eine Ausnahme. Alle sind von GeocoderServiceError abgeleitet:
| HTTP-Status | Ausnahme | Meistens |
|---|---|---|
| 400, 412, 413, 414 | GeocoderQueryError | Ein falscher Parameter, oder eine zu lange Abfrage. |
| 401, 407 | GeocoderAuthenticationFailure | Ein fehlender oder falscher API-Schlüssel. |
| 402 | GeocoderQuotaExceeded | Das bezahlte Kontingent ist aufgebraucht. |
| 403 | GeocoderInsufficientPrivileges | Vom öffentlichen Nominatim gesperrt, oder ein Schlüssel, der für diese Anfrage nicht erlaubt ist. |
| 408, 503, 504 | GeocoderTimedOut | Der Dienst ist überlastet oder hat nicht rechtzeitig geantwortet. |
| 429 | GeocoderRateLimited | Zu viele Anfragen; eine Unterklasse von GeocoderQuotaExceeded. |
| 502 | GeocoderServiceError | Der Dienst ist ausgefallen. |
| keine Antwort rechtzeitig | GeocoderTimedOut | Der Standard-Timeout ist 1 Sekunde; übergeben Sie dem Geocoder timeout=10. |
Die Meldung eines Statusfehlers lautet etwa Non-successful status code 403.
ConfigurationError: «Using Nominatim with default or sample user_agent»
Für den öffentlichen Server nominatim.openstreetmap.org lehnt geopy den Standard-User-Agent (geopy/2.5.0) und die Beispiele aus seiner Dokumentation ab (my-application, my_app/1, my_user_agent/1.0, specify_your_app_name_here). Es wirft ConfigurationError, bevor es etwas sendet, weil solche Anfragen die Nutzungsrichtlinie verletzen und mit 403 oder 429 enden. Nennen Sie Ihre eigene App:
from geopy.geocoders import Nominatim
geolocator = Nominatim(user_agent="shop-locator/1.2 (admin@example.com)", timeout=10)
print(geolocator.geocode("Bundesplatz 3, Bern"))
Die Prüfung gilt nur für den öffentlichen Server. Mit einer anderen domain sendet geopy, was Sie setzen.
GeocoderInsufficientPrivileges: Non-successful status code 403
Beim öffentlichen Nominatim heisst das, dass der Server den Client gesperrt hat: kein richtiger User-Agent, mehr als eine Anfrage pro Sekunde, immer wieder dieselben Abfragen, oder Autovervollständigung. Die Einzelheiten stehen in Nominatim: «Access blocked», 403 und 429. Andere Dienste antworten mit 403 bei einem Schlüssel, der von diesem Ort aus nicht erlaubt ist, etwa einem auf Websites beschränkten Google-Schlüssel, der von einem Server aus verwendet wird.
Ein Skript, das auf Ihrem Computer funktioniert und auf einem Cloud-Host (Streamlit Cloud, Colab, ein geteilter CI-Runner) 403 erhält, teilt seine IP-Adresse womöglich mit anderen Clients, die gesperrt wurden.
GeocoderRateLimited: 429
Der Dienst will weniger Anfragen. Sein Header Retry-After steht, wenn er gesendet wird, in e.retry_after. Warten Sie mindestens so lange, und machen Sie dann langsamer weiter.
RateLimiter, und warum jede Zeile None ist
RateLimiter setzt Pausen zwischen die Aufrufe. Seine Standardwerte (geopy 2.5.0) sind min_delay_seconds=0.0, max_retries=2, error_wait_seconds=5.0, swallow_exceptions=True und return_value_on_exception=None:
- Mit der Standardpause von 0 verlangsamt er nichts. Setzen Sie
min_delay_seconds=1für das öffentliche Nominatim. - Jeder
GeocoderServiceErrorwird wiederholt; eine Sperre mit 403 wird also noch zweimal im Abstand von 5 Sekunden gesendet. - Nach dem letzten Versuch gibt er
Nonezurück und schreibt eine Warnung ins Log. Ein gesperrter Lauf füllt also die ganze Spalte mitNone. Setzen Sie beim Testenswallow_exceptions=False, damit der erste Fehler den Lauf stoppt.
import pandas as pd
from geopy.extra.rate_limiter import RateLimiter
geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1, swallow_exceptions=False)
df = pd.DataFrame({"address": ["Bundesplatz 3, Bern", "Rue du Rhône 42, Genève"]})
unique = df["address"].drop_duplicates() # jede Adresse einmal
found = {a: geocode(a) for a in unique}
df["lat"] = df["address"].map(lambda a: found[a].latitude if found[a] else None)
df["lon"] = df["address"].map(lambda a: found[a].longitude if found[a] else None)
Mit einer Anfrage pro Sekunde brauchen 10'000 Adressen fast drei Stunden, und die Nutzungsrichtlinie rät vom Geocoding grösserer Mengen auf dem öffentlichen Server ab.
None ohne Fehler
geocode() gibt None zurück, wenn der Dienst nichts gefunden hat. Dienste auf OpenStreetMap-Daten kennen nur die dort erfassten Hausnummern; versuchen Sie Strasse und Ort allein, prüfen Sie auf ein überflüssiges Land oder eine Postleitzahl, und lassen Sie Wohnungsnummern und c/o-Zeilen weg.
Einen anderen Server verwenden
Die Geocoder Nominatim und Photon von geopy nehmen eine domain. Mit overpass.ch kommt der Schlüssel dort hinein, und die Antworten behalten ihr Format:
from geopy.geocoders import Nominatim, Photon
KEY = "IHR_SCHLÜSSEL"
nominatim = Nominatim(domain=f"api.overpass.ch/v1/{KEY}/nominatim", user_agent="shop-locator/1.2", timeout=10)
photon = Photon(domain=f"api.overpass.ch/v1/{KEY}/geocode", user_agent="shop-locator/1.2", timeout=10)
print(nominatim.geocode("Bundesplatz 3, Bern", addressdetails=True).raw["address"])
print(photon.reverse("47.3769, 8.5417"))
Fehler von overpass.ch werden zu denselben Ausnahmen: 401 GeocoderAuthenticationFailure, 403 GeocoderInsufficientPrivileges, 429 GeocoderRateLimited. Die Tarife erlauben weit mehr als eine Anfrage pro Sekunde; siehe die Preise.
Geocoding von overpass.ch
Die APIs von Nominatim und Photon auf OpenStreetMap-Daten, jede Nacht mit geopy getestet. 100'000 Anfragen im Monat sind kostenlos, und Sie dürfen die Ergebnisse speichern.