Anleitung

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-StatusAusnahmeMeistens
400, 412, 413, 414GeocoderQueryErrorEin falscher Parameter, oder eine zu lange Abfrage.
401, 407GeocoderAuthenticationFailureEin fehlender oder falscher API-Schlüssel.
402GeocoderQuotaExceededDas bezahlte Kontingent ist aufgebraucht.
403GeocoderInsufficientPrivilegesVom öffentlichen Nominatim gesperrt, oder ein Schlüssel, der für diese Anfrage nicht erlaubt ist.
408, 503, 504GeocoderTimedOutDer Dienst ist überlastet oder hat nicht rechtzeitig geantwortet.
429GeocoderRateLimitedZu viele Anfragen; eine Unterklasse von GeocoderQuotaExceeded.
502GeocoderServiceErrorDer Dienst ist ausgefallen.
keine Antwort rechtzeitigGeocoderTimedOutDer 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=1 für das öffentliche Nominatim.
  • Jeder GeocoderServiceError wird wiederholt; eine Sperre mit 403 wird also noch zweimal im Abstand von 5 Sekunden gesendet.
  • Nach dem letzten Versuch gibt er None zurück und schreibt eine Warnung ins Log. Ein gesperrter Lauf füllt also die ganze Spalte mit None. Setzen Sie beim Testen swallow_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.