Come analizzare testo / indirizzo postale a mano libera da testo e componenti

Operiamo prevalentemente negli Stati Uniti e stiamo cercando di migliorare l’esperienza utente combinando tutti i campi dell’indirizzo in un’unica area di testo. Ma ci sono alcuni problemi:

  • L’indirizzo dei tipi di utenti potrebbe non essere corretto o in un formato standard
  • L’indirizzo deve essere separato in parti (strada, città, stato, ecc.) Per elaborare i pagamenti con carta di credito
  • Gli utenti possono inserire più del proprio indirizzo (come il loro nome o la società con esso)
  • Google può farlo, ma i Termini di servizio e i limiti di query sono proibitivi, specialmente con un budget limitato

Apparentemente, questa è una domanda comune:

  • Script PHP per analizzare l’indirizzo?
  • Come analizzo l’indirizzo di formato libero da salvare nel DataBase
  • parser per indirizzo postale java
  • Modo più efficiente per estrarre i componenti dell’indirizzo
  • Come posso mostrare un indirizzo postale pre-popolato nella schermata dei contatti con street, city, zip su Android
  • PHP regexp indirizzo degli Stati Uniti

C’è un modo per isolare un indirizzo dal testo che lo circonda e romperlo in pezzi? Esiste un’espressione regolare per analizzare gli indirizzi?

Ho visto questa domanda molto quando ho lavorato per una società di verifica degli indirizzi. Sto postando qui la risposta per renderla più accessibile ai programmatori che stanno cercando in giro con la stessa domanda. La società ero a miliardi di indirizzi elaborati e abbiamo imparato molto nel processo.

Innanzitutto, dobbiamo capire alcune cose sugli indirizzi.

Gli indirizzi non sono regolari

Ciò significa che le espressioni regolari sono fuori. Ho visto tutto, dalle semplici espressioni regolari che corrispondono agli indirizzi in un formato molto specifico, a questo:

/ \ 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}) (corte | ct | via | st | DRIVE | dr | corsia | ln | stradali | 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 | CN | ND | NE | NH | NJ | nm | NV | NY | OH | OK | O | PA | RI | SC | SD | TN | TX | UT | VA | VI | VT | WA | WI | WV | WY) ([\ s | \, |.]? +) (\ s + \ d {5}) ([\ s | \, |.] +) / i

… a questo, dove un file di class di 900+ genera un’espressione regolare supermassicizzata al volo per abbinarla ancora di più. Non li consiglio (ad esempio, ecco un violino della regex di cui sopra, che fa molti errori ). Non esiste una formula magica facile per farlo funzionare. In teoria e in teoria, non è ansible abbinare gli indirizzi con un’espressione regolare.

La pubblicazione USPS 28 documenta i molti formati di indirizzi che sono possibili, con tutte le loro parole chiave e variazioni. Peggio ancora, gli indirizzi sono spesso ambigui. Le parole possono significare più di una cosa (“St” può essere “Saint” o “Street”) e ci sono parole che sono abbastanza sicure che abbiano inventato. (Chi sapeva che “Stravenue” era un suffisso stradale?)

Avresti bisogno di un codice che comprenda veramente gli indirizzi e, se questo codice esiste, è un segreto commerciale. Ma probabilmente potresti fare il tuo da solo se ti piace davvero.

Gli indirizzi hanno forms e dimensioni inaspettate

Ecco alcuni indirizzi forzati (ma completi):

1) 102 main street Anytown, state 2) 400n 600e #2, 52173 3) po #104 60203 

Anche questi sono probabilmente validi:

 4) 829 LKSDFJlkjsdflkjsdljf Bkpw 12345 5) 205 1105 14 90210 

Ovviamente, questi non sono standardizzati. Punteggiatura e interruzioni di riga non garantite. Ecco cosa sta succedendo:

  1. Il numero 1 è completo perché contiene un indirizzo e una città e stato. Con queste informazioni, è sufficiente identificare l’indirizzo e può essere considerato “deliverable” (con una certa standardizzazione).

  2. Il numero 2 è completo perché contiene anche un indirizzo (con numero di unità secondaria / unità) e un codice di avviamento postale a 5 cifre, che è sufficiente per identificare un indirizzo.

  3. Il numero 3 è un formato di casella postale completo, poiché contiene un codice postale.

  4. Anche il numero 4 è completo perché il codice postale è univoco , il che significa che un’ quadro o una società private ha acquistato tale spazio indirizzo. Un codice ZIP univoco è per spazi di consegna elevati o concentrati. Qualsiasi cosa indirizzata al codice di avviamento postale 12345 va a General Electric a Schenectady, NY. Questo esempio non arriverà a nessuno in particolare, ma l’USPS sarebbe ancora in grado di consegnarlo.

  5. Anche il numero 5 è completo, che ci crediate o no. Con solo quei numeri, l’indirizzo completo può essere scoperto quando analizzato su un database di tutti gli indirizzi possibili. Compilare le direzioni mancanti, il designatore secondario e il codice ZIP + 4 è banale quando si vede ogni numero come componente. Ecco come appare, completamente ampliato e standardizzato:

205 N 1105 W Apt 14

Beverly Hills CA 90210-5221

I dati dell’indirizzo non sono tuoi

Nella maggior parte dei paesi che forniscono dati ufficiali sugli indirizzi ai venditori autorizzati, i dati degli indirizzi stessi appartengono all’agenzia governativa. Negli Stati Uniti, l’USPS possiede gli indirizzi. Lo stesso vale per Canada Post, Royal Mail e altri, anche se ogni paese applica o definisce la proprietà un po ‘diversamente. Sapere questo è importante, dato che di solito proibisce il reverse engineering del database degli indirizzi. Devi stare attento a come acquisire, archiviare e utilizzare i dati.

Google Maps è una soluzione comune per risolvere rapidamente gli indirizzi, ma i TOS sono piuttosto proibitivi; ad esempio, non è ansible utilizzare i propri dati o API senza mostrare una mappa di Google e solo per scopi non commerciali (a meno che non si paghi) e non è ansible memorizzare i dati (ad eccezione della memorizzazione temporanea nella cache). Ha senso. I dati di Google sono tra i migliori al mondo. Tuttavia, Google Maps non verifica l’indirizzo. Se un indirizzo non esiste, ti mostrerà comunque dove si troverebbe l’indirizzo se esistesse (provalo sulla tua strada, usa un numero civico che tu sai che non esiste). Questo è utile a volte, ma sii consapevole di ciò.

La politica di utilizzo di Nominatim è analogamente limitativa, in particolare per volumi elevati e uso commerciale, e i dati sono per lo più tratti da fonti gratuite, quindi non è mantenuto (è la natura dei progetti aperti) – tuttavia, questo potrebbe ancora adattarsi I tuoi bisogni. È supportato da una grande comunità.

Lo stesso USPS ha un’API, ma scende molto e non ha garanzie né supporto. Potrebbe anche essere difficile da usare. Alcune persone lo usano con parsimonia senza problemi. Ma è facile perdere che USPS richiede che tu usi la loro API solo per confermare gli indirizzi da spedire attraverso di loro.

La gente si aspetta che gli indirizzi siano difficili

Sfortunatamente, abbiamo condizionato la nostra società ad aspettarci che gli indirizzi siano complicati. Esistono dozzine di buoni articoli UX su Internet, ma il fatto è che, se si dispone di un modulo di indirizzo con campi individuali, questo è ciò che gli utenti si aspettano, anche se rende più difficile per gli indirizzi edge-case che non si adattano al formattare il modulo è in attesa, o forse il modulo richiede un campo che non dovrebbe. O gli utenti non sanno dove mettere una certa parte del loro indirizzo.

Potrei continuare a parlare della brutta UX dei moduli di pagamento in questi giorni, ma invece dirò semplicemente che combinare gli indirizzi in un singolo campo sarà un cambiamento positivo – le persone saranno in grado di digitare il loro indirizzo come ritengono opportuno , piuttosto che cercare di capire la tua forma lunga. Tuttavia, questo cambiamento sarà inaspettato e gli utenti potrebbero trovarlo un po ‘irritante all’inizio. Basta essere consapevoli di questo.

Parte di questo dolore può essere alleviato mettendo il campo di campagna davanti, prima dell’indirizzo. Quando compilano per primi il campo Paese, sai come far apparire il tuo modulo. Forse hai un buon modo per gestire gli indirizzi statunitensi a campo singolo, quindi se si seleziona Stati Uniti, puoi ridurre il modulo in un singolo campo, altrimenti mostrare i campi del componente. Solo cose a cui pensare!

Ora sappiamo perché è difficile; cosa puoi fare al riguardo?

L’USPS vende i fornitori attraverso un processo chiamato Certificazione CASS ™ per fornire agli utenti gli indirizzi verificati. Questi venditori hanno accesso al database USPS, aggiornato mensilmente. Il loro software deve essere conforms a standard rigorosi per essere certificato e spesso non richiedono l’accettazione di tali termini limitativi come discusso sopra.

Ci sono molte aziende certificate CASS che possono elaborare elenchi o avere API: Melissa Data, Experian QAS e SmartyStreet per citarne alcuni.

(A causa di una falla per “pubblicità” ho troncato la mia risposta a questo punto. Sta a te trovare una soluzione che funzioni per te.)

La verità: davvero, gente, non lavoro in nessuna di queste compagnie. Non è una pubblicità.

Ci sono molti parser per gli indirizzi stradali. Vengono in due sapori base – quelli che hanno database di nomi di luoghi e nomi di strade, e quelli che non lo fanno.

Un parser per gli indirizzi stradali con espressioni regolari può arrivare a una percentuale di successo del 95% senza troppi problemi. Quindi inizi a colpire i casi insoliti. Quello di Perl in CPAN, “Geo :: StreetAddress :: US”, parla di questo bene. Ci sono le porte Python e Javascript di questo, tutto open source. Ho una versione migliorata in Python che sposta leggermente il tasso di successo gestendo più casi. Per ottenere l’ultimo 3% giusto, però, hai bisogno di database per aiutare con il chiarimento delle ambiguità.

Un database con codici postali a 3 cifre e nomi e abbreviazioni degli Stati Uniti è di grande aiuto. Quando un parser vede un codice postale e un nome di stato coerenti, può iniziare a bloccarsi sul formato. Funziona molto bene per gli Stati Uniti e il Regno Unito.

L’analisi degli indirizzi stradali corretta inizia dalla fine e funziona all’indietro. Ecco come lo fanno i sistemi USPS. Gli indirizzi sono meno ambigui alla fine, dove nomi di paesi, nomi di città e codici postali sono relativamente facili da riconoscere. I nomi delle strade di solito possono essere isolati. Le posizioni sulle strade sono le più complesse da analizzare; lì incontri cose come “Fifth Floor” e “Staples Pavillion”. Questo è quando un database è di grande aiuto.

libpostal: una libreria open source per analizzare gli indirizzi, la formazione con i dati di OpenStreetMap, OpenAddresses e OpenCage.

https://github.com/openvenues/libpostal ( maggiori informazioni a riguardo )

Altri strumenti / servizi:

AGGIORNAMENTO: Geocode.xyz ora funziona in tutto il mondo. Per esempi vedi https://geocode.xyz

Per USA, Messico e Canada, vedi geocoder.ca .

Per esempio:

Input: qualcosa in corso vicino all’incrocio tra la principale e l’arthur, uccidi rd new york

Produzione:

  40.5123510000 -74.2500500000 347,718 America/New_York  main arthur kill   STATEN ISLAND NY 11385 0.9   

Puoi anche controllare i risultati nell’interfaccia web o ottenere l’output come Json o Jsonp. per esempio. Sto cercando ristoranti nei pressi di 123 Main Street, New York

Nessun codice? Per vergogna!

Ecco un semplice parser di indirizzo JavaScript. È piuttosto orribile per ogni singola ragione che Matt dà nella sua tesi sopra (che sono quasi al 100% d’accordo: gli indirizzi sono tipi complessi, e gli umani commettono errori, meglio esternalizzarli e automatizzarli – quando puoi permetterti di farlo).

Ma piuttosto che piangere, ho deciso di provare:

Questo codice funziona findAddressCandidate per analizzare la maggior parte dei risultati Esri per findAddressCandidate e anche con altri geocoder (inversi) che restituiscono l’indirizzo a riga singola in cui strada / città / stato sono delimitate da virgole. È ansible estendere se si desidera o scrivere parser specifici per Paese. O semplicemente usa questo come caso di studio su quanto può essere difficile questo esercizio o su quanto io sia pessimo su JavaScript. Ammetto di aver speso circa trenta minuti su questo (le future iterazioni potrebbero aggiungere cache, validazione zip e ricerche di stato oltre al contesto di posizione dell’utente), ma ha funzionato per il mio caso d’uso: l’utente finale vede il modulo che analizza la risposta di ricerca geocodifica in 4 caselle di testo. Se l’analisi degli indirizzi risulta errata (il che è raro a meno che i dati di origine fossero scarsi) non è un grosso problema – l’utente deve verificarlo e correggerlo! (Ma per le soluzioni automatizzate è ansible scartare / ignorare o contrassegnare come errore in modo che dev possa supportare il nuovo formato o correggere i dati di origine).

 /* 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); 
   

In uno dei nostri progetti abbiamo usato il seguente parser di indirizzi. Analizza gli indirizzi per la maggior parte dei paesi del mondo con una buona precisione.

http://address-parser.net/

È disponibile come libreria autonoma o come API live.

Se si desidera fare affidamento sui dati OSM, libpostal è molto potente e gestisce molti dei problemi più comuni con gli input di indirizzo.