Como analisar endereço postal / postal de forma livre sem texto e em componentes

136

Realizamos negócios em grande parte nos Estados Unidos e estamos tentando melhorar a experiência do usuário combinando todos os campos de endereço em uma única área de texto. Mas existem alguns problemas:

  • O endereço digitado pelo usuário pode não estar correto ou em um formato padrão
  • O endereço deve ser separado em partes (rua, cidade, estado etc.) para processar pagamentos com cartão de crédito
  • Os usuários podem inserir mais do que apenas o endereço (como o nome ou a empresa)
  • O Google pode fazer isso, mas os Termos de Serviço e os limites de consulta são proibitivos, especialmente com um orçamento apertado

Aparentemente, esta é uma pergunta comum:

Existe uma maneira de isolar um endereço do texto ao redor e dividi-lo em pedaços? Existe uma expressão regular para analisar endereços?

Matt
fonte
As respostas abaixo são mais úteis porque não ignoram o problema global - esses endereços não se encaixam em um padrão comum.
Marc Maxmeister

Respostas:

290

Eu vi muito essa pergunta quando trabalhei para uma empresa de verificação de endereço. Estou postando a resposta aqui para torná-la mais acessível aos programadores que estão pesquisando com a mesma pergunta. A empresa em que eu estava processou bilhõ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

Isso significa que expressões regulares estão fora. Eu já vi tudo, desde expressões regulares simples que correspondem a endereços em um formato muito específico, até isso:

/ \ 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 | rua | st | drive | dr | faixa | ln | estrada | 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 isso, onde um arquivo com mais de 900 linhas de classe gera uma expressão regular supermassiva em tempo real para corresponder ainda mais. Eu não os recomendo (por exemplo, aqui está um violino do regex acima, que comete muitos erros ). Não existe uma fórmula mágica fácil para fazer isso funcionar. Na teoria e pela teoria, não é possível combinar endereços com uma expressão regular.

A Publicação USPS 28 documenta os muitos formatos de endereços possíveis, com todas as suas palavras-chave e variáveis. O pior de tudo é que os endereços geralmente são ambíguos. Palavras podem significar mais de uma coisa ("St" pode ser "Saint" ou "Street") e há palavras que eu tenho certeza que elas inventaram. (Quem sabia que "Stravenue" era um sufixo de rua?)

Você precisaria de algum código que realmente entendesse endereços e, se esse código existir, é um segredo comercial. Mas você provavelmente poderia fazer o seu próprio se realmente gostar disso.

Os endereços têm formatos e tamanhos inesperados

Aqui estão alguns endereços planejados (mas completos):

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 quebras de linha não garantidas. Aqui está o que está acontecendo:

  1. O número 1 está completo porque contém um endereço e uma cidade e estado. Com essas informações, basta identificar o 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 (com número de unidade / secundário) e um CEP de 5 dígitos, o suficiente para identificar um endereço.

  3. O número 3 é um formato completo de caixa postal, pois contém um CEP.

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

  5. O número 5 também está completo, acredite ou não. Com apenas esses números, o endereço completo pode ser descoberto quando analisado em um banco de dados de todos os endereços possíveis. O preenchimento dos direcionais ausentes, designador secundário e código ZIP + 4 é trivial quando você vê cada número como um componente. Veja como é, 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 oficiais de endereço a fornecedores licenciados, os próprios dados de endereço pertencem à agência governamental. Nos EUA, o USPS possui os endereços. O mesmo vale para o Canada Post, o Royal Mail e outros, embora cada país imponha ou defina a propriedade de maneira um pouco diferente. Saber disso é importante, pois geralmente proíbe a engenharia reversa do banco de dados de endereços. Você deve ter cuidado ao adquirir, armazenar e usar os dados.

O Google Maps é uma ferramenta comum para correções rápidas de endereços, mas os Termos de Serviço são proibitivos; por exemplo, você não pode usar os dados ou APIs sem mostrar um mapa do Google e apenas para fins não comerciais (a menos que pague) e não pode armazenar os dados (exceto para cache temporário). Faz sentido. Os dados do Google são alguns dos melhores do mundo. No entanto, o Google Maps não verifica o endereço. Se um endereço não existe, ele ainda vai mostrar onde o endereço iria ser se fez exist (experimentá-lo em sua própria rua, use um número de casa que você sabe que não existe). Isso às vezes é útil, mas esteja ciente disso.

A política de uso da Nominatim é similarmente limitada, especialmente para uso comercial e de alto volume, e os dados são extraídos principalmente de fontes gratuitas, portanto, não são tão bem mantidos (como é a natureza dos projetos abertos) - no entanto, isso ainda pode ser adequado suas necessidades. É apoiado por uma grande comunidade.

O USPS em si tem uma API, mas diminui bastante e vem sem garantias nem suporte. Também pode ser difícil de usar. Algumas pessoas o usam com moderação, sem problemas. Mas é fácil perder que o USPS exige que você use a API deles apenas para confirmar endereços para enviá-los.

As pessoas esperam que endereços sejam difíceis

Infelizmente, condicionamos nossa sociedade a esperar que endereços sejam complicados. Existem dezenas de bons artigos de UX em toda a Internet sobre isso, mas o fato é que, se você tiver um formulário de endereço com campos individuais, é isso que os usuários esperam, mesmo que isso dificulte os endereços de ponta que não se encaixam no padrão. formato que o formulário está esperando, ou talvez o formulário exija um campo que não deveria. Ou os usuários não sabem onde colocar uma determinada parte do endereço.

Eu poderia continuar falando sobre o UX ruim dos formulários de checkout hoje em dia, mas, em vez disso, direi que combinar os endereços em um único campo será uma mudança bem - vinda - as pessoas poderão digitar seu endereço como acharem melhor , em vez de tentar descobrir seu formulário longo. No entanto, essa alteração será inesperada e os usuários podem achar um pouco chocantes no início. Apenas esteja ciente disso.

Parte dessa dor pode ser aliviada colocando o campo do país na frente, antes do endereço. Quando eles preenchem o campo do país primeiro, você sabe como fazer seu formulário aparecer. Talvez você tenha uma boa maneira de lidar com endereços dos EUA em um único campo; portanto, se eles selecionarem Estados Unidos, você poderá reduzir seu formulário para um único campo; caso contrário, mostre os campos dos componentes. Apenas coisas para pensar!

Agora sabemos porque é difícil; o que você pode fazer sobre isso?

O USPS licencia os fornecedores através de um processo chamado Certificação CASS ™ para fornecer endereços verificados aos clientes. Esses fornecedores têm acesso ao banco de dados USPS, atualizado mensalmente. Seu software deve estar em conformidade com padrões rigorosos para ser certificado e, muitas vezes, eles não exigem concordância com os termos limitantes mencionados acima.

Existem muitas empresas com certificação CASS que podem processar listas ou ter APIs: Melissa Data, Experian QAS e SmartyStreets, entre outras.

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

A verdade: Realmente, pessoal, não trabalho em nenhuma dessas empresas. Não é um anúncio.

Matt
fonte
1
E os endereços da América do Sul (Uruguai)? : D
Bart Calixto
11
@ Brian - Talvez porque o usuário tenha fornecido muitas informações úteis para quem lê a pergunta e a resposta, independentemente de optar ou não por usar o produto de sua empresa.
Zarepheth
7
@Brian Esses sites são scrapers de conteúdo. Eles estão buscando conteúdo para obter classificações SERP. Eu nunca os vi antes. Eu nunca publiquei este conteúdo antes ou depois de qualquer outro lugar.
Matt
2
@khuderm notei agora, quando li seu comentário, que todos os comentários dissidentes desapareceram; não sei como / quando isso aconteceu. De qualquer forma, consulte o histórico de edições da minha resposta e você encontrará referências diretas a um extrator de endereços nos EUA que pode ajudá-lo. Eu o construí quando trabalhei no meu último emprego, mas é um código proprietário, então não posso compartilhá-lo ... mas eles existem. Espero que seja útil.
Matt
2
Opa Desculpe @Matt. Bem, eu comecei a segui-lo através de suas perguntas e também do Github. Muito impressionante você é.
Sayka
28

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

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

Outras ferramentas / serviços:

David Portabella
fonte
13

Existem muitos analisadores de endereço. Eles vêm em dois sabores básicos - aqueles que possuem bancos de dados de nomes de lugares e ruas e outros que não.

Um analisador de endereço de expressão regular pode chegar a uma taxa de sucesso de 95% sem muitos problemas. Então você começa a bater nos casos incomuns. O Perl no CPAN, "Geo :: StreetAddress :: US", é quase tão bom assim. Existem portas Python e Javascript, todas de código aberto. Eu tenho uma versão aprimorada em Python, que move a taxa de sucesso um pouco, manipulando mais casos. Para acertar os últimos 3%, você precisa de bancos de dados para ajudar na desambiguação.

Um banco de dados com códigos postais de três dígitos e nomes e abreviações de estados dos EUA é uma grande ajuda. Quando um analisador vê um código postal e um nome de estado consistentes, pode começar a bloquear o formato. Isso funciona muito bem para os EUA e o Reino Unido.

A análise adequada do endereço começa no final e funciona de trás para a frente. É assim que os sistemas USPS fazem isso. Os 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 das ruas geralmente podem ser isolados. Locais nas ruas são os mais complexos de analisar; lá você encontra coisas como "Fifth Floor" e "Staples Pavillion". É quando um banco de dados é uma grande ajuda.

John Nagle
fonte
Há também o módulo CPAN Lingua: EN :: AddressParse. Embora mais lento do que "Geo :: StreetAddress :: EUA, dá uma maior taxa de sucesso.
Kim Ryan
8

ATUALIZAÇÃO: Geocode.xyz agora funciona em todo o mundo. Para exemplos, consulte https://geocode.xyz

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

Por exemplo:

Entrada: algo acontecendo perto da interseção de main e arthur kill rd nova york

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>

Você também pode verificar os resultados na interface da web ou obter a saída como Json ou Jsonp. por exemplo. Estou à procura de restaurantes em 123 Main Street, Nova York

Ervin Ruci
fonte
Como você implementou o sistema de análise de endereço usando o endereço aberto? Você está usando a estratégia de força bruta?
Nithin K Anil
1
O que você quer dizer com 'força bruta'? Dividir o texto em todas as combinações possíveis de seqüências de endereços possíveis e comparar cada uma delas com um banco de dados de endereços não é prático e levará muito mais tempo para fornecer uma resposta do que esse sistema. Os endereços abertos são uma das fontes de dados para a criação de um 'conjunto de treinamento' de formatos de endereço para o algoritmo. Ele usa essas informações para analisar endereços de texto não estruturado.
Ervin Ruci
2
Outro sistema semelhante é Geo :: libpostal ( perltricks.com/article/announcing-geo--libpostal ) Eles também usam openstreetmap e openaddresses ao que parece, para modelos de endereço construção na mosca
Ervin Ruci
Acabei de testar o geoparser do geocode.xyz (enviar texto, voltar à localização) em centenas de endereços reais. Dado lado a lado com a API do google map e um conjunto global de endereços, geocode.xyzo scantextmétodo falhou na maioria das vezes. Ele sempre escolheu "Genebra, EUA", em vez de "Genebra, Suíça" e era geralmente tendencioso nos EUA.
Marc Maxmeister
Depende do contexto. geocode.xyz/?scantext=Geneva,%20Switzerland produzirá: Match Location Geneva, Switzerland, CH Confidence Score: 0.8 enquanto geocode.xyz/?scantext=Geneva,%20USA produzirá Match Location Geneva, US Confidence Score: 1.0 Também, você pode polarizar a região da seguinte maneira: geocode.xyz/?scantext=Geneva,%20USA®ion=CH
Ervin Ruci
4

Nenhum código? Por vergonha!

Aqui está um simples analisador de endereço JavaScript. É bastante horrível por todas as razões que Matt cita 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ê puder).

Mas, em vez de chorar, decidi tentar:

Esse código funciona bem para analisar a maioria dos resultados Esri parafindAddressCandidatee também com alguns outros geocodificadores (reversos) que retornam endereços de linha única em que rua / cidade / estado são delimitados por vírgulas. Você pode estender se desejar ou escrever analisadores específicos de cada país. Ou apenas use isso como um estudo de caso de quão desafiador esse exercício pode ser ou de quão ruim eu sou no JavaScript. Admito que gastei apenas cerca de trinta minutos nisso (iterações futuras podem adicionar caches, validação de 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ê um formulário que analisa a resposta da pesquisa de geocódigo em 4 caixas de texto. Se a análise de endereço der errado (o que é raro, a menos que os dados de origem sejam ruins), não é grande coisa - o usuário pode verificar e corrigi-lo! (Porém, para soluções automatizadas, você pode descartar / ignorar ou sinalizar como erro, o desenvolvedor pode suportar o novo formato ou corrigir os dados de origem.)

/* 
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 é necessário
fonte
isenção de responsabilidade: meus clientes possuem seus dados de endereço e executam seus próprios servidores Esri. Se você pegar os dados do Google, OSM, ArcGisOnline, ou onde quer, certifique-se que é OK para loja e usá-lo (muitos serviços têm restrições sobre como você pode armazenar, e por quanto tempo)
nothingisnecessary
A primeira resposta acima mostra que esse problema não pode ser resolvido com expressões regulares se você estiver lidando com uma lista de endereços global. 200 países têm muitas exceções. Nos meus testes, você pode determinar o país a partir de uma string de maneira bastante confiável e, em seguida, procurar uma regex específica para cada país - que provavelmente é como as melhores APIs funcionam.
Marc Maxmeister
2

Se você deseja confiar nos dados do OSM, o libpostal é muito poderoso e lida com muitas das advertências mais comuns com entradas de endereço.

Vitor Magalhães
fonte
Acho que sua resposta é uma duplicata deste post. Boa sugestão, no entanto.
Michael - Onde está Clay Shirky
2

Outra opção para endereços nos EUA é o YAddress (da empresa em que trabalho).

Muitas respostas para essa pergunta sugerem ferramentas de geocodificação como uma solução. É importante não confundir a análise de endereços e a geocodificação; Eles não são os mesmos. Embora os geocodificadores possam dividir um endereço em componentes como um benefício colateral, eles geralmente confiam em conjuntos de endereços não padrão. Isso significa que um endereço analisado por geocoder pode não ser o mesmo que o endereço oficial. Por exemplo, o que a API de geocodificação do Google chama de "6th Ave" em Manhattan, o USPS chama de "Avenue of the Americas".

Michael Diomin
fonte
2

Para análise de endereço nos EUA,

Prefiro usar o pacote usaddress que está disponível no pip apenas para usaddress

python3 -m pip install usaddress

Documentação
PyPi

Isso funcionou bem para mim no endereço dos EUA.

#!/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)))

Executando o 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
fonte
0

Em um de nosso projeto, usamos o seguinte analisador de endereço. Ele analisa os endereços da maioria dos países do mundo com boa precisão.

http://address-parser.net/

Está disponível como biblioteca autônoma ou como uma API ao vivo.

Waqas Anwar
fonte
1
Mas é pago pelo produto.
Jeremy Thompson
0

Estou atrasado para a festa, aqui está um script VBA do Excel que escrevi anos atrás para a Austrália. Pode ser facilmente modificado para apoiar outros países. Eu criei um repositório GitHub do código C # aqui. Eu o hospedei no meu site e você pode baixá-lo aqui: http://jeremythompson.net/rocks/ParseAddress.xlsm

Estratégia

Para qualquer país com um código postal que seja numérico ou possa corresponder a um RegEx, minha estratégia funciona muito bem:

  1. Primeiro, detectamos o Primeiro e o Sobrenome, que são assumidos como a linha superior. É fácil pular o nome e começar com o endereço desmarcando a caixa de seleção (chamada 'Nome é a linha superior', como mostrado abaixo).

  2. Em seguida, é seguro esperar que o endereço que consiste na rua e no número chegue antes do subúrbio e o St, Pde, Ave, Av.

  3. Detectar o subúrbio versus o estado e até o país pode enganar os analisadores mais sofisticados, pois pode haver conflitos. Para superar isso, eu uso uma pesquisa PostCode com base no fato de que, depois de remover os números de ruas e apartamentos / unidades, bem como o PoBox, Ph, Fax , celular etc., apenas o número PostCode permanecerá. É fácil combinar com um regEx para procurar o (s) subúrbio (s) e país.

Seu Serviço Nacional de Correios fornecerá uma lista de códigos postais com subúrbios e estados gratuitos que você pode armazenar em uma planilha do excel, tabela db, arquivo de texto / json / xml, etc.

  1. Finalmente, como alguns códigos postais têm vários subúrbios, verificamos qual subúrbio aparece no endereço.

Exemplo

insira a descrição da imagem aqui

Código VBA

AVISO LEGAL, eu sei que esse código não é perfeito, ou mesmo foi escrito bem, no entanto, é muito fácil converter para qualquer linguagem de programação e executar em qualquer tipo de aplicativo. A estratégia é a resposta, dependendo do país e das regras, tome esse código como exemplo :

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
fonte