Como analisar a rua de forma livre / endereço postal fora do texto e em componentes

Fazemos 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 que os tipos de usuário podem não estar corretos 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 seu endereço (como seu nome ou empresa com ele)
  • O Google pode fazer isso, mas os Termos de Serviço e os limites de consulta são proibitivos, principalmente com um orçamento apertado

Aparentemente, esta é uma pergunta comum:

  • Script PHP para analisar o endereço?
  • Como faço para analisar o endereço de formato livre para salvar no DataBase
  • analisador de endereço postal de java
  • Maneira mais eficiente de extrair componentes de endereço
  • Como posso mostrar um endereço postal pré-preenchido na canvas de contatos com rua, cidade, zip no android
  • Endereço de PHP regexp nos EUA

Existe uma maneira de isolar um endereço do texto em torno dele e quebrá-lo em pedaços? Existe uma expressão regular para analisar endereços?

    Eu vi essa pergunta muito quando trabalhei para uma empresa de verificação de endereços. Estou postando a resposta aqui para torná-la mais acessível aos programadores que estão pesquisando com a mesma pergunta. A empresa estava processando bilhões de endereços e aprendemos muito no processo.

    Primeiro, precisamos entender algumas coisas sobre endereços.

    Endereços não são regulares

    Isso significa que expressões regulares estão fora. Eu já vi de tudo, de simples expressões regulares que correspondem a endereços em um formato muito específico, para 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}) (corte | ct | rua | st | passeio | dr | lane | 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 | OU | 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 de class de linha superior a 900 gera uma expressão regular supermassiva em tempo real para corresponder ainda mais. Eu não recomendo estes (por exemplo, aqui está um violino do regex acima, que comete muitos erros ). Não há uma fórmula mágica fácil para que isso funcione. Em teoria e por teoria, não é possível combinar endereços com uma expressão regular.

    A publicação USPS 28 documenta os vários formatos de endereços que são possíveis, com todas as suas palavras-chave e variatons. Pior de tudo, os endereços costumam ser 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 os endereços e, se esse código existe, é um segredo comercial. Mas você provavelmente poderia criar o seu próprio se você realmente gostar disso.

    Endereços vêm em formas e tamanhos inesperados

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

    1) 102 main street Anytown, state 2) 400n 600e #2, 52173 3) po #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 de rua e uma cidade e estado. Com essa informação, 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 secundário / unidade) e um CEP de 5 dígitos, o que é suficiente para identificar um endereço.

    3. O número 3 é um formato completo de checkbox postal, pois contém um código postal.

    4. O número 4 também está completo porque o CEP é exclusivo , o que significa que uma entidade privada ou corporação adquiriu esse espaço de endereço. Um CEP 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 poderá 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 database de todos os endereços possíveis. Preencher os direcionais ausentes, o designador secundário e o 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 de endereço não são seus

    Na maioria dos países que fornecem dados de endereço oficiais para fornecedores licenciados, os dados de endereço em si 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 forma um pouco diferente. Saber disso é importante, já que geralmente proíbe a engenharia reversa do database de endereços. Você precisa ter cuidado ao adquirir, armazenar e usar os dados.

    O Google Maps é um recurso comum para correções de endereço rápidas, mas o TOS é bastante proibitivo. por exemplo, você não pode usar seus dados ou APIs sem mostrar um mapa do Google e para fins não comerciais apenas (a menos que você pague), e não é possível armazenar os dados (exceto para armazenamento em 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 existir, ele ainda mostrará onde o endereço estaria se ele existisse (tente na sua própria rua; use um número de casa que você sabe que não existe). Isso é útil às vezes, mas esteja ciente disso.

    A política de uso da Nominatim é similarmente limitante, especialmente para alto volume e uso comercial, e os dados são principalmente extraídos de fonts livres, então não é tão bem mantido (tal é a natureza dos projetos abertos) – no entanto, isso ainda pode servir suas necessidades. É apoiado por uma grande comunidade.

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

    As pessoas esperam que os endereços sejam difíceis

    Infelizmente, condicionamos nossa sociedade a esperar que os endereços sejam complicados. Há dezenas de bons artigos sobre UX em toda a Internet sobre isso, mas o fato é que, se você tiver um formulário de endereço com campos individuais, é o que os usuários esperam, mesmo que isso torne mais difícil para os endereços de ponta que não se encheckboxm 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 e falar sobre o mau UX dos formulários de pagamento nos dias de hoje, mas em vez disso vou dizer que combinar os endereços em um único campo será uma mudança bemvinda – as pessoas poderão digitar seu endereço como quiserem , em vez de tentar descobrir sua forma longa. No entanto, essa mudança será inesperada e os usuários podem achar um pouco chocante no começo. 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 norte-americanos de campo único, portanto, se eles selecionarem Estados Unidos, você poderá reduzir seu formulário para um único campo, caso contrário, mostre os campos do componente. Apenas coisas para pensar!

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

    O USPS licencia fornecedores através de um processo chamado Certificação CASS ™ para fornecer endereços verificados aos clientes. Esses fornecedores têm access ao database do USPS, atualizado mensalmente. Seu software deve estar em conformidade com os padrões rigorosos a serem certificados, e eles geralmente não exigem concordância com termos tão limitados como discutido acima.

    Existem muitas empresas certificadas pela CASS que podem processar listas ou ter APIs: Melissa Data, Experian QAS e SmartyStreets, para citar algumas.

    (Devido a receber críticas por “publicidade”, eu trunquei a minha resposta neste momento. Cabe a você encontrar uma solução que funcione para você.)

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

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

    Um analisador de endereço de rua de expressão regular pode obter uma taxa de sucesso de até 95% sem muita dificuldade. Então você começa a bater nos casos incomuns. O Perl no CPAN, “Geo :: StreetAddress :: US”, é muito bom. Existem portas Python e Javascript, todas de código aberto. Eu tenho uma versão melhorada em Python que move a taxa de sucesso ligeiramente, lidando com mais casos. Para obter os últimos 3%, você precisa de bancos de dados para ajudar na desambiguação.

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

    A análise correta do endereço da rua começa no final e funciona de trás para 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 database é uma grande ajuda.

    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:

    UPDATE: Geocode.xyz agora funciona em todo o mundo. Para exemplos, consulte https://geocode.xyz

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

    Por exemplo:

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

    Saída:

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

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

    Nenhum código? Por vergonha!

    Aqui está um analisador de endereço JavaScript simples. É muito ruim por todos os motivos que Matt cita em sua dissertação acima (com quase 100% de concordância: endereços são tipos complexos, e humanos cometem erros; melhor terceirizar e automatizar isso – quando você pode pagar).

    Mas ao invés de chorar, decidi tentar:

    Esse código funciona bem para analisar a maioria dos resultados da Esri para findAddressCandidate e também com alguns outros geocoders (reversos) que retornam o endereço de linha única em que rua / cidade / estado são delimitados por vírgulas. Você pode estender se quiser ou escrever analisadores específicos de país. Ou apenas use isso como estudo de caso do quão desafiador este exercício pode ser ou de quão ruim eu sou em JavaScript. Admito que passei apenas cerca de trinta minutos nisso (futuras iterações poderiam adicionar caches, validação de zip e pesquisas de estado, bem como contexto de localização do usuário), mas funcionou para meu caso de uso: Usuário final vê o formulário que analisa a resposta de pesquisa geocódigo em 4 checkboxs de texto. Se a análise de endereço for incorreta (o que é raro, a menos que os dados de origem sejam ruins), não é grande coisa – o usuário pode verificar e consertar isso! (Mas, para soluções automatizadas, é possível descartar / ignorar ou marcar como erro, para que o dev possa 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); 
       

    Em um de nossos projetos, 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 autônoma ou como uma API ao vivo.

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