Géocoder des adresses françaises en Python avec l'API de la Géoplateforme (ex-API Adresse)

Pour placer des clients sur une carte, calculer des distances ou rattacher une adresse à sa commune, il faut d'abord la géocoder, c'est-à-dire la convertir en latitude et longitude. En France, le service public de référence s'appuie sur la Base adresse nationale (BAN). Il a changé d'adresse : l'ancienne API api-adresse.data.gouv.fr est dépréciée au profit du service de géocodage de la Géoplateforme de l'IGN, sur data.geopf.fr/geocodage. Il est gratuit et sans clé. Voici comment l'utiliser en Python, adresse par adresse puis pour un fichier entier.
Exemples exécutés le 9 novembre 2026 avec Python 3.13, requests 2.34 et pandas 3.0.5 ; les sorties sont réelles. Les blocs s'enchaînent dans un même fichier.
Ce qui a changé avec la Géoplateforme
La page de documentation d'adresse.data.gouv.fr annonce que l'API Adresse BAN est dépréciée et intégrée au service de géocodage de la Géoplateforme, et que l'URL api-adresse.data.gouv.fr devait être décommissionnée fin janvier 2026. Au moment de nos tests, elle répondait encore, mais il serait imprudent d'en dépendre. La migration est simple : les paramètres (q, limit, postcode, citycode, type…) et le format de réponse GeoJSON sont les mêmes ; seule l'URL de base change, et le service ajoute un paramètre index pour choisir entre adresses (address), points d'intérêt (poi) et parcelles cadastrales (parcel).
D'après la documentation de la Géoplateforme, l'usage est limité à 50 requêtes par seconde depuis une même adresse IP. Au-delà, le service répond par une erreur 429 et bloque les appels suivants tant que le rythme ne baisse pas.
Géocoder une adresse
Le point d'entrée /search prend l'adresse en texte libre dans q. On limite la réponse au meilleur résultat et on restreint la recherche aux adresses avec index=address :
import requests
BASE = "https://data.geopf.fr/geocodage"
def geocoder(adresse: str, code_postal: str | None = None) -> dict | None:
params = {"q": adresse, "limit": 1, "index": "address"}
if code_postal:
params["postcode"] = code_postal
r = requests.get(f"{BASE}/search", params=params, timeout=10)
r.raise_for_status()
features = r.json()["features"]
if not features:
return None
f = features[0]
lon, lat = f["geometry"]["coordinates"]
p = f["properties"]
return {"label": p["label"], "score": round(p["score"], 2), "type": p["type"],
"citycode": p["citycode"], "lat": lat, "lon": lon}
print(geocoder("20 avenue de Ségur, Paris"))
print(geocoder("1 place de la mairie", code_postal="01000"))
print(geocoder("rue qui n'existe pas du tout zzz"))
{'label': '20 Avenue de Ségur 75007 Paris', 'score': 0.97, 'type': 'housenumber', 'citycode': '75107', 'lat': 48.850699, 'lon': 2.308628}
{'label': '1 Place de la Mairie 01000 Saint-Denis-lès-Bourg', 'score': 0.95, 'type': 'housenumber', 'citycode': '01344', 'lat': 46.202453, 'lon': 5.18974}
None
Quelques remarques sur ces résultats :
- Les coordonnées GeoJSON sont dans l'ordre longitude, latitude, l'inverse de l'usage courant. Le déballage
lon, lat = …évite l'erreur classique qui place Paris au large de la Somalie. - Le score, entre 0 et 1, mesure la proximité entre la saisie et l'adresse trouvée. Au-dessus de 0,8 environ, le résultat est en général fiable ; en dessous de 0,5, il faut le vérifier à la main. Le seuil dépend de vos données : contrôlez un échantillon avant de le fixer.
- Le type indique la précision :
housenumber(numéro dans la rue),street(rue sans numéro trouvé),locality(lieu-dit) oumunicipality(commune seulement). Une adresse géocodée « à la commune » n'est pas fausse, mais elle est imprécise. - Pour Paris, Lyon et Marseille,
citycodeest le code de l'arrondissement (75107 pour le 7e) et non celui de la commune (75056). Pensez-y avant une jointure avec un fichier de communes.
Le deuxième exemple montre un autre piège : le code postal 01000 couvre plusieurs communes, et la « place de la Mairie » trouvée est celle de Saint-Denis-lès-Bourg, pas celle de Bourg-en-Bresse. Un code postal n'identifie pas une commune ; pour lever l'ambiguïté, passez le code commune Insee dans le paramètre citycode, ou ajoutez le nom de la commune dans q. Enfin, une adresse introuvable renvoie une liste vide, que la fonction traduit par None.
Le géocodage inverse
Le point d'entrée /reverse fait le chemin inverse : à partir de coordonnées, il renvoie l'adresse la plus proche.
r = requests.get("https://data.geopf.fr/geocodage/reverse",
params={"lon": 2.308628, "lat": 48.850699, "limit": 1, "index": "address"}, timeout=10)
r.raise_for_status()
p = r.json()["features"][0]["properties"]
print(p["label"], "|", p["type"], "|", p["citycode"])
20 Avenue de Ségur 75007 Paris | housenumber | 75107
C'est utile pour afficher une adresse lisible à partir d'une position GPS (un relevé terrain, une photo géolocalisée) ou pour retrouver la commune d'un point. Si vous n'avez besoin que de la commune, lisez simplement les propriétés city et citycode de la réponse.
Géocoder un fichier CSV en un seul appel
Pour des centaines ou des milliers d'adresses, ne bouclez pas sur /search : le point d'entrée /search/csv reçoit un fichier CSV et renvoie le même fichier enrichi de colonnes result_…, en une seule requête. On indique la ou les colonnes qui composent l'adresse avec columns, et éventuellement la colonne du code postal avec postcode :
import io
import pandas as pd
adresses = pd.DataFrame({
"id": [1, 2, 3],
"adresse": ["20 avenue de Ségur", "1 place de la Mairie", "8 boulevard du Port"],
"code_postal": ["75007", "01000", "95000"],
})
fichier = adresses.to_csv(index=False).encode("utf-8")
r = requests.post(
"https://data.geopf.fr/geocodage/search/csv",
files={"data": ("adresses.csv", fichier, "text/csv")},
data={"columns": ["adresse"], "postcode": "code_postal", "indexes": "address"},
timeout=60,
)
r.raise_for_status()
resultat = pd.read_csv(io.StringIO(r.text), dtype={"code_postal": "string", "result_citycode": "string"})
print(resultat[["id", "result_label", "result_score", "result_status"]].to_string(index=False))
id result_label result_score result_status
1 20 Avenue de Ségur 75007 Paris 0.971628 ok
2 1 Place de la Mairie 01000 Saint-Denis-lès-Bourg 0.954228 ok
3 8 Boulevard du Port 95000 Cergy 0.970895 ok
Le fichier renvoyé contient notamment latitude, longitude, result_score, result_type, result_citycode et result_status (ok, not-found ou skipped). Comme toujours avec les fichiers français, relisez les codes en texte : sinon le code postal 01000 redevient 1000. Ce point est détaillé dans notre article sur la lecture des CSV français avec pandas.
Pour un fichier volumineux, découpez-le en lots de quelques milliers de lignes, envoyez-les l'un après l'autre et gardez les résultats intermédiaires sur disque, de façon à pouvoir reprendre en cas d'interruption. Le site adresse.data.gouv.fr propose aussi un géocodeur CSV en ligne, pratique pour un fichier ponctuel sans écrire de code.
Boucler sans dépasser la limite
Si vous devez tout de même appeler /search en boucle (autocomplétion, adresses arrivant au fil de l'eau), espacez les requêtes et gérez l'erreur 429. Une session requests réutilise la connexion HTTPS, ce qui accélère nettement les appels successifs :
import time
session = requests.Session()
def geocoder_poli(adresses, pause=0.05):
"""Une requête toutes les 50 ms au plus : 20 par seconde, bien sous la limite de 50."""
for adresse in adresses:
for essai in range(3):
r = session.get("https://data.geopf.fr/geocodage/search",
params={"q": adresse, "limit": 1}, timeout=10)
if r.status_code == 429: # trop de requêtes : on attend et on réessaie
time.sleep(2 ** essai)
continue
r.raise_for_status()
break
feats = r.json()["features"]
yield adresse, feats[0]["properties"]["label"] if feats else None
time.sleep(pause)
for a, label in geocoder_poli(["10 rue de Rivoli Paris", "Place Bellecour Lyon"]):
print(a, "->", label)
10 rue de Rivoli Paris -> 10 Rue de Rivoli 75004 Paris
Place Bellecour Lyon -> Place Bellecour 69002 Lyon
Avec une pause de 50 millisecondes, le script reste à environ 20 requêtes par seconde, bien en dessous de la limite. En cas de 429, il attend 1, 2 puis 4 secondes avant de réessayer. N'exécutez pas plusieurs copies de ce script en parallèle depuis la même machine : la limite s'applique à l'adresse IP, pas au processus.
Bonnes pratiques
- Nettoyez les adresses avant d'envoyer : une colonne « complément » (bâtiment, étage, BP) mélangée à la rue fait baisser le score. Envoyez la ligne de voie et la commune, pas l'adresse postale complète.
- Gardez le score et le type dans vos données, pas seulement les coordonnées : ils permettent de filtrer après coup les résultats douteux.
- Mettez en cache les adresses déjà géocodées : une même adresse revient souvent dans un fichier de commandes ou de clients.
- Vérifiez les identifiants associés : si vos adresses sont celles d'établissements, contrôlez aussi leurs numéros SIRET, l'API Recherche d'entreprises renvoyant déjà l'adresse et les coordonnées du siège.
Les données de la Base adresse nationale sont publiées sous licence ouverte. Si vous diffusez des coordonnées issues du géocodage, citez la source (Base adresse nationale, via le service de géocodage de la Géoplateforme).
Sources
- IGN, cartes.gouv.fr, documentation du service de géocodage de la Géoplateforme (points d'entrée, données BAN, limite de 50 requêtes par seconde et par IP).
- Base adresse nationale, documentation de l'API Adresse (dépréciation de api-adresse.data.gouv.fr, géocodeur CSV).
Tutoriels en françaisLire un CSV français avec pandas
Point-virgule, virgule décimale, espaces insécables, Windows-1252, codes postaux à zéro initial : les réglages de read_csv qui marchent.
Tutoriels en françaisVérifier un SIRET ou un SIREN
Clé de Luhn, exception de La Poste, numéro de TVA et vérification en ligne : le contrôle complet en quelques lignes de Python.
Tutoriels en françaisJours fériés et jours ouvrés 2026-2030
Les 11 jours fériés (13 en Alsace-Moselle) calculés depuis Pâques, vérifiés avec l'API officielle, et le nombre de jours ouvrés par année.