Como processar o endereço Livre da rua / postal a partir do texto e em componentes

Fazemos negócios em grande parte nos Estados Unidos e estamos a tentar melhorar a experiência do utilizador, combinando todos os campos de endereços numa única área de texto. Mas há alguns problemas:

  • o endereço que os tipos de utilizadores podem não estar correctos ou num formato normalizado
  • o endereço deve ser separado em partes (Rua, Cidade, Estado, etc.) para processar pagamentos com cartão de crédito
  • Os utilizadores podem indicar mais do que apenas o seu endereço (como o seu nome ou empresa com ele)
  • Google pode fazer isso, mas os Termos de Serviço e limites de consulta são proibitivos, especialmente em um orçamento apertado
Aparentemente, esta é uma pergunta comum: Há alguma forma de isolar um endereço do texto à sua volta e quebrá-lo em pedaços? Existe uma expressão regular para processar endereços?

Author: Community, 2012-06-22

7 answers

Vi muito esta pergunta quando trabalhava para uma empresa de verificação de endereços. Estou postando a resposta aqui para torná-la mais acessível para programadores que estão procurando por aí com a mesma pergunta. A empresa em que estive processou milhares de milhões de endereços, e aprendemos muito no processo. Primeiro, precisamos entender algumas coisas sobre endereços.

Os endereços não são regulares

Isto significa que as expressões regulares estão fora. Eu vi tudo, de expressões regulares simples que correspondem a endereços num formato muito específico, a isto:

/ \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})(Tribunal / 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

... para Este em que um ficheiro de classe de linha 900 + gera um expressão regular supermassiva na mosca para combinar ainda mais. Eu não recomendo estes (por exemplo, Aqui está um violino do regex acima, que faz uma abundância de erros). Não há uma fórmula mágica fácil para fazer isto funcionar. Na teoria e na teoria por, não é possível combinar endereços com uma expressão regular.

A publicação 28 da USPS documenta os vários formatos de endereços possíveis, com todas as suas palavras-chave e variantes. O pior de tudo, os endereços são muitas vezes ambíguos. Palavras podem significar mais do que uma coisa ("St" pode ser "santo" ou "rua") e há palavras que tenho certeza que eles inventaram. (Quem sabia que "Stravenue" era um sufixo de rua?)

Precisarias de um código que percebesse as moradas, e se esse código existe, é um segredo comercial. Mas talvez possas rolar o teu próprio, se gostas mesmo disso.

Os endereços vêm em formas e tamanhos inesperados

Aqui estão alguns inventados (mas completos) endereços:
1)  102 main street
    Anytown, state

2)  400n 600e #2, 52173

3)  p.o. #104 60203

Mesmo estes são possivelmente válidos:

4)  829 LKSDFJlkjsdflkjsdljf Bkpw 12345

5)  205 1105 14 90210
Obviamente, estes não são padronizados. Pontuação e quebra de linha não garantida. Eis o que se passa:
  1. O número 1 {[43] } está completo porque contém um endereço de rua e uma cidade e estado. Com essa informação, há suficiente identificação do endereço, e ele pode ser considerado "entregável" (com alguma padronização).

  2. O número 2 está completo porque também contém um endereço de rua (com número secundário / unidade) e um código postal de 5 dígitos, o que é suficiente para identificar um endereço.

  3. O número 3 é um formato postal completo, Uma vez que contém um código postal.

  4. O número 4 também está completo porque o código postal é único , o que significa que uma entidade privada ou corporação comprou esse espaço de endereços. Um código postal único é para espaços de entrega de alto volume ou concentrados. Qualquer endereçado ao Código Postal 12345 vai para a General Electric em Schenectady, NY. Este exemplo não vai chegar a ninguém em particular, mas o USPS ainda seria capaz de entregá-lo.

  5. O número 5 também está completo, acredites ou não. Com apenas esses números, o endereço completo pode ser descoberto quando comparado com um banco de dados de todos os endereços possíveis. Preencher as direções em falta, designador secundário e Código ZIP+4 é trivial quando você vê cada número como um componente. Aqui está. o que parece, totalmente expandido e padronizado:

205 N 1105 W Apt 14

Beverly Hills CA 90210-5221

Os dados do endereço não são seus

Na maioria dos países que fornecem dados de endereços oficiais a fornecedores licenciados, os dados de endereços pertencem à agência governamental. Nos EUA, A USP é dona das moradas. O mesmo é verdade para o Canada Post, Royal Mail e outros, embora cada país impõe ou define a propriedade um pouco diferentemente. Saber isso é importante, uma vez que normalmente proíbe a engenharia reversa da base de dados de endereços. Você tem que ter cuidado como adquirir, armazenar e usar os dados.

Google Maps é comum um go-to para rápida endereço de correções, mas a TOS é bastante proibitivo; por exemplo, você não pode usar os seus dados ou APIs sem mostrar um Mapa do Google, e para utilização não-comercial meramente informativo (a menos que você pagar), e você não pode armazenar os dados (exceto cache temporário). Sentir. Google os dados são dos melhores do mundo. No entanto, o Google Maps não verifica o endereço . Se um endereço não existir, ele ainda lhe mostrará onde o endereço seria se existisse (Experimente - o na sua própria rua; use um número de casa que sabe que não existe). Isto é útil às vezes, mas esteja ciente disso.

A Política de Utilização do Nominatim é igualmente limitada, especialmente no que se refere ao elevado volume e à utilização comercial, e os dados são, na sua maioria, extraídos de fontes livres, por isso não é tão bem mantido (tal é a natureza de projetos abertos) -- no entanto, isso ainda pode atender às suas necessidades. É apoiado por uma grande comunidade.

O próprio USPS tem uma API, mas desce muito e não tem garantias nem apoio. Também pode ser difícil de usar. Algumas pessoas usam-no com moderação sem problemas. Mas é fácil não perceber que a USPS exige que use a API deles apenas para confirmar endereços para enviar através deles.

Pessoas espera que os endereços sejam difíceis

Infelizmente, condicionámos a nossa sociedade a esperar que as moradas sejam complicadas. Há dezenas de boa UX artigos em toda a Internet sobre isso, mas o fato é que, se você tiver um endereço de formulário com campos individuais, que é o que os usuários esperam, mesmo que isso torna mais difícil para a borda, caso os endereços que não se encaixam no formato de formulário está esperando, ou talvez o formulário requer um campo que não deve. Ou os usuários não sabem onde colocar uma parte das suas endereco.

Eu poderia ir sobre e sobre o mau UX de formulários de checkout esses dias, mas em vez disso, eu vou apenas dizer que a combinação dos endereços em um único campo será uma bem-vindo altere-as pessoas serão capazes de digitar seu endereço como eles a vêem, em vez de tentar descobrir a sua longa formulário. No entanto, esta mudança será inesperada e os utilizadores podem achar um pouco chocante no início. Fica ciente disso.

Parte desta dor pode ser aliviada colocando o campo de campo à frente, antes da morada. Quando preencherem o campo de campo primeiro, você sabe como fazer seu formulário aparecer. Talvez você tenha uma boa maneira de lidar com endereços de campo único dos EUA, então se eles selecionarem os Estados Unidos, você pode reduzir seu formulário para um único campo, caso contrário, mostrar os campos componentes. Só coisas para pensar! Agora sabemos porque é difícil, o que podemos fazer? A USPS licencia vendedores através de um processo chamado certificação CASS™ para fornecer endereços verificados para os clientes. Estes fornecedores têm acesso à base de dados USPS, atualizada mensalmente. Seu software deve estar em conformidade com padrões rigorosos para ser certificado, e eles muitas vezes não exigem acordo a termos tão limitantes como discutido acima. Há muitas empresas certificadas pela CASS que podem processar listas ou ter APIs: Melissa Data, Experian QAS, e SmartyStreets para citar algumas.

(devido a ser criticado por "publicidade", truncei a minha resposta neste momento. É ... cabe a você encontrar uma solução que funcione para você.)

A verdade é que não trabalho em nenhuma destas empresas. Não é um anúncio.

 234
Author: Matt, 2015-07-02 01:16:42
Há muitos parsers de endereços de rua. Eles vêm em dois sabores básicos-aqueles que têm bases de dados de nomes de lugares e nomes de rua, e aqueles que não.

Um analisador de endereços de rua de expressão regular pode chegar a uma taxa de sucesso de 95% sem muito problema. Depois começas a tratar dos casos invulgares. O Perl one em CPAN, "Geo:: StreetAddress:: US", é mais ou menos bom. Existem portas Python e Javascript disso, todas de código aberto. Eu tenho uma versão melhorada em Python que move a taxa de sucesso para cima ligeiramente, lidando com mais casos. Para obter os últimos 3% correto, no entanto, você precisa de bancos de dados para ajudar com a desambiguação.

Uma base de dados com códigos postais de 3 dígitos e nomes de estado e abreviaturas dos EUA é uma grande ajuda. Quando um analisador vê um código postal consistente e nome de Estado, ele pode começar a bloquear no formato. Isto funciona muito bem para os EUA e Reino Unido.

A análise do endereço da rua começa do fim e funciona ao contrário. É assim que os sistemas USPS fazem ele. Endereços são menos ambíguos no final, onde nomes de Países, nomes de cidades e códigos postais são relativamente fáceis de reconhecer. Os nomes de rua geralmente podem ser isolados. Locais nas ruas são os mais complexos para analisar; lá você encontra coisas como" quinto andar "e"Staples Pavillion". É quando uma base de dados é uma grande ajuda.

 10
Author: John Nagle, 2015-04-05 05:25:59

Libpostal: uma biblioteca de código aberto para analisar endereços, a treinar com dados do OpenStreetMap, OpenAddresses e OpenCage.

Https://github.com/openvenues/libpostal (mais informações sobre ele)

Outras ferramentas / serviços:

 10
Author: David Portabella, 2017-07-11 08:34:28

Actualização: Geocode.xyz agora trabalha em todo o mundo. Por exemplo, ver https://geocode.xyz

Para os EUA, México e Canadá, Ver geocoder.ca.

Por exemplo:

Passa-se algo perto da intersecção da main e do arthur kill rd Nova Iorque.

Resultado:

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

Também pode verificar os resultados na interface web ou obter resultados como Json ou Jsonp. exemplo. Estou à procura de restaurantes em redor. 123 Main Street, New York

 8
Author: Ervin Ruci, 2018-04-25 20:29:36
Nenhum código? Que vergonha!

Aqui está um simples analisador de endereços JavaScript. É bastante horrível por cada razão que Matt dá em sua dissertação acima (com a qual eu quase 100% concordo: endereços são tipos complexos, e humanos cometem erros; é melhor terceirizar e automatizar isso - quando você pode dar ao luxo de fazê-lo).

Mas em vez de chorar, decidi tentar:

Este código funciona bem para analisar a maioria dos resultados do Esri para findAddressCandidate e também com alguns outros geocoders (inversos)que devolver o endereço de linha única onde a rua/cidade/estado são delimitados por vírgulas. Você pode estender se quiser ou escrever parsers específicos do país. Ou apenas use isso como estudo de caso de quão desafiador este exercício pode ser ou de quão ruim eu sou em JavaScript. Eu admito que eu só gastei cerca de trinta minutos sobre isso (iterações futuras poderiam adicionar caches, validação zip, e pesquisas de Estado, bem como o contexto de localização do usuário), mas funcionou para o meu caso de uso: o usuário final vê a forma que analisa a resposta de pesquisa geocode em 4 caixa. Se o processamento de endereços sair errado (o que é raro, a menos que os dados de fonte eram pobres) não é nada de mais - o usuário pode verificar e corrigi-lo! (But for automated solutions could either develout / ignore or flag as error so dev can either support the new format or fix source data.)

/* 
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, 2018-03-15 19:05:08

Num dos nossos projectos usamos o seguinte analisador de endereços. Ele analisa endereços para a maioria dos países do mundo com boa precisão.

Http://address-parser.net/

Está disponível como biblioteca independente ou como uma API ao vivo.

 0
Author: Waqas Anwar, 2017-03-16 11:40:37

Se quiser confiar nos dados do OSM a libpostal é muito poderosa e lida com muitas das advertências mais comuns com entradas de endereços.

 0
Author: Vitor Magalhães, 2017-07-28 13:22:09