Cómo analizar la dirección postal / calle de forma libre fuera del texto y en componentes

136

Hacemos negocios principalmente en los Estados Unidos y estamos tratando de mejorar la experiencia del usuario combinando todos los campos de dirección en un solo área de texto. Pero hay algunos problemas:

  • La dirección que escribe el usuario puede no ser correcta o estar en un formato estándar
  • La dirección debe estar separada en partes (calle, ciudad, estado, etc.) para procesar los pagos con tarjeta de crédito
  • Los usuarios pueden ingresar más que solo su dirección (como su nombre o compañía con ella)
  • Google puede hacer esto, pero los Términos de servicio y los límites de consulta son prohibitivos, especialmente con un presupuesto ajustado

Aparentemente, esta es una pregunta común:

¿Hay alguna manera de aislar una dirección del texto que lo rodea y dividirla en pedazos? ¿Existe una expresión regular para analizar direcciones?

Mate
fuente
Las respuestas a continuación son más útiles porque no ignoran el problema global: las direcciones no se ajustan a un patrón común.
Marc Maxmeister

Respuestas:

290

Vi esta pregunta mucho cuando trabajaba para una compañía de verificación de direcciones. Estoy publicando la respuesta aquí para que sea más accesible para los programadores que están buscando la misma pregunta. La compañía en la que estaba procesó miles de millones de direcciones, y aprendimos mucho en el proceso.

Primero, necesitamos entender algunas cosas sobre las direcciones.

Las direcciones no son regulares.

Esto significa que las expresiones regulares están fuera. Lo he visto todo, desde simples expresiones regulares que coinciden con direcciones en un formato muy específico, hasta esto:

/ \ 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 | calle | st | unidad | dr | carril | ln | carretera | 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

... a esto donde un archivo de clase de línea 900+ genera una expresión regular supermasiva sobre la marcha para que coincida aún más. No recomiendo estos (por ejemplo, aquí hay un violín de la expresión regular anterior, que comete muchos errores ). No hay una fórmula mágica fácil para que esto funcione. En teoría y por teoría, no es posible hacer coincidir direcciones con una expresión regular.

La Publicación 28 de USPS documenta los diversos formatos de direcciones que son posibles, con todas sus palabras clave y variantes. Lo peor de todo, las direcciones son a menudo ambiguas. Las palabras pueden significar más de una cosa ("St" puede ser "Saint" o "Street") y hay palabras que estoy bastante seguro de que inventaron. (¿Quién sabía que "Stravenue" era un sufijo callejero?)

Necesitaría un código que realmente comprenda las direcciones, y si ese código existe, es un secreto comercial. Pero probablemente podrías rodar el tuyo si realmente te gusta eso.

Las direcciones vienen en formas y tamaños inesperados

Aquí hay algunas direcciones artificiales (pero completas):

1)  102 main street
    Anytown, state

2)  400n 600e #2, 52173

3)  p.o. #104 60203

Incluso estos son posiblemente válidos:

4)  829 LKSDFJlkjsdflkjsdljf Bkpw 12345

5)  205 1105 14 90210

Obviamente, estos no están estandarizados. La puntuación y los saltos de línea no están garantizados. Esto es lo que está pasando:

  1. El número 1 está completo porque contiene una dirección y una ciudad y estado. Con esa información, hay suficiente identificación de la dirección, y puede considerarse "entregable" (con cierta estandarización).

  2. El número 2 está completo porque también contiene una dirección (con número secundario / de unidad) y un código postal de 5 dígitos, que es suficiente para identificar una dirección.

  3. El número 3 es un formato completo de apartado postal, ya que contiene un código postal.

  4. El número 4 también está completo porque el código postal es único , lo que significa que una entidad o corporación privada ha comprado ese espacio de direcciones. Un código postal único es para espacios de entrega de gran volumen o concentrados. Cualquier cosa dirigida al código postal 12345 va a General Electric en Schenectady, NY. Este ejemplo no llegará a nadie en particular, pero USPS aún podría entregarlo.

  5. El número 5 también está completo, lo creas o no. Con solo esos números, se puede descubrir la dirección completa cuando se analiza en una base de datos de todas las direcciones posibles. Completar los direccionales faltantes, el designador secundario y el código ZIP + 4 es trivial cuando ve cada número como un componente. Así es como se ve, completamente expandido y estandarizado:

205 N 1105 W Apto 14

Beverly Hills CA 90210-5221

Los datos de la dirección no son tuyos

En la mayoría de los países que proporcionan datos de direcciones oficiales a proveedores con licencia, los datos de direcciones pertenecen a la agencia gubernamental. En los Estados Unidos, el USPS posee las direcciones. Lo mismo es cierto para Canada Post, Royal Mail y otros, aunque cada país hace cumplir o define la propiedad de manera un poco diferente. Saber esto es importante, ya que generalmente prohíbe la ingeniería inversa de la base de datos de direcciones. Debe tener cuidado de cómo adquirir, almacenar y usar los datos.

Google Maps es un recurso común para soluciones rápidas de direcciones, pero el TOS es bastante prohibitivo; por ejemplo, no puede usar sus datos o API sin mostrar un mapa de Google, y solo para fines no comerciales (a menos que pague), y no puede almacenar los datos (excepto para el almacenamiento en caché temporal). Tiene sentido. Los datos de Google son algunos de los mejores del mundo. Sin embargo, Google Maps no verifica la dirección. Si no existe una dirección, aún le mostrará dónde estaría la dirección si lo hiciera existe (probarlo en su propia calle, el uso de un número de casa que sabes no existe). Esto es útil a veces, pero ten en cuenta eso.

La política de uso de Nominatim es similarmente limitante, especialmente para un gran volumen y uso comercial, y los datos se obtienen principalmente de fuentes gratuitas, por lo que no están tan bien mantenidos (tal es la naturaleza de los proyectos abiertos); sin embargo, esto puede ser adecuado tus necesidades. Es apoyado por una gran comunidad.

El propio USPS tiene una API, pero se cae mucho y viene sin garantías ni soporte. También puede ser difícil de usar. Algunas personas lo usan con moderación sin problemas. Pero es fácil pasar por alto que USPS requiere que use su API solo para confirmar las direcciones para enviarlas.

La gente espera que las direcciones sean difíciles

Desafortunadamente, hemos condicionado a nuestra sociedad a esperar que las direcciones sean complicadas. Hay docenas de buenos artículos de experiencia de usuario en todo Internet sobre esto, pero el hecho es que, si tiene un formulario de dirección con campos individuales, eso es lo que esperan los usuarios, a pesar de que dificulta las direcciones de casos extremos que no se ajustan al formatee el formulario que espera, o tal vez el formulario requiere un campo que no debería. O los usuarios no saben dónde poner una determinada parte de su dirección.

Podría seguir y seguir sobre la mala experiencia de usuario de los formularios de pago en estos días, pero en cambio solo diré que combinar las direcciones en un solo campo será un cambio bienvenido : las personas podrán escribir su dirección como mejor les parezca , en lugar de tratar de averiguar su forma larga. Sin embargo, este cambio será inesperado y los usuarios pueden encontrarlo un poco discordante al principio. Solo ten cuidado con eso.

Parte de este dolor se puede aliviar colocando el campo del país al frente, antes de la dirección. Cuando primero completan el campo del país, usted sabe cómo hacer que aparezca su formulario. Tal vez tenga una buena manera de tratar con direcciones de EE. UU. De un solo campo, por lo que si seleccionan Estados Unidos, puede reducir su formulario a un solo campo, de lo contrario, muestre los campos componentes. ¡Solo cosas para pensar!

Ahora sabemos por qué es difícil; ¿Qué puedes hacer al respecto?

El USPS otorga licencias a los proveedores a través de un proceso llamado Certificación CASS ™ para proporcionar direcciones verificadas a los clientes. Estos proveedores tienen acceso a la base de datos de USPS, actualizada mensualmente. Su software debe cumplir con estándares rigurosos para ser certificado, y no requieren a menudo el acuerdo con términos tan limitados como se discutió anteriormente.

Hay muchas compañías con certificación CASS que pueden procesar listas o tener API: Melissa Data, Experian QAS y SmartyStreets, por nombrar algunas.

(Debido a las críticas por la "publicidad", he truncado mi respuesta en este momento. Depende de usted encontrar una solución que funcione para usted).

La verdad: amigos, no trabajo en ninguna de estas compañías. No es un anuncio publicitario.

Mate
fuente
1
¿Qué pasa con las direcciones sudamericanas (Uruguay)? : D
Bart Calixto
11
@Brian - Quizás porque el usuario ha proporcionado mucha información útil para quienes leen la pregunta y la respuesta, independientemente de si eligen usar o no el producto de su empresa.
Zarepheth
77
@Brian Esos sitios son raspadores de contenido. Están obteniendo contenido para obtener clasificaciones SERP. Nunca los he visto antes. Nunca he publicado este contenido antes o después en ningún otro lugar.
Matt
2
@khuderm Me di cuenta justo ahora cuando leí tu comentario que todos los comentarios discrepantes han desaparecido; No estoy seguro de cómo / cuándo sucedió eso. Pero de todos modos, vea el historial de edición de mi respuesta y encontrará una referencia directa a un extractor de direcciones de EE. UU. Que podría ayudarlo. Lo construí cuando trabajé en mi último trabajo, pero es un código propietario, así que no puedo compartirlo ... pero existen. Espero sea de ayuda.
Matt
2
Ups Lo siento @ Matt. Bueno, he comenzado a seguirte a través de tus preguntas y también a Github. Muy impresionante eres.
Sayka
28

libpostal: una biblioteca de código abierto para analizar direcciones, entrenamiento con datos de OpenStreetMap, OpenAddresses y OpenCage.

https://github.com/openvenues/libpostal ( más información al respecto )

Otras herramientas / servicios:

David Portabella
fuente
13

Hay muchos analizadores de direcciones de calles. Vienen en dos sabores básicos: los que tienen bases de datos de nombres de lugares y nombres de calles, y los que no.

Un analizador de direcciones de calle de expresión regular puede obtener hasta un 95% de éxito sin muchos problemas. Entonces comienzas a golpear los casos inusuales. El de Perl en CPAN, "Geo :: StreetAddress :: US", es así de bueno. Hay puertos Python y Javascript de eso, todos de código abierto. Tengo una versión mejorada en Python que aumenta ligeramente la tasa de éxito al manejar más casos. Sin embargo, para obtener el último 3% correcto, necesita bases de datos para ayudar con la desambiguación.

Una base de datos con códigos postales de 3 dígitos y nombres y abreviaturas de estados de EE. UU. Es de gran ayuda. Cuando un analizador ve un código postal y un nombre de estado coherentes, puede comenzar a bloquear el formato. Esto funciona muy bien para los Estados Unidos y el Reino Unido.

El análisis adecuado de la dirección de la calle comienza desde el final y funciona al revés. Así es como lo hacen los sistemas USPS. Las direcciones son menos ambiguas al final, donde los nombres de países, ciudades y códigos postales son relativamente fáciles de reconocer. Los nombres de las calles generalmente pueden aislarse. Las ubicaciones en las calles son las más complejas para analizar; allí te encuentras con cosas como "Fifth Floor" y "Staples Pavillion". Es entonces cuando una base de datos es de gran ayuda.

John Nagle
fuente
También está el módulo CPAN Lingua: EN :: AddressParse. Si bien es más lento que "Geo :: StreetAddress :: US, ofrece una mayor tasa de éxito.
Kim Ryan
8

ACTUALIZACIÓN: Geocode.xyz ahora funciona en todo el mundo. Para ver ejemplos, consulte https://geocode.xyz

Para EE. UU., México y Canadá, consulte geocoder.ca .

Por ejemplo:

Entrada: algo sucede cerca de la intersección de main y arthur kill rd new york

Salida:

<geodata>
  <latt>40.5123510000</latt>
  <longt>-74.2500500000</longt>
  <AreaCode>347,718</AreaCode>
  <TimeZone>America/New_York</TimeZone>
  <standard>
    <street1>main</street1>
    <street2>arthur kill</street2>
    <stnumber/>
    <staddress/>
    <city>STATEN ISLAND</city>
    <prov>NY</prov>
    <postal>11385</postal>
    <confidence>0.9</confidence>
  </standard>
</geodata>

También puede verificar los resultados en la interfaz web u obtener resultados como Json o Jsonp. p.ej. Estoy buscando restaurantes en 123 Main Street, Nueva York

Ervin Ruci
fuente
¿Cómo implementó el sistema de análisis de direcciones usando openaddress? ¿Estás usando la estrategia de fuerza bruta?
Nithin K Anil
1
¿Qué quieres decir con "fuerza bruta"? No es práctico dividir el texto en todas las combinaciones posibles de cadenas de direcciones posibles y comparar cada una con una base de datos de direcciones y tomará más tiempo proporcionar una respuesta que este sistema. Las direcciones abiertas son una de las fuentes de datos para construir un 'conjunto de entrenamiento' de formatos de dirección para el algoritmo. Utiliza esta información para analizar direcciones de texto no estructurado.
Ervin Ruci
2
Otro sistema similar es Geo :: libpostal ( perltricks.com/article/announcing-geo--libpostal ) También usan openstreetmap y openaddresses, al parecer, para construir plantillas de direcciones sobre la marcha
Ervin Ruci
Acabo de probar el geoparser de geocode.xyz (enviar texto, recuperar ubicación) en cientos de direcciones reales. Dado un lado a lado con la API de google map y un conjunto global de direcciones, geocode.xyzel scantextmétodo falló la mayor parte del tiempo. Siempre eligió "Ginebra, EE. UU." Sobre "Ginebra, Suiza" y, en general, estaba sesgado por EE. UU.
Marc Maxmeister
Depende del contexto. geocode.xyz/?scantext=Geneva,%20Switzerland producirá: Match Location Ginebra, Suiza, CH Confianza Puntuación: 0.8 mientras que geocode.xyz/?scantext=Geneva,%20USA producirá Match Location Ginebra, US Confianza Puntuación: 1.0 También, puede sesgar la región de la siguiente manera: geocode.xyz/?scantext=Geneva,%20USA®ion=CH
Ervin Ruci
4

¿Sin código? ¡Para vergüenza!

Aquí hay un simple analizador de direcciones JavaScript. Es bastante horrible por cada razón que Matt da en su disertación anterior (con la que estoy casi 100% de acuerdo: las direcciones son tipos complejos y los humanos cometen errores; es mejor externalizar y automatizar esto, cuando puede permitírselo).

Pero en lugar de llorar, decidí probar:

Este código funciona bien para analizar la mayoría de los resultados de Esri parafindAddressCandidatey también con algunos otros geocodificadores (inversos) que devuelven direcciones de una sola línea donde la calle / ciudad / estado están delimitadas por comas. Puede ampliar si lo desea o escribir analizadores específicos de país. O simplemente use esto como estudio de caso de lo desafiante que puede ser este ejercicio o de lo pésimo que soy en JavaScript. Admito que solo pasé unos treinta minutos en esto (las iteraciones futuras podrían agregar cachés, validación de zip y búsquedas de estado, así como el contexto de ubicación del usuario), pero funcionó para mi caso de uso: el usuario final ve el formulario que analiza la respuesta de búsqueda de geocodificación en 4 cajas de texto. Si el análisis de direcciones sale mal (lo cual es raro a menos que los datos de origen sean deficientes) no es gran cosa: ¡el usuario puede verificarlo y solucionarlo! (Pero las soluciones automatizadas podrían descartar / ignorar o marcar como error, por lo que el desarrollador puede admitir el nuevo formato o corregir los datos de origen).

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

nada es necesario
fuente
descargo de responsabilidad: mis clientes poseen sus datos de dirección y ejecutan sus propios servidores Esri. Si obtiene datos de google, OSM, ArcGisOnline, o donde sea, asegúrese de que está bien almacenarlos y usarlos (muchos servicios tienen restricciones sobre cómo puede almacenarlos y por cuánto tiempo)
nada es necesario el
La primera respuesta anterior presenta un caso convincente de que este problema no se puede resolver con expresiones regulares si se trata de una lista global de direcciones. 200 países tienen demasiadas excepciones. En mis pruebas, puede determinar el país a partir de una cadena de manera bastante confiable, luego buscar una expresión regular específica para cada país, que es probablemente la forma en que funcionan las mejores API.
Marc Maxmeister el
2

Si desea confiar en los datos de OSM, libpostal es muy potente y maneja muchas de las advertencias más comunes con entradas de dirección.

Vitor Magalhães
fuente
Creo que tu respuesta es un duplicado de esta publicación. Buena sugerencia, sin embargo.
Michael
2

Otra opción para las direcciones basadas en los EE. UU. Es YAddress (hecha por la compañía para la que trabajo).

Muchas respuestas a esta pregunta sugieren herramientas de geocodificación como solución. Es importante no confundir el análisis de direcciones y la codificación geográfica; Ellos no son los mismos. Si bien los geocodificadores pueden dividir una dirección en componentes como un beneficio adicional, por lo general dependen de conjuntos de direcciones no estándar. Esto significa que una dirección analizada por geocodificador puede no ser la misma que la dirección oficial. Por ejemplo, lo que la API de geocodificación de Google llama "6th Ave" en Manhattan, USPS llama "Avenue of the Americas".

Michael Diomin
fuente
2

Para el análisis de direcciones de EE. UU.,

Prefiero usar el paquete usaddress que está disponible en pip solo para usaddress

python3 -m pip install usaddress

Documentación
PyPi

Esto funcionó bien para mí para la dirección de EE. UU.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# address_parser.py
import sys
from usaddress import tag
from json import dumps, loads

if __name__ == '__main__':
    tag_mapping = {
        'Recipient': 'recipient',
        'AddressNumber': 'addressStreet',
        'AddressNumberPrefix': 'addressStreet',
        'AddressNumberSuffix': 'addressStreet',
        'StreetName': 'addressStreet',
        'StreetNamePreDirectional': 'addressStreet',
        'StreetNamePreModifier': 'addressStreet',
        'StreetNamePreType': 'addressStreet',
        'StreetNamePostDirectional': 'addressStreet',
        'StreetNamePostModifier': 'addressStreet',
        'StreetNamePostType': 'addressStreet',
        'CornerOf': 'addressStreet',
        'IntersectionSeparator': 'addressStreet',
        'LandmarkName': 'addressStreet',
        'USPSBoxGroupID': 'addressStreet',
        'USPSBoxGroupType': 'addressStreet',
        'USPSBoxID': 'addressStreet',
        'USPSBoxType': 'addressStreet',
        'BuildingName': 'addressStreet',
        'OccupancyType': 'addressStreet',
        'OccupancyIdentifier': 'addressStreet',
        'SubaddressIdentifier': 'addressStreet',
        'SubaddressType': 'addressStreet',
        'PlaceName': 'addressCity',
        'StateName': 'addressState',
        'ZipCode': 'addressPostalCode',
    }
    try:
        address, _ = tag(' '.join(sys.argv[1:]), tag_mapping=tag_mapping)
    except:
        with open('failed_address.txt', 'a') as fp:
            fp.write(sys.argv[1] + '\n')
        print(dumps({}))
    else:
        print(dumps(dict(address)))

Ejecutando address_parser.py

 python3 address_parser.py 9757 East Arcadia Ave. Saugus MA 01906
 {"addressStreet": "9757 East Arcadia Ave.", "addressCity": "Saugus", "addressState": "MA", "addressPostalCode": "01906"}
theBuzzyCoder
fuente
0

En uno de nuestros proyectos, hemos utilizado el siguiente analizador de direcciones. Analiza direcciones para la mayoría de los países del mundo con buena precisión.

http://address-parser.net/

Está disponible como biblioteca independiente o como API en vivo.

Waqas Anwar
fuente
1
Pero es un producto pagado.
Jeremy Thompson
0

Llego tarde a la fiesta, aquí hay un script Excel VBA que escribí hace años para Australia. Se puede modificar fácilmente para admitir otros países. He hecho un repositorio GitHub del código C # aquí. Lo he alojado en mi sitio y puedes descargarlo aquí: http://jeremythompson.net/rocks/ParseAddress.xlsm

Estrategia

Para cualquier país con un código postal que sea numérico o que pueda combinarse con un RegEx, mi estrategia funciona muy bien:

  1. Primero detectamos el Primero y el Apellido, que se supone que son la línea superior. Es fácil omitir el nombre y comenzar con la dirección desmarcando la casilla de verificación (llamada 'Nombre es la fila superior' como se muestra a continuación).

  2. A continuación, es seguro esperar que la Dirección que consiste en la Calle y el Número se presenten antes de que el Suburbio y el St, Pde, Ave, Av, Rd, Cres, loop, etc. sean un separador.

  3. Detectar el suburbio frente al estado e incluso el país puede engañar a los analizadores más sofisticados ya que puede haber conflictos. Para superar esto, utilizo una búsqueda de PostCode basada en el hecho de que después de eliminar los números de Calle y Apartamento / Unidad, así como el PoBox, Ph, Fax , Mobile, etc., solo quedará el número de PostCode. Esto es fácil de combinar con un regEx para luego buscar los suburbios y el país.

Su Servicio Nacional de Correos le proporcionará una lista de códigos postales con Suburbios y Estados de forma gratuita que puede almacenar en una hoja de Excel, tabla de base de datos, archivo de texto / json / xml, etc.

  1. Finalmente, dado que algunos códigos postales tienen múltiples suburbios, verificamos qué suburbio aparece en la dirección.

Ejemplo

ingrese la descripción de la imagen aquí

Código VBA

DESCARGO DE RESPONSABILIDAD, sé que este código no es perfecto, ni siquiera está bien escrito, sin embargo, es muy fácil de convertir a cualquier lenguaje de programación y ejecutar en cualquier tipo de aplicación. La estrategia es la respuesta dependiendo de su país y reglas, tome este código como ejemplo :

Option Explicit

Private Const TopRow As Integer = 0

Public Sub ParseAddress()
Dim strArr() As String
Dim sigRow() As String
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim Stat As String
Dim SpaceInName As Integer
Dim Temp As String
Dim PhExt As String

On Error Resume Next

Temp = ActiveSheet.Range("Address")

'Split info into array
strArr = Split(Temp, vbLf)

'Trim the array
For i = 0 To UBound(strArr)
strArr(i) = VBA.Trim(strArr(i))
Next i

'Remove empty items/rows    
ReDim sigRow(LBound(strArr) To UBound(strArr))
For i = LBound(strArr) To UBound(strArr)
    If Trim(strArr(i)) <> "" Then
        sigRow(j) = strArr(i)
        j = j + 1
    End If
Next i
ReDim Preserve sigRow(LBound(strArr) To j)

'Find the name (MUST BE ON THE FIRST ROW UNLESS CHECKBOX UNTICKED)
i = TopRow
If ActiveSheet.Shapes("chkFirst").ControlFormat.Value = 1 Then

SpaceInName = InStr(1, sigRow(i), " ", vbTextCompare) - 1

If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
ActiveSheet.Range("FirstName") = VBA.Left(sigRow(i), SpaceInName)
Else
 If MsgBox("First Name: " & VBA.Mid$(sigRow(i), 1, SpaceInName), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("FirstName") = VBA.Left(sigRow(i), SpaceInName)
End If

If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
ActiveSheet.Range("Surname") = VBA.Mid(sigRow(i), SpaceInName + 2)
Else
  If MsgBox("Surame: " & VBA.Mid(sigRow(i), SpaceInName + 2), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Surname") = VBA.Mid(sigRow(i), SpaceInName + 2)
End If
sigRow(i) = ""
End If

'Find the Street by looking for a "St, Pde, Ave, Av, Rd, Cres, loop, etc"
For i = 1 To UBound(sigRow)
If Len(sigRow(i)) > 0 Then
    For j = 0 To 8
    If InStr(1, VBA.UCase(sigRow(i)), Street(j), vbTextCompare) > 0 Then

    'Find the position of the street in order to get the suburb
    SpaceInName = InStr(1, VBA.UCase(sigRow(i)), Street(j), vbTextCompare) + Len(Street(j)) - 1

    'If its a po box then add 5 chars
    If VBA.Right(Street(j), 3) = "BOX" Then SpaceInName = SpaceInName + 5

    If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
    ActiveSheet.Range("Street") = VBA.Mid(sigRow(i), 1, SpaceInName)
    Else
      If MsgBox("Street Address: " & VBA.Mid(sigRow(i), 1, SpaceInName), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Street") = VBA.Mid(sigRow(i), 1, SpaceInName)
    End If
    'Trim the Street, Number leaving the Suburb if its exists on the same line
    sigRow(i) = VBA.Mid(sigRow(i), SpaceInName) + 2
    sigRow(i) = Replace(sigRow(i), VBA.Mid(sigRow(i), 1, SpaceInName), "")

    GoTo PastAddress:
    End If
    Next j
End If
Next i
PastAddress:

'Mobile
For i = 1 To UBound(sigRow)
If Len(sigRow(i)) > 0 Then
    For j = 0 To 3
    Temp = Mb(j)
        If VBA.Left(VBA.UCase(sigRow(i)), Len(Temp)) = Temp Then
        If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
        ActiveSheet.Range("Mobile") = VBA.Mid(sigRow(i), Len(Temp) + 2)
        Else
          If MsgBox("Mobile: " & VBA.Mid(sigRow(i), Len(Temp) + 2), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Mobile") = VBA.Mid(sigRow(i), Len(Temp) + 2)
        End If
    sigRow(i) = ""
    GoTo PastMobile:
    End If
    Next j
End If
Next i
PastMobile:

'Phone
For i = 1 To UBound(sigRow)
If Len(sigRow(i)) > 0 Then
    For j = 0 To 1
    Temp = Ph(j)
        If VBA.Left(VBA.UCase(sigRow(i)), Len(Temp)) = Temp Then

            'TODO: Detect the intl or national extension here.. or if we can from the postcode.
            If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
            ActiveSheet.Range("Phone") = VBA.Mid(sigRow(i), Len(Temp) + 3)
            Else
              If MsgBox("Phone: " & VBA.Mid(sigRow(i), Len(Temp) + 3), vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Phone") = VBA.Mid(sigRow(i), Len(Temp) + 3)
            End If

        sigRow(i) = ""
        GoTo PastPhone:
        End If
    Next j
End If
Next i
PastPhone:


'Email
For i = 1 To UBound(sigRow)
    If Len(sigRow(i)) > 0 Then
        'replace with regEx search
        If InStr(1, sigRow(i), "@", vbTextCompare) And InStr(1, VBA.UCase(sigRow(i)), ".CO", vbTextCompare) Then
        Dim email As String
        email = sigRow(i)
        email = Replace(VBA.UCase(email), "EMAIL:", "")
        email = Replace(VBA.UCase(email), "E-MAIL:", "")
        email = Replace(VBA.UCase(email), "E:", "")
        email = Replace(VBA.UCase(Trim(email)), "E ", "")
        email = VBA.LCase(email)

            If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
            ActiveSheet.Range("Email") = email
            Else
              If MsgBox("Email: " & email, vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("Email") = email
            End If
        sigRow(i) = ""
        Exit For
        End If
    End If
Next i

'Now the only remaining items will be the postcode, suburb, country
'there shouldn't be any numbers (eg. from PoBox,Ph,Fax,Mobile) except for the Post Code

'Join the string and filter out the Post Code
Temp = Join(sigRow, vbCrLf)
Temp = Trim(Temp)

For i = 1 To Len(Temp)

Dim postCode As String
postCode = VBA.Mid(Temp, i, 4)

'In Australia PostCodes are 4 digits
If VBA.Mid(Temp, i, 1) <> " " And IsNumeric(postCode) Then

    If ActiveSheet.Shapes("chkConfirm").ControlFormat.Value = 0 Then
    ActiveSheet.Range("PostCode") = postCode
    Else
      If MsgBox("Post Code: " & postCode, vbQuestion + vbYesNo, "Confirm Details") = vbYes Then ActiveSheet.Range("PostCode") = postCode
    End If

    'Lookup the Suburb and State based on the PostCode, the PostCode sheet has the lookup
    Dim mySuburbArray As Range
    Set mySuburbArray = Sheets("PostCodes").Range("A2:B16670")

    Dim suburbs As String
    For j = 1 To mySuburbArray.Columns(1).Cells.Count
    If mySuburbArray.Cells(j, 1) = postCode Then
        'Check if the suburb is listed in the address
        If InStr(1, UCase(Temp), mySuburbArray.Cells(j, 2), vbTextCompare) > 0 Then

        'Set the Suburb and State
        ActiveSheet.Range("Suburb") = mySuburbArray.Cells(j, 2)
        Stat = mySuburbArray.Cells(j, 3)
        ActiveSheet.Range("State") = Stat

        'Knowing the State - for Australia we can get the telephone Ext
        PhExt = PhExtension(VBA.UCase(Stat))
        ActiveSheet.Range("PhExt") = PhExt

        'remove the phone extension from the number
        Dim prePhone As String
        prePhone = ActiveSheet.Range("Phone")
        prePhone = Replace(prePhone, PhExt & " ", "")
        prePhone = Replace(prePhone, "(" & PhExt & ") ", "")
        prePhone = Replace(prePhone, "(" & PhExt & ")", "")
        ActiveSheet.Range("Phone") = prePhone
        Exit For
        End If
    End If
    Next j
Exit For
End If
Next i

End Sub


Private Function PhExtension(ByVal State As String) As String
Select Case State
Case Is = "NSW"
PhExtension = "02"
Case Is = "QLD"
PhExtension = "07"
Case Is = "VIC"
PhExtension = "03"
Case Is = "NT"
PhExtension = "04"
Case Is = "WA"
PhExtension = "05"
Case Is = "SA"
PhExtension = "07"
Case Is = "TAS"
PhExtension = "06"
End Select
End Function

Private Function Ph(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
Ph = "PH"
Case Is = 1
Ph = "PHONE"
'Case Is = 2
'Ph = "P"
End Select
End Function

Private Function Mb(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
Mb = "MB"
Case Is = 1
Mb = "MOB"
Case Is = 2
Mb = "CELL"
Case Is = 3
Mb = "MOBILE"
'Case Is = 4
'Mb = "M"
End Select
End Function

Private Function Fax(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
Fax = "FAX"
Case Is = 1
Fax = "FACSIMILE"
'Case Is = 2
'Fax = "F"
End Select
End Function

Private Function State(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
State = "NSW"
Case Is = 1
State = "QLD"
Case Is = 2
State = "VIC"
Case Is = 3
State = "NT"
Case Is = 4
State = "WA"
Case Is = 5
State = "SA"
Case Is = 6
State = "TAS"
End Select
End Function

Private Function Street(ByVal Num As Integer) As String
Select Case Num
Case Is = 0
Street = " ST"
Case Is = 1
Street = " RD"
Case Is = 2
Street = " AVE"
Case Is = 3
Street = " AV"
Case Is = 4
Street = " CRES"
Case Is = 5
Street = " LOOP"
Case Is = 6
Street = "PO BOX"
Case Is = 7
Street = " STREET"
Case Is = 8
Street = " ROAD"
Case Is = 9
Street = " AVENUE"
Case Is = 10
Street = " CRESENT"
Case Is = 11
Street = " PARADE"
Case Is = 12
Street = " PDE"
Case Is = 13
Street = " LANE"
Case Is = 14
Street = " COURT"
Case Is = 15
Street = " BLVD"
Case Is = 16
Street = "P.O. BOX"
Case Is = 17
Street = "P.O BOX"
Case Is = 18
Street = "PO BOX"
Case Is = 19
Street = "POBOX"
End Select
End Function
Jeremy Thompson
fuente