Jak przeanalizować dowolną ulicę / adres pocztowy z tekstu i na komponenty

136

Prowadzimy działalność głównie w Stanach Zjednoczonych i staramy się poprawić komfort użytkowania, łącząc wszystkie pola adresowe w jeden obszar tekstowy. Ale jest kilka problemów:

  • Adres, który wpisuje użytkownik, może być nieprawidłowy lub w standardowym formacie
  • Adres musi być podzielony na części (ulica, miasto, województwo itp.), Aby przetwarzać płatności kartą kredytową
  • Użytkownicy mogą podać więcej niż tylko swój adres (na przykład imię i nazwisko lub firmę)
  • Google może to zrobić, ale Warunki korzystania z usługi i limity zapytań są zaporowe, zwłaszcza przy napiętym budżecie

Najwyraźniej jest to częste pytanie:

Czy istnieje sposób na oddzielenie adresu od otaczającego go tekstu i rozbicie go na kawałki? Czy istnieje wyrażenie regularne do analizowania adresów?

Matt
źródło
Poniższe odpowiedzi są bardziej przydatne, ponieważ nie ignorują problemu globalnego - adresy nie pasują do wspólnego wzorca.
Marc Maxmeister

Odpowiedzi:

290

Często widziałem to pytanie, gdy pracowałem dla firmy weryfikującej adresy. Umieszczam tutaj odpowiedź, aby była bardziej dostępna dla programistów, którzy szukają tego samego pytania. Firma, w której byłem, przetwarzała miliardy adresów i przy okazji wiele się nauczyliśmy.

Najpierw musimy zrozumieć kilka rzeczy dotyczących adresów.

Adresy nie są regularne

Oznacza to, że wyrażenia regularne są wyłączone. Widziałem to wszystko, od prostych wyrażeń regularnych, które dopasowują adresy w bardzo specyficznym formacie, do tego:

/ \ 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}) (sąd | 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

... do tego, w którym plik klasy liniowej 900+ generuje supermasywne wyrażenie regularne w locie, aby dopasować jeszcze więcej. Nie polecam ich (na przykład tutaj jest skrzypce powyższego wyrażenia regularnego, które popełnia wiele błędów ). Nie ma łatwej magicznej formuły, aby to zadziałało. W teorii i przez teorię, to nie jest możliwe, aby dopasować adresy z wyrażenia regularnego.

Publikacja USPS 28 dokumentuje wiele możliwych formatów adresów wraz ze wszystkimi ich słowami kluczowymi i odmianami. Co najgorsze, adresy są często niejednoznaczne. Słowa mogą oznaczać więcej niż jedną rzecz („Święty” może oznaczać „Święty” lub „Ulica”), a są słowa, które z pewnością wymyślili. (Kto wiedział, że „Stravenue” to sufiks ulicy?)

Potrzebowałbyś kodu, który naprawdę rozumie adresy, a jeśli ten kod istnieje, jest to tajemnica handlowa. Ale prawdopodobnie mógłbyś rzucić własne, jeśli naprawdę się tym interesujesz.

Adresy mają nieoczekiwane kształty i rozmiary

Oto kilka wymyślonych (ale kompletnych) adresów:

1)  102 main street
    Anytown, state

2)  400n 600e #2, 52173

3)  p.o. #104 60203

Nawet te są prawdopodobnie ważne:

4)  829 LKSDFJlkjsdflkjsdljf Bkpw 12345

5)  205 1105 14 90210

Oczywiście nie są one znormalizowane. Nie gwarantuje się stosowania znaków interpunkcyjnych ani podziałów wierszy. Oto, co się dzieje:

  1. Numer 1 jest kompletny, ponieważ zawiera adres oraz miasto i województwo. Dzięki tym informacjom wystarczy zidentyfikować adres i można go uznać za „dostarczalny” (przy pewnej standaryzacji).

  2. Numer 2 jest kompletny, ponieważ zawiera również adres (z numerem dodatkowym / jednostki) i 5-cyfrowy kod pocztowy, który jest wystarczający do zidentyfikowania adresu.

  3. Numer 3 to pełny format skrytki pocztowej, ponieważ zawiera kod pocztowy.

  4. Numer 4 jest również kompletny, ponieważ kod pocztowy jest unikalny , co oznacza, że ​​podmiot prywatny lub korporacja zakupiła tę przestrzeń adresową. Unikalny kod pocztowy jest przeznaczony dla dużych lub skoncentrowanych miejsc dostawczych. Wszystko zaadresowane na kod pocztowy 12345 trafia do General Electric w Schenectady w stanie Nowy Jork. Ten przykład nie dotrze do nikogo w szczególności, ale USPS nadal byłby w stanie go dostarczyć.

  5. Numer 5 jest również kompletny, wierz lub nie. Mając tylko te liczby, pełny adres można znaleźć, analizując go w bazie danych zawierającej wszystkie możliwe adresy. Wypełnienie brakujących kierunków, dodatkowego oznaczenia i kodu ZIP + 4 jest trywialne, gdy widzisz każdą liczbę jako składnik. Oto, jak to wygląda, w pełni rozwinięte i ustandaryzowane:

205 N 1105 W Apt 14

Beverly Hills CA 90210-5221

Dane adresowe nie należą do Ciebie

W większości krajów, które udostępniają licencjonowanym dostawcom oficjalne dane adresowe, same dane adresowe należą do agencji zarządzającej. W USA adresy są własnością USPS. To samo dotyczy Canada Post, Royal Mail i innych, chociaż każdy kraj egzekwuje lub definiuje własność nieco inaczej. Wiedza o tym jest ważna, ponieważ zwykle zabrania inżynierii wstecznej bazy danych adresów. Musisz uważać, jak pozyskiwać, przechowywać i wykorzystywać dane.

Mapy Google są popularnym miejscem do szybkiego poprawiania adresów, ale TOS są raczej zaporowe; na przykład nie możesz korzystać z ich danych lub interfejsów API bez pokazania mapy Google i tylko do celów niekomercyjnych (chyba że płacisz) i nie możesz przechowywać danych (z wyjątkiem tymczasowego buforowania). Ma sens. Dane Google należą do najlepszych na świecie. Jednak Google Maps nie weryfikuje adresu. Jeśli adres nie istnieje, to jeszcze pokazać, gdzie adres byłoby , gdyby to zrobił istnieje (spróbuj go na swojej własnej ulicy, korzystać z numer domu, które znasz, nie istnieje). Czasami jest to przydatne, ale pamiętaj o tym.

Polityka użytkowania Nominatim jest podobnie ograniczająca, szczególnie w przypadku dużych wolumenów i zastosowań komercyjnych, a dane pochodzą głównie z bezpłatnych źródeł, więc nie są tak dobrze utrzymywane (taki jest charakter otwartych projektów) - jednak może to nadal pasować Twoje potrzeby. Jest wspierany przez wspaniałą społeczność.

Sam USPS ma interfejs API, ale bardzo się obniża i nie ma żadnych gwarancji ani wsparcia. Może być również trudny w użyciu. Niektórzy używają go oszczędnie i bez problemów. Ale łatwo przeoczyć, że USPS wymaga, abyś używał ich API tylko do potwierdzania adresów do wysyłki za ich pośrednictwem.

Ludzie oczekują, że adresy będą trudne

Niestety, uwarunkowaliśmy nasze społeczeństwo, aby oczekiwało, że adresy będą skomplikowane. W całym Internecie można znaleźć dziesiątki dobrych artykułów UX na ten temat, ale faktem jest, że jeśli masz formularz adresowy z pojedynczymi polami, tego oczekują użytkownicy, mimo że utrudnia to w przypadku adresów skrajnych, które nie pasują do format, jakiego oczekuje formularz, a może formularz wymaga pola, którego nie powinien. Lub użytkownicy nie wiedzą, gdzie umieścić określoną część swojego adresu.

W dzisiejszych czasach mógłbym ciągle mówić o złym UX formularzy płatności, ale zamiast tego powiem tylko, że połączenie adresów w jedno pole będzie mile widzianą zmianą - ludzie będą mogli wpisać swój adres tak, jak uważają za stosowny , zamiast próbować wymyślić długi formularz. Jednak ta zmiana będzie nieoczekiwana i początkowo użytkownicy mogą uznać ją za nieco irytującą. Po prostu bądź tego świadomy.

Część tego bólu można złagodzić, umieszczając pole kraju z przodu, przed adresem. Kiedy najpierw wypełnią pole kraju, wiesz, jak wyświetlić formularz. Być może masz dobry sposób radzenia sobie z adresami w USA z jednym polem, więc jeśli wybierzesz Stany Zjednoczone, możesz zredukować formularz do jednego pola, w przeciwnym razie pokaż pola składowe. Tylko rzeczy do przemyślenia!

Teraz wiemy, dlaczego jest to trudne; co możesz z tym zrobić?

USPS udziela dostawcom licencji w ramach procesu zwanego CASS ™ Certification, aby zapewnić klientom zweryfikowane adresy. Dostawcy ci mają dostęp do bazy danych USPS, aktualizowanej co miesiąc. Ich oprogramowanie musi być zgodne z rygorystycznymi standardami, aby uzyskać certyfikat, i często nie wymagają zgody na takie ograniczające warunki, jak omówiono powyżej.

Istnieje wiele firm z certyfikatem CASS, które mogą przetwarzać listy lub mieć interfejsy API: Melissa Data, Experian QAS i SmartyStreets, aby wymienić tylko kilka.

(W związku z tym, że nie mogę się doczekać „reklamy”, skróciłem w tym miejscu swoją odpowiedź. To do Ciebie należy znalezienie rozwiązania, które będzie dla Ciebie odpowiednie).

Prawda: Naprawdę, ludzie, nie pracuję w żadnej z tych firm. To nie jest reklama.

Matt
źródło
1
A co z adresami w Ameryce Południowej (Urugwaju)? : D
Bart Calixto
11
@Brian - Być może dlatego, że użytkownik udzielił wielu przydatnych informacji osobom czytającym pytanie i odpowiedź, niezależnie od tego, czy zdecydują się na korzystanie z produktu jego firmy.
Zarepheth
7
@Brian Te strony to skrobaki treści. Gromadzą treści, aby uzyskać ranking SERP. Nigdy wcześniej ich nie widziałem. Nigdy wcześniej nie publikowałem tych treści przed ani po nigdzie indziej.
Matt
2
@khuderm Zauważyłem przed chwilą, kiedy przeczytałem twój komentarz, że wszystkie komentarze sprzeciwu zniknęły; nie wiem, jak / kiedy to się stało. W każdym razie, zobacz historię edycji mojej odpowiedzi, a znajdziesz bezpośrednie odniesienie do narzędzia do ekstrakcji adresów w USA, które może ci pomóc. Zbudowałem go, kiedy pracowałem w mojej ostatniej pracy, ale jest to zastrzeżony kod, więc nie mogę go udostępnić ... ale one istnieją. Mam nadzieję, że jest pomocny.
Matt
2
Ups. Przepraszamy @Matt. Cóż, zacząłem śledzić Cię poprzez Twoje pytania, a także Github. Jesteś całkiem imponujący.
Sayka
28

libpostal: biblioteka open source do analizowania adresów, szkolenia z danymi z OpenStreetMap, OpenAddresses i OpenCage.

https://github.com/openvagues/libpostal ( więcej informacji na ten temat )

Inne narzędzia / usługi:

David Portabella
źródło
13

Istnieje wiele analizatorów adresów ulic. Występują w dwóch podstawowych smakach - takich, które mają bazy danych nazw miejsc i nazw ulic oraz takich, które ich nie mają.

Parser adresów ulicznych wyrażeń regularnych może bez większych problemów uzyskać do około 95% wskaźnika sukcesu. Wtedy zaczynasz trafiać w nietypowe przypadki. Perl w CPAN, „Geo :: StreetAddress :: US”, jest prawie taki dobry. Istnieją porty Python i Javascript, wszystkie open source. Mam ulepszoną wersję w Pythonie, która nieznacznie podnosi wskaźnik sukcesu, obsługując więcej przypadków. Aby jednak uzyskać prawidłowe 3%, potrzebujesz baz danych, które pomogą w ujednoznacznieniu.

Baza danych zawierająca 3-cyfrowe kody pocztowe oraz nazwy stanów i skróty w Stanach Zjednoczonych jest bardzo pomocna. Gdy parser zobaczy spójny kod pocztowy i nazwę stanu, może zacząć blokować format. Działa to bardzo dobrze w Stanach Zjednoczonych i Wielkiej Brytanii.

Prawidłowe analizowanie adresu ulicy zaczyna się od końca i działa wstecz. Tak to robią systemy USPS. Adresy są najmniej niejednoznaczne na końcu, gdzie nazwy krajów, nazwy miast i kody pocztowe są stosunkowo łatwe do rozpoznania. Nazwy ulic można zwykle wyodrębnić. Lokalizacje na ulicach są najtrudniejsze do przeanalizowania; napotkasz tam takie rzeczy jak „Fifth Floor” i „Staples Pavillion”. Wtedy baza danych jest bardzo pomocna.

John Nagle
źródło
Istnieje również moduł CPAN Lingua: EN :: AddressParse. Chociaż wolniej niż „Geo :: StreetAddress :: US, daje wyższy wskaźnik sukcesu.
Kim Ryan
8

UPDATE: Geocode.xyz działa teraz na całym świecie. Przykłady można znaleźć pod adresem https://geocode.xyz

W przypadku USA, Meksyku i Kanady odwiedź stronę geocoder.ca .

Na przykład:

Dane wejściowe: coś się dzieje w pobliżu skrzyżowania głównych i arthur kill rd new york

Wynik:

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

Możesz również sprawdzić wyniki w interfejsie internetowym lub uzyskać dane wyjściowe jako Json lub Jsonp. na przykład. Szukam restauracji w okolicach 123 Main Street w Nowym Jorku

Ervin Ruci
źródło
Jak zaimplementowałeś system parsowania adresów za pomocą openaddress? Czy używasz strategii brutalnej siły?
Nithin K Anil
1
Co masz na myśli mówiąc „brutalna siła”? Dzielenie tekstu na wszystkie możliwe kombinacje możliwych ciągów adresów i porównywanie ich z bazą danych adresów nie jest praktyczne, a udzielenie odpowiedzi zajmie znacznie więcej czasu niż ten system. Openaddresses to jedno ze źródeł danych służących do budowania „zestawu szkoleniowego” formatów adresów dla algorytmu. Wykorzystuje te informacje do analizowania adresów z tekstu bez struktury.
Ervin Ruci
2
Innym podobnym systemem jest Geo :: libpostal ( perltricks.com/article/announcing-geo--libpostal ) Korzystają również z openstreetmap i openaddresses, jak się wydaje, do tworzenia szablonów adresów w locie
Ervin Ruci
Właśnie przetestowałem geoparser geocode.xyz (wyślij tekst, wróć do lokalizacji) na setkach rzeczywistych adresów. Biorąc pod uwagę u boku Google Map API oraz globalnego zbioru adresów, geocode.xyz„s scantextmetody zawiodły większość czasu. Zawsze wybierał „Genewa, USA” zamiast „Genewa, Szwajcaria” i był ogólnie nastawiony na Stany Zjednoczone.
Marc Maxmeister
To zależy od kontekstu. geocode.xyz/?scantext=Geneva,%20Switzerland wyprodukuje: Match Location Genewa, Szwajcaria, CH Confidence Score: 0,8, podczas gdy geocode.xyz/?scantext=Geneva,%20USA wygeneruje Match Location Genewa, US Confidence Score: 1.0 Ponadto, możesz nastawić
Ervin
4

Brak kodu? Wstyd!

Oto prosty parser adresów JavaScript. To dość okropne z każdego powodu, który Matt podaje w swojej powyższej rozprawie (z którą prawie w 100% się zgadzam: adresy są złożonymi typami, a ludzie popełniają błędy; lepiej zlecić to na zewnątrz i zautomatyzować - kiedy możesz sobie na to pozwolić).

Ale zamiast płakać, postanowiłem spróbować:

Ten kod działa poprawnie w przypadku analizowania większości wyników Esri dlafindAddressCandidatea także z niektórymi innymi (odwrotnymi) geokoderami, które zwracają jednowierszowy adres, w którym ulica / miasto / stan są oddzielone przecinkami. Możesz rozszerzyć, jeśli chcesz, lub napisać parsery specyficzne dla kraju. Lub użyj tego jako studium przypadku, jak trudne może być to ćwiczenie lub jak kiepski jestem w JavaScript. Przyznaję, że spędziłem na tym tylko około trzydziestu minut (przyszłe iteracje mogą dodawać pamięci podręczne, sprawdzanie poprawności zip i wyszukiwania stanu, a także kontekst lokalizacji użytkownika), ale zadziałało w moim przypadku użycia: użytkownik końcowy widzi formularz, który analizuje odpowiedź wyszukiwania geokodu na 4 pola tekstowe. Jeśli parsowanie adresu wyjdzie nieprawidłowo (co jest rzadkością, chyba że dane źródłowe są słabe), to nic wielkiego - użytkownik może to zweryfikować i naprawić! (Ale w przypadku zautomatyzowanych rozwiązań może odrzucić / zignorować lub oznaczyć jako błąd, aby programista mógł albo obsługiwać nowy format, albo naprawiać dane źródłowe).

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

nic niepotrzebnego
źródło
zastrzeżenie: moi klienci są właścicielami swoich danych adresowych i mają własne serwery Esri. Jeśli pobierasz dane z Google, OSM, ArcGisOnline lub z dowolnego miejsca, upewnij się, że ich przechowywanie i używanie jest w porządku (wiele usług ma ograniczenia dotyczące sposobu przechowywania i czasu
trwania
Pierwsza odpowiedź powyżej daje przekonujący argument, że tego problemu nie da się rozwiązać za pomocą wyrażeń regularnych, jeśli masz do czynienia z globalną listą adresów. W 200 krajach jest zbyt wiele wyjątków. W moich testach można dość wiarygodnie określić kraj na podstawie ciągu znaków, a następnie wyszukać konkretne wyrażenie regularne dla każdego kraju - prawdopodobnie tak działają lepsze interfejsy API.
Marc Maxmeister
2

Jeśli chcesz polegać na danych OSM, libpostal jest bardzo wydajny i obsługuje wiele najczęstszych zastrzeżeń przy wprowadzaniu adresu.

Vitor Magalhães
źródło
Myślę, że twoja odpowiedź jest duplikatem tego postu. Dobra sugestia.
Michael - Where's Clay Shirky
2

Inną opcją dla adresów w USA jest YAddress (utworzona przez firmę, dla której pracuję).

Wiele odpowiedzi na to pytanie sugeruje narzędzia geokodowania jako rozwiązanie. Ważne jest, aby nie mylić analizowania adresów i geokodowania; one nie są takie same. Chociaż geokodery mogą rozbić adres na komponenty jako korzyść dodatkową, zwykle polegają na niestandardowych zestawach adresów. Oznacza to, że adres przeanalizowany przez geokodera może nie być taki sam jak adres oficjalny. Na przykład to, co interfejs API geokodowania Google nazywa „6th Ave” na Manhattanie, USPS nazywa „Avenue of the Americas”.

Michael Diomin
źródło
2

W przypadku analizowania adresów w USA,

Wolę używać pakietu usaddress, który jest dostępny w pip tylko dla usaddress

python3 -m pip install usaddress

Dokumentacja
PyPi

To działało dobrze dla mnie w przypadku adresu w USA.

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

Uruchomienie 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
źródło
0

W jednym z naszych projektów wykorzystaliśmy następujący parser adresów. Analizuje adresy większości krajów na świecie z dużą dokładnością.

http://address-parser.net/

Jest dostępny jako samodzielna biblioteka lub jako aktywny interfejs API.

Waqas Anwar
źródło
1
Ale to płatny produkt.
Jeremy Thompson,
0

Spóźniłem się na imprezę, oto skrypt Excel VBA, który napisałem lata temu dla Australii. Można go łatwo zmodyfikować, aby obsługiwał inne kraje. Utworzyłem tutaj repozytorium kodu C # w serwisie GitHub. Umieściłem go na swojej stronie i możesz go pobrać tutaj: http://jeremythompson.net/rocks/ParseAddress.xlsm

Strategia

W przypadku każdego kraju z kodem pocztowym, który jest numeryczny lub może być dopasowany do wyrażenia regularnego, moja strategia działa bardzo dobrze:

  1. Najpierw wykrywamy imię i nazwisko, które są uważane za górną linię. Łatwo jest pominąć nazwę i rozpocząć od adresu, odznaczając pole wyboru (o nazwie „Nazwa w górnym wierszu”, jak pokazano poniżej).

  2. Następnie można bezpiecznie oczekiwać, że adres składający się z ulicy i numeru znajduje się przed przedmieściem, a ulica St, Pde, Ave, Av, Rd, Cres, pętla itp. Jest separatorem.

  3. Wykrywanie przedmieścia w porównaniu ze stanem, a nawet krajem może oszukać najbardziej wyrafinowane parsery, ponieważ mogą wystąpić konflikty. Aby temu zaradzić, korzystam z wyszukiwania kodu pocztowego w oparciu o fakt, że po usunięciu numerów ulic i mieszkań / lokali, a także PoBox, Ph, Fax , Mobile itp. Pozostanie tylko numer PostCode. Można to łatwo dopasować za pomocą regEx, aby następnie wyszukać przedmieście (przedmieścia) i kraj.

Twoja poczta krajowa dostarczy bezpłatnie listę kodów pocztowych z przedmieściami i stanami, które możesz przechowywać w arkuszu Excela, tabeli db, pliku text / json / xml itp.

  1. Wreszcie, ponieważ niektóre kody pocztowe mają wiele przedmieść, sprawdzamy, które przedmieście pojawia się w adresie.

Przykład

wprowadź opis obrazu tutaj

Kod VBA

ZRZECZENIE SIĘ, wiem, że ten kod nie jest doskonały, a nawet nie jest dobrze napisany, ale bardzo łatwo go przekonwertować na dowolny język programowania i uruchomić w dowolnej aplikacji. Strategia jest odpowiedzią w zależności od kraju i przepisów, weź ten kod jako przykład :

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
źródło