172 votes

Comment analyser une adresse postale libre dans du texte et la diviser en composantes

Nous faisons principalement des affaires aux États-Unis et essayons d'améliorer l'expérience utilisateur en regroupant tous les champs d'adresse dans une seule zone de texte. Mais il y a quelques problèmes :

  • L'adresse saisie par l'utilisateur peut ne pas être correcte ou dans un format standard
  • L'adresse doit être séparée en parties (rue, ville, état, etc.) pour traiter les paiements par carte de crédit
  • Les utilisateurs peuvent saisir plus que leur adresse (comme leur nom ou leur entreprise avec)
  • Google peut le faire, mais les conditions d'utilisation et les limites de requête sont prohibitives, surtout avec un budget serré

Apparemment, c'est une question courante:

Y a-t-il un moyen d'isoler une adresse du texte qui l'entoure et de la diviser en morceaux ? Existe-t-il une expression régulière pour parser des adresses ?

0 votes

Les réponses ci-dessous sont plus utiles car elles ne ignorent pas le problème global - que les adresses ne suivent pas un schéma commun.

335voto

Matt Points 5202

J'ai souvent vu cette question lorsque je travaillais pour une société de vérification d'adresses. Je publie la réponse ici pour la rendre plus accessible aux programmeurs qui se posent la même question. La société dans laquelle je travaillais traitait des milliards d'adresses, et nous avons beaucoup appris au cours de ce processus.

Tout d'abord, nous devons comprendre certaines choses sur les adresses.

Les adresses ne sont pas régulier

Cela signifie que les expressions régulières sont exclues. J'ai tout vu, des expressions régulières simples qui correspondent à des adresses dans un format très spécifique, à ceci :

/ \s +( \d {2,5} \s +)( ?![a|p]m \b )(([a-zA-Z|| \s +]{1,5}){1,2}) ?([ \s |,|.]+) ?(([a-zA-Z| \s +]{1,30}){1,4})(court|ct|street|st|drive|dr|lane|ln|road|rd|blvd)([ \s |,|.| ;]+) ?(([a-zA-Z| \s +]{1,30}){1,2})([ \s |,|.]+) ? \b (AK|AL|AR|AZ|CA|CO|CT|DC|DE|FL|GA|GU|HI|IA|ID|IL|IN|KS|KY|LA|MA|MD|ME|MI|MN|MO|MS|MT|NC|ND|NE|NH|NJ|NM|NV|NY|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VA|VI|VT|WA|WI|WV|WY)([ \s |,|.]+) ?( \s + \d {5}) ?([ \s |,|.]+)/i

... à este où un fichier de plus de 900 lignes génère à la volée une expression régulière supermassive pour correspondre à encore plus de choses. Je ne les recommande pas (par exemple, voici un bidouillage de la regex ci-dessus, qui fait beaucoup d'erreurs ). Il n'y a pas de formule magique facile pour que cela fonctionne. En théorie et par En théorie, il est impossible de faire correspondre des adresses avec une expression régulière.

Publication 28 de l'USPS documente les nombreux formats d'adresses possibles, avec tous leurs mots-clés et variations. Le pire, c'est que les adresses sont souvent ambiguës. Les mots peuvent avoir plusieurs sens ("St" peut être "Saint" ou "Street"), et il y a des mots dont je suis presque sûr qu'ils ont été inventés. (Qui savait que "Stravenue" était un suffixe de rue ?)

Il faudrait un code qui comprenne vraiment les adresses, et si ce code existe, c'est un secret commercial. Mais tu pourrais probablement faire ton propre code si tu es vraiment intéressé.

Les adresses ont des formes et des tailles inattendues

Voici quelques adresses inventées (mais complètes) :

1)  102 main street
    Anytown, state

2)  400n 600e #2, 52173

3)  p.o. #104 60203

Même ceux-ci sont peut-être valables :

4)  829 LKSDFJlkjsdflkjsdljf Bkpw 12345

5)  205 1105 14 90210

Évidemment, ces éléments ne sont pas normalisés. La ponctuation et le saut de ligne ne sont pas garantis. Voici ce qui se passe :

  1. Numéro 1 est complet car il contient une adresse postale, une ville et un état. Ces informations sont suffisantes pour identifier l'adresse, et celle-ci peut être considérée comme "livrable" (moyennant une certaine normalisation).

  2. Numéro 2 est complet car il contient une adresse de rue (avec le numéro secondaire/unité) et un code ZIP à 5 chiffres, ce qui est suffisant pour identifier une adresse.

  3. Numéro 3 est un format complet de boîte postale, car il contient un code postal.

  4. Numéro 4 est également complet car le code postal est unique ce qui signifie qu'une entité ou une société privée a acheté cet espace d'adressage. Un code ZIP unique est destiné aux espaces de livraison à fort volume ou concentrés. Tout ce qui est adressé au code ZIP 12345 est destiné à General Electric à Schenectady, NY. Cet exemple n'atteindra personne en particulier, mais l'USPS le livrera quand même.

  5. Numéro 5 est également complet, croyez-le ou non. Avec ces seuls chiffres, l'adresse complète peut être découverte lorsqu'elle est analysée dans une base de données de toutes les adresses possibles. Il est facile de combler les lacunes en matière de direction, d'indicatif secondaire et de code ZIP+4 lorsque l'on considère chaque numéro comme un composant. Voici à quoi cela ressemble, entièrement développé et normalisé :

205 N 1105 W Apt 14

Beverly Hills CA 90210-5221

Les données d'adresse ne sont pas les vôtres

Dans la plupart des pays qui fournissent des données d'adresse officielles aux vendeurs agréés, les données d'adresse elles-mêmes appartiennent à l'agence qui les gère. Aux États-Unis, l'USPS est propriétaire des adresses. Il en va de même pour Postes Canada, Royal Mail et d'autres, bien que chaque pays applique ou définisse la propriété un peu différemment. Il est important de le savoir car cela interdit généralement la rétro-ingénierie de la base de données d'adresses. Vous devez faire attention à la manière dont vous acquérez, stockez et utilisez les données.

Google Maps est une solution courante pour trouver rapidement une adresse, mais la TOS est plutôt prohibitif ; par exemple, vous ne pouvez pas utiliser leurs données ou leurs API sans afficher une carte Google, et ce uniquement à des fins non commerciales (sauf si vous payez), et vous ne pouvez pas stocker les données (sauf pour une mise en cache temporaire). C'est logique. Les données de Google sont parmi les meilleures au monde. Cependant, Google Maps no vérifier l'adresse. Si une adresse n'existe pas, le système vous indiquera quand même où se trouve l'adresse. serait être si elle hizo existent (essayez dans votre propre rue ; utilisez un numéro de maison dont vous savez qu'il n'existe pas). Cette méthode est parfois utile, mais il faut en être conscient.

Nominatim's politique d'utilisation est également limité, en particulier pour les gros volumes et l'utilisation commerciale, et les données sont principalement tirées de sources gratuites, donc elles ne sont pas aussi bien maintenues (comme la nature des projets ouverts). Cependant, il peut encore répondre à vos besoins. Une grande communauté le soutient.

L'USPS lui-même dispose d'une API, mais il descend beaucoup et ne s'accompagne d'aucune garantie ni d'aucune assistance. Il peut également être difficile à utiliser. Certaines personnes l'utilisent avec parcimonie et sans problème. Mais il est facile d'oublier que l'USPS exige que vous utilisiez son API uniquement pour confirmer les adresses à expédier par son intermédiaire.

Les gens s'attendent à ce que les adresses soient difficiles

Malheureusement, nous avons conditionné notre société à s'attendre à ce que les adresses soient compliquées. Il existe des dizaines de bons articles d'UX sur Internet à ce sujet. Il n'en reste pas moins que si vous disposez d'un formulaire d'adresse avec des champs individuels, c'est ce que les utilisateurs attendent, même si cela complique les choses pour les adresses marginales qui ne correspondent pas au format attendu par le formulaire, ou si le formulaire exige un champ qu'il ne devrait pas. Ou encore, les utilisateurs ne savent pas où placer une certaine partie de leur adresse.

Je pourrais continuer à parler de la mauvaise ergonomie des formulaires de paiement de nos jours, mais je dirai plutôt que le fait de regrouper les adresses dans un seul champ sera un atout pour l'avenir. bienvenue à changement : les gens pourront taper leur adresse comme ils l'entendent, plutôt que d'essayer de comprendre votre long formulaire. Cependant, ce changement sera inattendu et les utilisateurs peuvent trouver cela un peu déroutant au début. Il faut en être conscient.

Une partie de cette douleur peut être atténuée en plaçant le champ de pays au début, avant l'adresse. Lorsqu'ils remplissent d'abord le champ "pays", vous savez comment faire apparaître votre formulaire. Vous avez peut-être un bon moyen de traiter les adresses américaines à champ unique. Ainsi, s'ils sélectionnent les États-Unis, vous pouvez réduire votre formulaire à un seul champ, sinon vous affichez les champs des composants. Autant de choses auxquelles il faut réfléchir !

Maintenant que nous savons pourquoi c'est difficile, que pouvez-vous faire ?

L'USPS accorde des licences aux vendeurs par le biais d'un processus appelé certification CASS™ pour fournir des adresses vérifiées aux clients. Ces fournisseurs ont accès à la base de données de l'USPS, mise à jour mensuellement. Leurs logiciels doivent se conformer à des normes rigoureuses pour être certifiés, et ils n'exigent pas souvent l'acceptation de conditions restrictives telles que celles mentionnées ci-dessus.

De nombreuses entreprises certifiées CASS peuvent traiter des listes ou disposent d'API : Melissa Data, Experian QAS, et SmartyStreets, pour n'en citer que quelques-unes.

(En raison des critiques que j'ai reçues pour "publicité", j'ai tronqué ma réponse à ce stade. C'est à vous de trouver une solution qui vous convient).

La vérité : Vraiment, les amis, je ne travaille dans aucune de ces entreprises. Ce n'est pas une publicité.

1 votes

Qu'en est-il des adresses sud-américaines (Uruguay) ? :D

1 votes

@Bart Je ne connais aucun service qui prend en charge l'extraction des adresses en Uruguay, désolé!

11 votes

@Brian - Peut-être parce que l'utilisateur a fourni beaucoup d'informations utiles pour ceux qui lisent la question et la réponse, que ce soit ou non qu'ils choisissent d'utiliser le produit de son entreprise.

38voto

David Portabella Points 1811

Libpostal: une bibliothèque open-source pour parser les adresses, formée avec des données provenant d'OpenStreetMap, OpenAddresses et OpenCage.

https://github.com/openvenues/libpostal (plus d'infos à ce sujet)

Autres outils/services :

15voto

John Nagle Points 324

Il existe de nombreux analyseurs d'adresses de rue. Ils se déclinent en deux versions de base - ceux qui possèdent des bases de données de noms de lieux et de noms de rue, et ceux qui n'en ont pas.

Un analyseur d'adresse de rue basé sur les expressions régulières peut atteindre un taux de réussite d'environ 95% sans trop de difficulté. Ensuite, vous commencez à rencontrer des cas inhabituels. Celui en Perl dans CPAN, "Geo::StreetAddress::US", est à peu près de cette qualité. Il existe des versions portées en Python et en Javascript, toutes open source. J'ai une version améliorée en Python qui augmente légèrement le taux de réussite en traitant plus de cas. Pour obtenir les 3% restants de façon correcte, cependant, vous avez besoin de bases de données pour aider à la désambiguïsation.

Une base de données avec des codes postaux à 3 chiffres et des noms et abréviations d'États-Unis est d'une grande aide. Lorsqu'un analyseur voit un code postal cohérent et un nom d'État, il peut commencer à verrouiller le format. Cela fonctionne très bien pour les États-Unis et le Royaume-Uni.

Un analyse approprié des adresses de rue commence par la fin et travaille à l'envers. C'est ainsi que les systèmes USPS le font. Les adresses sont moins ambiguës à la fin, où les noms de pays, les noms de ville et les codes postaux sont relativement faciles à reconnaître. Les noms de rue peuvent généralement être isolés. Les emplacements dans les rues sont les plus complexes à analyser ; c'est là que vous rencontrez des choses telles que "Cinquième étage" et "Pavillon Staples". C'est à ce moment qu'une base de données est d'une grande aide.

0 votes

Il existe également le module CPAN Lingua:EN::AddressParse. Bien qu'il soit plus lent que "Geo::StreetAddress::US, il offre un taux de réussite plus élevé.

0 votes

Pouvez-vous recommander un sans base de données pour le Royaume-Uni ? Merci !

10voto

Ervin Ruci Points 392

MISE À JOUR : Geocode.xyz fonctionne désormais dans le monde entier. Pour des exemples, consultez https://geocode.xyz

Pour les États-Unis, le Mexique et le Canada, consultez geocoder.ca.

Par exemple:

Entrée: something going on near the intersection of main and arthur kill rd new york

Sortie:

  40.5123510000
  -74.2500500000
  347,718
  America/New_York

    main
    arthur kill

    STATEN ISLAND
    NY
    11385
    0.9

Vous pouvez également vérifier les résultats dans l'interface web ou obtenir une sortie en Json ou Jsonp. par exemple I'm looking for restaurants around 123 Main Street, New York

0 votes

Comment avez-vous mis en place le système d'analyse d'adresse en utilisant openaddress ? Utilisez-vous la stratégie de la force brute ?

1 votes

Que voulez-vous dire par 'force brute'? Diviser le texte en toutes les combinaisons possibles de chaînes d'adresses possibles et les comparer une par une à une base de données d'adresses n'est pas pratique et prendra bien plus de temps pour fournir une réponse que ce système ne le fait. Les openaddresses sont l'une des sources de données pour construire un 'jeu d'entraînement' de formats d'adresse pour l'algorithme. Il utilise ces informations pour analyser les adresses à partir de texte non structuré.

3 votes

Un autre système similaire est Geo::libpostal ( perltricks.com/article/announcing-geo--libpostal ). Ils utilisent également openstreetmap et openaddresses, semble-t-il, pour construire des modèles d'adresse à la volée.

5voto

Pas de code ? C'est une honte !

Voici un analyseur d'adresses simple en JavaScript. Il est assez mauvais pour toutes les raisons que Matt donne dans sa dissertation ci-dessus (avec lesquelles je suis d'accord à presque 100% : les adresses sont des types complexes, et les humains font des erreurs ; il vaut mieux externaliser et automatiser cela - quand on peut se le permettre).

Mais plutôt que de pleurer, j'ai décidé d'essayer :

Ce code fonctionne correctement pour analyser la plupart des résultats Esri pour findAddressCandidate et aussi avec d'autres géocodeurs (inversés) qui renvoient des adresses d'une seule ligne où la rue, la ville et l'état sont délimités par des virgules. Vous pouvez étendre si vous le souhaitez ou écrire des analyseurs spécifiques à chaque pays. Ou simplement utiliser ceci comme étude de cas pour montrer à quel point cet exercice peut être difficile ou à quel point je suis nul en JavaScript. J'admets que je n'ai passé qu'une trentaine de minutes sur ce projet (les itérations futures pourraient ajouter des caches, la validation du code postal, la recherche d'un état ainsi que le contexte de localisation de l'utilisateur), mais cela a fonctionné pour mon cas d'utilisation : L'utilisateur final voit un formulaire qui analyse la réponse à la recherche par géocode dans 4 zones de texte. Si l'analyse de l'adresse est erronée (ce qui est rare, sauf si les données sources sont de mauvaise qualité), ce n'est pas grave - l'utilisateur peut vérifier et corriger l'erreur ! (Mais pour les solutions automatisées, il est possible soit de rejeter/ignorer, soit de signaler une erreur pour que le développeur puisse soit prendre en charge le nouveau format, soit corriger les données sources).

/* 
address assumptions:
- US addresses only (probably want separate parser for different countries)
- No country code expected.
- if last token is a number it is probably a postal code
-- 5 digit number means more likely
- if last token is a hyphenated string it might be a postal code
-- if both sides are numeric, and in form #####-#### it is more likely
- if city is supplied, state will also be supplied (city names not unique)
- zip/postal code may be omitted even if has city & state
- state may be two-char code or may be full state name.
- commas: 
-- last comma is usually city/state separator
-- second-to-last comma is possibly street/city separator
-- other commas are building-specific stuff that I don't care about right now.
- token count:
-- because units, street names, and city names may contain spaces token count highly variable.
-- simplest address has at least two tokens: 714 OAK
-- common simple address has at least four tokens: 714 S OAK ST
-- common full (mailing) address has at least 5-7:
--- 714 OAK, RUMTOWN, VA 59201
--- 714 S OAK ST, RUMTOWN, VA 59201
-- complex address may have a dozen or more:
--- MAGICICIAN SUPPLY, LLC, UNIT 213A, MAGIC TOWN MALL, 13 MAGIC CIRCLE DRIVE, LAND OF MAGIC, MA 73122-3412
*/

var rawtext = $("textarea").val();
var rawlist = rawtext.split("\n");

function ParseAddressEsri(singleLineaddressString) {
  var address = {
    street: "",
    city: "",
    state: "",
    postalCode: ""
  };

  // tokenize by space (retain commas in tokens)
  var tokens = singleLineaddressString.split(/[\s]+/);
  var tokenCount = tokens.length;
  var lastToken = tokens.pop();
  if (
    // if numeric assume postal code (ignore length, for now)
    !isNaN(lastToken) ||
    // if hyphenated assume long zip code, ignore whether numeric, for now
    lastToken.split("-").length - 1 === 1) {
    address.postalCode = lastToken;
    lastToken = tokens.pop();
  }

  if (lastToken && isNaN(lastToken)) {
    if (address.postalCode.length && lastToken.length === 2) {
      // assume state/province code ONLY if had postal code
      // otherwise it could be a simple address like "714 S OAK ST"
      // where "ST" for "street" looks like two-letter state code
      // possibly this could be resolved with registry of known state codes, but meh. (and may collide anyway)
      address.state = lastToken;
      lastToken = tokens.pop();
    }
    if (address.state.length === 0) {
      // check for special case: might have State name instead of State Code.
      var stateNameParts = [lastToken.endsWith(",") ? lastToken.substring(0, lastToken.length - 1) : lastToken];

      // check remaining tokens from right-to-left for the first comma
      while (2 + 2 != 5) {
        lastToken = tokens.pop();
        if (!lastToken) break;
        else if (lastToken.endsWith(",")) {
          // found separator, ignore stuff on left side
          tokens.push(lastToken); // put it back
          break;
        } else {
          stateNameParts.unshift(lastToken);
        }
      }
      address.state = stateNameParts.join(' ');
      lastToken = tokens.pop();
    }
  }

  if (lastToken) {
    // here is where it gets trickier:
    if (address.state.length) {
      // if there is a state, then assume there is also a city and street.
      // PROBLEM: city may be multiple words (spaces)
      // but we can pretty safely assume next-from-last token is at least PART of the city name
      // most cities are single-name. It would be very helpful if we knew more context, like
      // the name of the city user is in. But ignore that for now.
      // ideally would have zip code service or lookup to give city name for the zip code.
      var cityNameParts = [lastToken.endsWith(",") ? lastToken.substring(0, lastToken.length - 1) : lastToken];

      // assumption / RULE: street and city must have comma delimiter
      // addresses that do not follow this rule will be wrong only if city has space
      // but don't care because Esri formats put comma before City
      var streetNameParts = [];

      // check remaining tokens from right-to-left for the first comma
      while (2 + 2 != 5) {
        lastToken = tokens.pop();
        if (!lastToken) break;
        else if (lastToken.endsWith(",")) {
          // found end of street address (may include building, etc. - don't care right now)
          // add token back to end, but remove trailing comma (it did its job)
          tokens.push(lastToken.endsWith(",") ? lastToken.substring(0, lastToken.length - 1) : lastToken);
          streetNameParts = tokens;
          break;
        } else {
          cityNameParts.unshift(lastToken);
        }
      }
      address.city = cityNameParts.join(' ');
      address.street = streetNameParts.join(' ');
    } else {
      // if there is NO state, then assume there is NO city also, just street! (easy)
      // reasoning: city names are not very original (Portland, OR and Portland, ME) so if user wants city they need to store state also (but if you are only ever in Portlan, OR, you don't care about city/state)
      // put last token back in list, then rejoin on space
      tokens.push(lastToken);
      address.street = tokens.join(' ');
    }
  }
  // when parsing right-to-left hard to know if street only vs street + city/state
  // hack fix for now is to shift stuff around.
  // assumption/requirement: will always have at least street part; you will never just get "city, state"  
  // could possibly tweak this with options or more intelligent parsing&sniffing
  if (!address.city && address.state) {
    address.city = address.state;
    address.state = '';
  }
  if (!address.street) {
    address.street = address.city;
    address.city = '';
  }

  return address;
}

// get list of objects with discrete address properties
var addresses = rawlist
  .filter(function(o) {
    return o.length > 0
  })
  .map(ParseAddressEsri);
$("#output").text(JSON.stringify(addresses));
console.log(addresses);

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea>
27488 Stanford Ave, Bowden, North Dakota
380 New York St, Redlands, CA 92373
13212 E SPRAGUE AVE, FAIR VALLEY, MD 99201
1005 N Gravenstein Highway, Sebastopol CA 95472
A. P. Croll &amp; Son 2299 Lewes-Georgetown Hwy, Georgetown, DE 19947
11522 Shawnee Road, Greenwood, DE 19950
144 Kings Highway, S.W. Dover, DE 19901
Intergrated Const. Services 2 Penns Way Suite 405, New Castle, DE 19720
Humes Realty 33 Bridle Ridge Court, Lewes, DE 19958
Nichols Excavation 2742 Pulaski Hwy, Newark, DE 19711
2284 Bryn Zion Road, Smyrna, DE 19904
VEI Dover Crossroads, LLC 1500 Serpentine Road, Suite 100 Baltimore MD 21
580 North Dupont Highway, Dover, DE 19901
P.O. Box 778, Dover, DE 19903
714 S OAK ST
714 S OAK ST, RUM TOWN, VA, 99201
3142 E SPRAGUE AVE, WHISKEY VALLEY, WA 99281
27488 Stanford Ave, Bowden, North Dakota
380 New York St, Redlands, CA 92373
</textarea>
<div id="output">
</div>

0 votes

Disclaimer: mes clients possèdent leurs propres données d'adresse et gèrent leurs propres serveurs Esri. Si vous récupérez des données de Google, OSM, ArcGIS Online, ou d'ailleurs, assurez-vous qu'il est autorisé de les stocker et de les utiliser (de nombreux services imposent des restrictions sur la manière dont vous pouvez les stocker et pendant combien de temps)

0 votes

La première réponse ci-dessus présente un argument convaincant selon lequel ce problème est irrésolvable avec des regex si vous traitez avec une liste d'adresses globale. 200 pays ont trop d'exceptions. Dans mes tests, vous pouvez déterminer le pays à partir d'une chaîne de manière assez fiable, puis rechercher une regex spécifique pour chaque pays - ce qui est probablement la manière dont fonctionnent les meilleures API.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X