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


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 el usuario escribe puede no ser correcta o en un formato estándar
  • La dirección debe estar separada en partes (calle, ciudad, estado, etc.) para procesar pagos con tarjeta de crédito
  • Los usuarios pueden introducir algo más que su dirección (como su nombre o empresa con ella)
  • Google puede haga esto, pero los Términos de servicio y los límites de consulta son prohibitivos, especialmente en un presupuesto ajustado

Aparentemente, esta es una pregunta común:

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

Author: Community, 2012-06-22

7 answers

Vi esta pregunta mucho cuando trabajé para una empresa de verificación de direcciones. Estoy publicando la respuesta aquí para hacerla más accesible a los programadores que están buscando con 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 simple expresiones regulares que coinciden con direcciones en un formato muy específico, a esto:

/ \ s+(\d{2,5}\s+) (?![a/p] m \ b) (([a -A-Z / \ s+]{1,5}){1,2})?([\s|\,|.]+)?(([a -A-Z/ \ s+]{1,30}){1,4})(court / ct / street / st / drive / dr / lane / ln / road / rd|blvd)([\s/\,/.|\;]+)?(([a -A-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 este donde un archivo de clase de línea 900+ genera un 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 hacer que esto funcione. En teoría y por teoría, no es posible hacer coincidir direcciones con una expresión regular.

La publicación USPS 28 documenta los muchos formatos de direcciones 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 "Santo" o "Calle") y hay palabras que estoy bastante seguro que inventaron. (¿Quién sabía que "Stravenue" era un sufijo de calle?)

Necesitas algún código que realmente entienda las direcciones, y si ese código existe, es un secreto comercial. Pero probablemente podrías rodar la tuya si realmente estás en eso.

Las direcciones vienen en formas y tamaños inesperados

Aquí hay algunos artificiosos (pero completos) direcciones:

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 un estado. Con esa información, hay suficiente identificar la dirección, y se puede considerar "entregable" (con cierta estandarización).

  2. El número 2 está completo porque también contiene una dirección (con número secundario / 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 privada o corporación ha comprado ese espacio de direcciones. Un código postal único es para espacios de entrega de gran volumen o concentrados. Nada dirigido al código postal 12345 va a General Electric en Schenectady, NY. Este ejemplo no llegará a nadie en particular, pero el USPS aún podría entregarlo.

  5. El número 5 también está completo, lo creas o no. Con solo esos números, la dirección completa se puede descubrir cuando se analiza contra 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. Aca cómo se ve, completamente expandido y estandarizado:

205 N 1105 W Apt 14

Beverly Hills CA 90210-5221

Los datos de dirección no son suyos

En la mayoría de los países que proporcionan datos de direcciones oficiales a proveedores con licencia, los datos de direcciones en sí pertenecen a la agencia gobernante. En los EE.UU., el USPS posee las direcciones. Lo mismo es cierto para Canada Post, Royal Mail y otros, aunque cada país impone o define la propiedad un poco diferente. Saber esto es importante, ya que generalmente prohíbe la ingeniería inversa de la base de datos de direcciones. Usted tiene que tener cuidado de cómo adquirir, almacenar y utilizar los datos.

Google Maps es una opción común para correcciones 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 con fines no comerciales (a menos que pague), y no puede almacenar los datos (excepto para el almacenamiento temporal en caché). Tiene sentido. Google los datos son algunos de los mejores del mundo. Sin embargo, Google Maps no verifica la dirección. Si una dirección no existe, todavía le mostrará dónde estaría la dirección si existiera (pruébela en su propia calle; use un número de casa que sepa que no existe). Esto es útil a veces, pero tenga en cuenta que.

La política de uso de Nominatim es similarmente limitante, especialmente para uso comercial y de alto volumen, y los datos se extraen principalmente de fuentes libres, por lo que no está tan bien mantenido (tal es la naturaleza de los proyectos abiertos) however sin embargo, esto aún puede satisfacer sus necesidades. Es apoyado por una gran comunidad.

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

Personas espere 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 UX en Internet sobre esto, pero el hecho es que, si tienes un formulario de dirección con campos individuales, eso es lo que esperan los usuarios, a pesar de que hace que sea más difícil para las direcciones de casos extremos que no se ajustan al formato que el formulario espera, o tal vez el formulario requiere un campo que no debería. dirección.

Podría seguir y seguir sobre la mala experiencia de usuario de los formularios de pago en estos días, pero en su lugar solo diré que la combinación de las direcciones en un solo campo será un cambio bienvenido people la gente será capaz de escribir su dirección como mejor le parezca, en lugar de tratar de averiguar su largo formulario. Sin embargo, este cambio será inesperado y los usuarios pueden encontrarlo un poco discordante al principio. Sólo sé consciente de eso.

Parte de este dolor se puede aliviar poniendo el campo del país en el frente, antes de la dirección. Cuando completen el campo de país primero, sabes cómo hacer que aparezca tu 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, mostrar los campos de componentes. ¡Solo cosas en las que 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 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 a menudo no requieren un acuerdo con los términos limitantes como se discutió anteriormente.

Hay muchas empresas certificadas por CASS que pueden procesar listas o tener API: Melissa Data, Experian QAS y SmartyStreets, por nombrar algunas.

(Debido a recibir críticas por "publicidad", he truncado mi respuesta en este punto. Es depende de usted encontrar una solución que funcione para usted.)

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

 234
Author: Matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-02 01:16:42

Hay muchos analizadores de direcciones. 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 una tasa de éxito de hasta un 95% sin muchos problemas. Entonces empiezas a golpear los casos inusuales. El de Perl en CPAN, "Geo:: streetAddress:: US", es sobre eso bueno. Hay puertos Python y Javascript de eso, todos de código abierto. Tengo una versión mejorada en Python que se mueve la tasa de éxito aumentó ligeramente al manejar más casos. Para obtener el último 3% correcto, sin embargo, 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 los Estados Unidos es una gran ayuda. Cuando un analizador ve un código postal y un nombre de estado consistentes, puede comenzar a bloquear el formato. Esto funciona muy bien para los EE.UU. y el Reino Unido.

El análisis correcto de la dirección de la calle comienza desde el final y funciona hacia atrás. Así es como lo hacen los sistemas de USPS. Las direcciones son menos ambiguas al final, donde los nombres de los países, los nombres de las ciudades y los códigos postales son relativamente fáciles de reconocer. Los nombres de las calles generalmente pueden ser aislados. Las ubicaciones en las calles son las más complejas de analizar; allí se encuentran cosas como" Quinto Piso "y"Staples Pavillion". Es entonces cuando una base de datos es una gran ayuda.

 10
Author: John Nagle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-05 05:25:59

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:

 10
Author: David Portabella,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-11 08:34:28

ACTUALIZACIÓN: Geocódigo.xyz ahora funciona en todo el mundo. Para ejemplos ver https://geocode.xyz

Para los estados UNIDOS, México y Canadá, ver geocoder.ca.

Por ejemplo:

Entrada: algo está pasando cerca de la intersección de main y arthur kill rd nueva 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 comprobar los resultados en la interfaz web o obtener la salida como Json o Jsonp. eg. Estoy buscando restaurantes alrededor de 123 Main Street, Nueva York

 8
Author: Ervin Ruci,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-25 20:29:36

¿No hay código? ¡Qué vergüenza!

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

Pero en lugar de llorar, decidí intentarlo:

Este código funciona bien para analizar la mayoría de los resultados de Esri para findAddressCandidate y también con algunos otros geocodificadores (inversos) que devuelven dirección de una sola línea donde la calle/ciudad/estado están delimitados por comas. Puede extender si lo desea o escribir analizadores específicos del país. O simplemente use esto como estudio de caso de lo difícil 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 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 un formulario que analiza la respuesta de búsqueda geocodificada en 4 cuadros de texto. Si el análisis de direcciones sale mal (lo cual es raro a menos que los datos de origen sean pobres) no es gran cosa - ¡el usuario puede verificarlo y arreglarlo! (Pero para soluciones automatizadas podría descartar / ignorar o marcar como error para que dev pueda 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>
 1
Author: nothingisnecessary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-15 19:05:08

En uno de nuestros proyectos hemos utilizado el siguiente analizador de direcciones. Analiza las direcciones de 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.

 0
Author: Waqas Anwar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-16 11:40:37

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

 0
Author: Vitor Magalhães,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-28 13:22:09