blog.bressure.net

Carnet professionnel d'un informaticien

Application, Langage

Liste des codes postaux et codes insee des communes

admin
Map showing the communes of Metropolitan Franc...
Image via Wikipedia

Le terme commune est utilisé de manière assez générique en France. Pour ma part je préfère considérer qu’une commune est la plus petite division administrative c’est-à-dire qu’une commune correspond à une mairie. Les communes de France sont identifiées par l’INSEE, organisme public, par un code. La liste des codes INSEE et donc des communes est disponible sur le site de l’INSEE dans la rubrique téléchargement des Code Officiels Géographiques. Le fichier intitulé Liste des communes existantes au 1er janvier 2011 nous intéresse ici.

Par ailleurs si les codes INSEE constituent une nomenclature officielle, ils sont aussi réservés aux initiés (statisticiens, cartographes…). Le grand public ne connait des communes que leur code postal. Malheureusement la notion de code postal ne correspond pas à la notion de commune. En effet sur une commune il peut y avoir plusieurs codes postaux. Un code postal peut par exemple se justifier quand une localité est éloignée du centre ville ou dans le cas d’arrondissement de grande ville. Les cas sont nombreux. La liste des codes postaux est gérée par La Poste, service d’origine publique. Pourtant il est difficile d’obtenir la liste des codes postaux et un marché de niche existe dans le domaine. Le Service National des Adresse (SNA) propose néanmoins un service en ligne des codes postaux. Ce dernier donne le code postal en fonction du nom de la localité.
Nous nous plaçons dans la problématique d’obtenir un référentiel des communes avec leur code INSEE et leur code postal. L’idée est de partir de la liste de l’INSEE puis de requêter le service du SNA. Ce dernier ne proposant pas de liste complète il faut pour chacune des communes remplir un formulaire pour obtenir le code postal correspondant.

Le script python suivant permet heureusement de faciliter ce travail en prenant en argument le fichier des codes INSEE et le fichier à générer. Les 2 difficultés sont:

#!/usr/bin/python
import urllib
import sys
from HTMLParser import HTMLParser
import logging

def get_cp(txtCommune,cdep=None):
  logging.info("looking for %s" %  txtCommune)
  params = urllib.urlencode({'txtCommune':txtCommune, 'selCritere':'CP'})
  f = urllib.urlopen('http://www.laposte.fr/sna/rubrique.php3?id_rubrique=59&recalcul=oui', params)
  reponse = f.read().decode('iso-8859-1')
  f.close()
  #-- remove malformatted HTML
  i = 0
  while i > -1:
    i = reponse.find('onclick=window.open')
    if i > -1 :
      logging.warn("malformed html found")
      j = reponse.find('false;',i)
      reponse = reponse[:i] + reponse[j+len('false;'):]
  p = MyParser(cdep)
  p.feed(reponse)
  p.close()
  return p.cp_found

class MyParser (HTMLParser):
  result_found=False
  tag = None
  cdep = None
  cp_found = []

  def __init__(self, cdep):
    HTMLParser.__init__(self)
    self.cdep = cdep
    self.tag = None
    self.result_found = False
    self.cp_found= []

  def handle_starttag(self, tag, attrs):
    for a,v in  attrs:
      if a == 'class':
        if v  =='resultat':
           self.result_found=True
           self.tag = tag

  def handle_endtag(self, tag):
    if self.result_found:
      if tag == self.tag:
        self.result_found=False

  def handle_data(self,data):
    if self.result_found :
      if self.cdep:
        if data.startswith(self.cdep) :
          self.cp_found.append(data)
      else:
        self.cp_found.append(data)

def normalize_commune(art, ncc):
  logging.info("normalizing %s %s" % (art, ncc))
  resu=None
  if art:
    resu = art.strip('(').strip(')').strip("'")
  if resu:
    resu = "%s %s" % (resu, ncc)
  else:
    resu = ncc
  resu = resu.replace('-',' ').replace('SAINT','ST').replace("'"," ")
  return resu

logging.basicConfig(level=logging.INFO)
with open(sys.argv[2],'w') as insee_cp:
  with  open(sys.argv[1]) as insee:
    first = True
    for line in insee:
      if first:
         first = False
         continue
      splitted = line.split('t')
      dep = splitted[3]
      com = splitted[4]
      artmaj = splitted[8]
      ncc = splitted[9]
      txtCommune = normalize_commune(artmaj, ncc)
      cp = get_cp(txtCommune, dep)
      cd_insee = dep + com
      if len(cp) > 1:
        logging.warn("Plusieurs  CP")
      for a_cp in cp:
        logging.info("%s CP %s INSEE %s" % (txtCommune, a_cp, cd_insee))
        insee_cp.write("%st%st%s" % (txtCommune, a_cp, cd_insee))
        insee_cp.write("n")

Le fichier généré contient le nom de la commune (formatée pour le site du SNA), le code postal et le code INSEE. Le lecteur pourra sans peine modifier le script pour qu’il sorte le nom de la commune dans sa forme originale (avec les apostrophes, tirets etc.)

Pour ceux que le code python rebute, le résultat du script est par ici : code postal et insee des communes 2011

Tags:

Comments

  1. Bonjour, et merci de faire profiter la communauté de vos efforts. Je n’utilise pas Python, j’ai récupéré votre fichier de résultat dans le cadre de recherches pour un site sur lequel je travaille (au final je compte utiliser le fichier officiel de l’INSEE car je n’ai pas de besoin impératif des CP, mais peu importe). Bref, je voulais simplement vous signaler qu’il doit y avoir une erreur quelque part, car les communes corses sont absentes ! Ca a surement à voir avec le 2A/2B…

    1. Bonsoir,

      Je vous remercie pour votre commentaire. Le fichier comsip2011 vous donnera satisfaction. En ce qui concerne le fichier résultant de mon script, il manque effectivement les communes de la Corse et vous avez raison c’est bien à cause de leur département en 2A et 2B dans le fichier comsip2011 alors que sur le site du SNA, ces communes ont des codes postaux commençant par 20 et non pas 2A et 2B. Une modification du script s’impose.

      Cdt.

  2. Bravo et merci pour le fichier de résultat déjà !
    Je ne connais pas trop Python mais ça aurait été bien d’expliciter le fonctionnement, il ne suffit pas de copier ce script sur son serveur je pense…
    J’imagine qu’il faut un fichier en entrée, est-ce le fichier brute Insee avec toutes ses colonnes ou bien un fichier modifié ? Je ne vois pas dans le script où est géré le 2a/2b ni les St/Ste et « -« . J’imagine qu’il fait un fichier en sortie mais lequel ? Comment adapter pour récupérer aussi les cedex ? Bref, une petite appli complète et commentée en zip ce serait le nirvana 😉

    1. Bonsoir,

      Une personne a soulevé le problème de la Corse. Mon script ne le gère pas. La modification n’est pas compliquée à faire. Il y a-t-il quelqu’un dans la salle ?
      En ce qui concerne le fonctionnement du script, ce dernier prend en entrée premièrement le fichier de l’insee (compsip) et en second le fichier final qui va contenir les codes postaux. Le script est fonctionnel tel quel. Il vous faut créer un fichier, disons code_postal.sh que vous devrez rendre exécutable (faîte un chmod 775 dessus). Puis lancer le script en ligne de commande avec les 2 arguments.

      Cdt.

      1. Ok, vu je le lance avec ./cp.sh 2011.txt cp-result.txt
        mais j’ai une erreur : ligne 72
        with open(sys.argv[2],’w’) as insee_cp:
        ^
        SyntaxError: invalid syntax

        1. Bonjour,

          La copie du script dans wordpress a du modifier sa mise en page. Python est un langage sans délimiteur de bloc et il se base sur l’indentation pour les définir. Il doit avoir un espace en trop ou en moins au début la ligne 72.
          Je vais mettre en ligne le fichier source et non seulement son contenu.

          Cdt.

  3. Salut, as-tu mis à jour ton script pour la Corse (et le fichier « communes.tar.gz ») ? Je ne te cacherais pas que ton fichier m’intéresse fortement, mais je n’ai jamais utilisé python et n’ai pas vraiment le temps de m’y mettre actuellement :/

    En tous cas merci pour ce bon script (et fichier 😉 )

  4. Bon en fait y’a un truc qui ne marche pas dans ce script c’est qu’il ne vérifie pas la ville donnée en réponse par le SNA en face du code postal. Du coup y’a des centaines de fausses infos.
    Exemple ton script interroge: dpt 50 ville St Lo, le SNA répond :
    50000 ST LO
    50580 ST LO D OURVILLE
    50420 ST LOUET SUR VIRE
    50300 ST LOUP
    Ton script ne vérifie pas la ville et enregistre ces 4 CP pour St Lo au lieu du seul 50000.
    A chaque fois qu’une ville à un nom qui peut-être le début d’autres dans le même département ça arrive.
    Un paquet de ST sont concernés (quasi 1 sur 2), vu qu’il est fréquent que dans un même dpt il y est un saint populaire qui a donné son prénom à plusieurs villages avec St Machin pas loin de St Machin de Truc voire d’un 3ème St Machin sur Bidule, ex dans le 23: ST PRIEST, ST PRIEST LA FEUILLE, ST PRIEST LA PLAINE, ST PRIEST PALUS.
    Mais pas seulement, tous les noms courts exemple 64300 MONT qui se retrouve avec 7 autres codes postaux attribués par erreur :
    64330 MONT DISSE
    64410 MONTAGUT
    64460 MONTANER
    64121 MONTARDON
    64800 MONTAUT
    64190 MONTFORT
    64470 MONTORY
    sans compter Mont (65) 6 de trop et Mont (71) 18 de trop, cette ville se retrouve avec 35 codes postaux au lieu de 3 !
    Ainsi Chas(63) prends aussi les CP de CHASTREIX et CHASSAGNE, Castet (64) en a 5 de trop comme Bou (45) ou Long (80), Brie (16,35,79) 4 de trop, Ger (64, 65) 6 de trop, La Chapelle (73) 3 de trop, Ville (60) 16 de trop, Le Mesnil (50) 21 de trop (!) etc etc…

    1. Bonsoir,

      Merci pour cette analyse. Voilà donc une seconde modification à apporter à ce script qui à l’air plus subtile que le problème de la Corse.
      Est-ce que quelqu’un peut s’y pencher ? La modification devrait se faire dans handle_data() ligne 51.

      Cdt.

    2. Bonjour,
      Je remarque le même problème que vous !
      Je ne connais pas Python, mais comme piste, je pense qu’il faudrait dans un premier temps, faire une requête sur le département + le nom exact de la commune. Puis pour les communes restantes, faire une requête sur le département + contient au moins le nom de la commune (comme le fait votre requête Python actuellement).
      Bonne journée

  5. Bravo pour votre code.
    Cependant j’ai travaillé quelques mois sur ces questions et c’est un problème trop complexe pour un script, puisque chaque cas de figure doit être envisagé en amont.
    À titre d’exemple il y a les communes dont le centre de distribution La Poste est dans un département voisin ( http://fr.wikipedia.org/wiki/Liste_des_communes_fran%C3%A7aises_dont_le_code_postal_ne_correspond_pas_au_d%C3%A9partement ), qui n’est que le sommet de l’iceberg des communes dont le bureau de poste est dans une commune voisine.
    Sans parler des communes affectées à plusieurs codes postaux (75116 affectée à 75016 et à 75116 pour la plus célèbre).
    À l’avenir on peut envisager des lieux-dits non desservis par l’opérateur de courrier (c’est le cas en Espagne), il faudra une liste des exceptions avec le code du bureau le plus proche.
    À propos d’étranger, pour Monaco le code insee est 99138 mais le code postal est 98000…
    Les problème des CEDEX a été abordé. Il y a aussi CIDEX, CONCOURS et ARMÉES. Quand j’avais tout réglé je me suis heurté aux ex-zones d’aménagement (la Défense, la Plaine-Saint-Denis, Roissy CDG,…) qui ont leurs propres codes postaux en superposition des codes postaux des communes qu’elles recouvrent.
    etc.
    Je me suis sorti en créant et reliant des tables de correspondances avec requêtes SQL (ne pas prendre que le premier résultat car il y a des correspondances multiples).
    Je vous recommande plutôt de créer des requêtes sur le site internet du SNA (La Poste) qui est exhaustif, par définition.
    Je me suis servi aussi dans le domaine public de tables de correspondance entre entonymes et codes insee ( http://download.geonames.org/export/dump/FR.zip ).

    1. Bonjour,

      J’ai écris ce script il y à longtemps maintenant. Il peut bien sûr être amélioré. Un lecteur a déjà indiqué que mon script ne gère pas la Corse.
      Le problème que vous soulevez est intéressant.En effet je me base sur le fichier comsip2011 fourni pas l’INSEE et qui donne la liste des communes avec leur code INSEE. Pour chaque commune, le script interroge le site du SNA avec en argument le nom de la commune. Le script gère également les réponses multiples : une commune avec plusieurs codes postaux ! J’ai mis en place le filtre sur le département afin de ne sélectionner que la commune du bon département dans le cas de plusieurs communes ayant le même nom dans des départements différents. Malheureusement dans le cas que vous indiquez, le script s’attend à un code postal sur son département ce qui n’est pas le cas pour les exceptions que vous relevez.

      Quelqu’un pour améliorer le script python ?

      Cdt.

  6. Bonjour,

    Excellent travail. Je me demandais s’il n’y avait pas de possibilités de systématiser cette démarche. Effectivement, je trouve scandaleux la rétention d’informations publiques pour lesquelles tout un chacun paye des impôts.

    Je poste votre travail sur différents forum de Système d’Informations Géographiques (SIG) afin que cela serve à tous les cartographes en herbe.

    Merenguey

  7. Bonjour et merci pour ce script bien pratique.

    Pour que le script gère les communes de la Corse, il suffit d’ajouter les 2 lignes suivantes après la ligne contenant : « HTMLParser.__init__(self) » :

    if cdep in [‘2A’, ‘2B’]:
    cdep = ’20’

    Je suis en train d’écrire un script inspiré du votre pour essayer de traiter un maximum de communes et de gérer différents problèmes (notamment celui de la vérification du nom de la commune dont parle Stéphane dans son commentaire). Ce qui est sûr, c’est que ce n’est pas un problème aussi simple qu’on pourrait le penser au premier abord…

    1. Bien sûr, les 2 lignes de code doivent être correctement indentée (visiblement WordPress a supprimé les espaces en début de ligne)

  8. Bonjour,

    Est ce que le script tient compte des remarques des différents commentaires ? Surtout les commentaire de Stéphane. J’ai modifié votre script comme indiqué par cmessiant pour tenir compte de la corse.

    Merci pour votre travail.

  9. Bonjour,
    Merci beaucoup pour ce script. J’ai retravaillé 2-3 trucs, et cela à l’air de marcher pas trop mal. Par contre j’ai été plus strict sur le remplacement de SAINT en ST car cela remplaçait même quand SAINT était contenu dans un autre mot.
    Par contre je génère le fichier des codes postaux avec le nom et le code postal retourné par le site et non pas par celui présent dans les communes (parfois le nom diffère un peu mais je ne sais pas qui a raison).
    Finalement, au vu des problèmes rencontrés, je me demande si ça ne serait pas plus simple de faire une boucle sur les codes postaux et de retourner le nom des communes.
    Ce que je ne comprends pas par contre est pourquoi sur le site de la SNA des recherches du type AMBERIEUX EN DOMBES ne retourne rien alors que la recherche sur 01330 retourne bien AMBERIEUX EN DOMBES.
    En tout cas ma modeste pierre à l’édifice ce trouve sur bitbucket (si cela ne pose pas de problème) : https://bitbucket.org/rhumland/code-postal
    Romain

    1. Bien vu Romain !
      Apparemment pour le SNA il faut tronquer les espaces car je viens de réussir à le faire marcher avec AMBERIEUXENDOMBES et LEPUYENVELAY , mais pas avec PUYENVELAY sans l’article. Il faut toujours l’article : LOSMASOS et non MASOS.
      Effectivement si tu essayais toutes les combinaisons de 5 chiffres tu pourrais nous constituer un fichier excel.
      Ceci ne résoud pas le homonymies ni le manque des détail (le fait que le SNA ne donne les arrondissements que si le code postal couvre plusieurs arrondissements, exemple : 75862.
      JackAttack

    2. Bonjour,
      Un grand merci à Thierry Bressure, Romain et à tout ceux non cités, je cherchais un moyen d’avoir une liste de CP à jour et la plus exhaustive possible, une chance que je sois tombé sur votre blog.

      De plus le script que vous avez développé est un bon moyen pour mettre à jour la bdd à tout moment.

      Merci pour ce très bon article et vos sources.

  10. Bonjour,

    Merci pour votre travail mais comme je ne suis pas fort en informatique, j’ai téléchargé la base de données toute faite en .csv que j’ai pu adapter dans Excel à cette adresse http://www.data-tables.com .

    Franchement c’est un beau boulot car je recherchais en plus des codes postaux les altitudes des villes et sur ce fichier il y a tout ça.

    Bonne continuation à tous
    .

  11. Bonjour, j’ai utilisé le script de R0M1S30N avec le fichier de l’INSEE de 2013 et il passe une quantité astronomique de commune pour lesquels il ne trouve pas de code postal.

    De plus, il est impossible de couper le script avec CTRL+C. Il faut faire un kill .

  12. Il n’y a aujourd’hui pas de solution parfaite pour cette recherche de CP du moins en se basant sur le service du SNA. Pour le moment, pour certaine page, on n’arrive pas à lire la réponse du web service, car le tableau n’est pas balisé de la même façon selon le type de réponse (ex : 33115). La méthode par recherche de communes ne trouve pas tout (cf. qq exemples cités plus tôt). L’autre méthode par boucle est très loin d’être parfaite car au bout d’un moment le site de la poste blackliste l’IP qui fait trop d’appel. J’ai quand même fusionner les différents fichiers pour avoir un fichier de référence qui n’est pas complet, mais présente des données en plus par rapport au fichier initial.
    A l’occasion, il faudrait découper l’appel en plusieurs boucles, pour ne pas se faire black lister et essayer de trouver un algo un peu plus intelligent pour ne pas faire toutes les combinaisons.
    Bref, encore des améliorations à faire, mais cela retourne déjà des choses.
    https://bitbucket.org/rhumland/code-postal
    @xarkam, j’ai retiré le try/catch autour de l’appel au web service, donc maintenant le ctrl-c arrête bien le programme.

  13. Malheureusement le site cp-ville.com ne semble pas être complet (ex: la recherche 33115 ne retourne rien), donc je ne pense pas que ce site permettra de combler les lacunes des méthodes présentés ici.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

Back to top