Jak parsować dowolne formatowanie ulicy/adresu pocztowego z tekstu i na komponenty

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

  • adres typów użytkowników może nie być poprawny lub w standardowym formacie
  • adres musi być podzielony na części (ulica, miasto, stan itp.) do przetwarzania płatności kartą kredytową
  • Użytkownicy mogą wpisywać więcej niż tylko swój adres (np. nazwę lub firmę)
  • Google może czy to zrobić, ale Warunki Świadczenia Usług i limity zapytań są wygórowane, zwłaszcza w ograniczonym budżecie

Najwyraźniej jest to częste pytanie:

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

Author: Community, 2012-06-22

7 answers

Często widziałem to pytanie, kiedy pracowałem dla firmy weryfikującej adres. Zamieszczam tutaj odpowiedź, aby uczynić ją bardziej dostępną dla programistów, którzy szukają wokół z tym samym pytaniem. Firma, w której byłem przetwarzane miliardy adresów, i wiele się nauczyliśmy w procesie.

Najpierw musimy zrozumieć kilka rzeczy na temat adresów.

Adresy nie są zwykłe

Oznacza to, że wyrażenia regularne są wyłączone. Widziałem to wszystko, od prostych wyrażenia regularne, które pasują do adresów 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})(court|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

... to to gdzie plik klasy 900 + generuje supermassive regularne wyrażenie w locie, aby dopasować jeszcze więcej. Nie polecam tych (na przykład tutaj jest skrzypek powyższego regex, który popełnia wiele błędów ). Nie ma łatwej magicznej formuły, aby to zadziałało. W teorii i przez teorii nie jest możliwe dopasowanie adresów do wyrażenia regularnego.

USPS Publication 28 dokumentuje wiele formatów adresów, które są możliwe, ze wszystkimi ich słowami kluczowymi i wariantami. Najgorsze ze wszystkich, adresy są często niejednoznaczne. Słowa mogą oznaczać więcej niż jedną rzecz ("St" może być " Święty "lub " Ulica") i są słowa, które jestem prawie pewien, że zostały wymyślone. (Kto by pomyślał ,że "Stravenue" to przyrostek uliczny?)

Potrzebujesz kodu, który naprawdę rozumie adresy, a jeśli ten kod istnieje, to tajemnica handlowa. Ale ty pewnie możesz zrobić swoje, jeśli naprawdę ci się to podoba.

Adresy mają nieoczekiwane kształty i rozmiary

Oto niektóre wymyślone (ale kompletne) adresy:

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 standaryzowane. Znaki interpunkcyjne i podziały linii nie są gwarantowane. Oto co się dzieje:
  1. Numer 1 jest kompletny, ponieważ zawiera adres ulicy oraz miasto i stan. Z tymi informacjami wystarczy zidentyfikować adres i można go uznać za" dostarczalny " (z pewną standaryzacją).

  2. Liczba 2 {[42] } jest kompletna, ponieważ zawiera również adres ulicy (z numerem wtórnym / jednostkowym) i 5-cyfrowy Kod pocztowy, który wystarczy do identyfikacji adresu.

  3. Numer 3 jest kompletnym formatem skrzynki pocztowej, ponieważ zawiera kod pocztowy.

  4. Liczba 4 jest również kompletna, ponieważ kod pocztowy jest unikalny , co oznacza, że prywatna Jednostka lub korporacja zakupiła tę przestrzeń adresową. Unikalny kod pocztowy jest przeznaczony do dużych lub skoncentrowanych miejsc dostawy. Anything adres do kodu pocztowego 12345 idzie do General Electric w Schenectady, NY. Ten przykład nie dotrze do nikogo konkretnego, ale USPS nadal będzie w stanie go dostarczyć.

  5. Numer 5 jest również kompletny, Wierzcie lub nie. Z tymi właśnie numerami, pełny adres może zostać wykryty podczas parsowania z bazą danych wszystkich możliwych adresów. Wypełnienie brakującego oznaczenia kierunkowego, oznaczenia wtórnego i kodu ZIP+4 jest trywialne, gdy widzisz każdą liczbę jako składową. Oto jak wygląda, w pełni rozbudowany i ustandaryzowany:

205 N 1105 W Apt 14

Beverly Hills CA 90210-5221

Dane adresowe nie są twoimi własnymi

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

[2]} Google Maps jest powszechnym rozwiązaniem do szybkich poprawek adresów, ale TOS jest raczej zaporowy; na przykład nie można używać ich danych lub interfejsów API bez pokazywania Mapy Google i tylko do celów niekomercyjnych (chyba że zapłacisz), i nie można przechowywać danych (z wyjątkiem tymczasowego buforowania). To ma sens. Google ' s dane są jednymi z najlepszych na świecie. Jednak Google Maps nie weryfikuje adres. Jeśli adres nie istnieje, nadal pokaże ci, gdzie adres byłby, gdyby istniał (Spróbuj go na własnej ulicy; użyj numeru domu, o którym wiesz, że nie istnieje). Czasami jest to przydatne, ale pamiętaj o tym. Podobnie ogranicza się polityka użytkowania Nominatim, szczególnie w przypadku dużych ilości i zastosowań komercyjnych, a dane pochodzą głównie z wolne źródła, więc nie jest tak dobrze utrzymane (taka jest natura otwartych projektów) - jednak może to nadal odpowiadać Twoim potrzebom. Jest wspierany przez wielką społeczność. [[2]}USPS sam ma API, ale idzie w dół dużo i nie ma żadnych gwarancji ani wsparcia. Może być również trudny w użyciu. Niektórzy ludzie używają go oszczędnie bez żadnych problemów. Ale łatwo jest przegapić to, że USPS wymaga korzystania z ich API tylko do potwierdzania adresów do wysyłki za ich pośrednictwem.

Ludzie spodziewaj się, że adresy będą trudne

Niestety, uwarunkowaliśmy nasze społeczeństwo, aby oczekiwać, że adresy będą skomplikowane. Istnieją dziesiątki dobrych artykułów o tym w Internecie, ale faktem jest, że jeśli masz formularz adresowy z pojedynczymi polami, to tego oczekują użytkownicy, nawet jeśli utrudnia to adresom o małych rozmiarach, które nie pasują do formatu, którego oczekuje formularz, lub może formularz wymaga pola, którego nie powinien. lub użytkownicy nie wiedzą, gdzie umieścić pewną część swoich danych. adres.

Mógłbym mówić dalej o złym UX formularzy kasowych w dzisiejszych czasach, ale zamiast tego powiem tylko, że łączenie adresów w jedno pole będzie mile widzianą zmianą-ludzie będą mogli wpisać swój adres, jak uznają za odpowiedni, zamiast próbować rozgryźć Twój długi formularz. Jednak ta zmiana będzie nieoczekiwana i użytkownicy mogą na początku uznać ją za trochę drażniącą. Bądź tego świadomy.

Część tego bólu można złagodzić poprzez umieszczenie pole przed domem, przed adresem. Gdy najpierw wypełnią pole kraj, wiesz, jak wyświetlić formularz. Być może masz dobry sposób na radzenie sobie z pojedynczymi polami adresów amerykańskich, więc jeśli wybierzesz Stany Zjednoczone, możesz zmniejszyć formularz do jednego pola, w przeciwnym razie Pokaż pola komponentów. Po prostu rzeczy do przemyślenia!

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

[2]}USPS udziela licencji sprzedawcom w procesie zwanym Cass ™ Certification, aby zapewnić zweryfikowane adresy do klientów. Dostawcy ci mają dostęp do bazy 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 posiadających certyfikat CASS, które mogą przetwarzać listy lub mieć interfejsy API: Melissa Data, Experian QAS i SmartyStreets, aby wymienić tylko kilka.

(ze względu na otrzymywanie Flaka za "reklamę" obcięłam w tym momencie moją odpowiedź. On do ciebie, aby znaleźć rozwiązanie, które działa dla Ciebie.)

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

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

Istnieje wiele parserów adresów ulicznych. Występują w dwóch podstawowych smakach-tych, które mają bazy danych Nazw Miejscowości i ulic, i tych, które nie.

Parser adresu ulicy wyrażeń regularnych może osiągnąć 95% wskaźnik sukcesu bez większych problemów. Potem zaczynasz uderzać w nietypowe sprawy. Ten Perl w CPAN, "Geo:: StreetAddress:: US", jest o tym dobrze. Istnieją porty Pythona i Javascript, wszystkie open source. Mam ulepszoną wersję w Pythonie która porusza wskaźnik sukcesu nieznacznie się zwiększył, zajmując się większą liczbą przypadków. Aby uzyskać Ostatnie 3%, potrzebujesz baz danych, które pomogą Ci w disambiguacji.

Baza danych z 3-cyfrowymi kodami pocztowymi oraz nazwami i skrótami Stanów USA jest bardzo pomocna. Gdy parser widzi spójny kod pocztowy i nazwę stanu, może rozpocząć pobieranie do formatu. To działa bardzo dobrze w Stanach Zjednoczonych i WIELKIEJ BRYTANII.

Poprawne parsowanie adresu ulicy zaczyna się od końca i działa wstecz. Tak robią to 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 zwykle można wyodrębnić. Lokalizacje na ulicach są najbardziej złożone do analizy; tam można spotkać rzeczy takie jak "piąte piętro" i "Staples Pavillion". Wtedy baza danych jest bardzo pomocna.

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

Libpostal: biblioteka open-source do analizy adresów, szkolenia z danych z OpenStreetMap, OpenAddresses i OpenCage.

Https://github.com/openvenues/libpostal ( więcej informacji o tym )

Inne narzędzia/usługi:

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

Aktualizacja: Geocode.xyz działa teraz na całym świecie. Przykłady patrz https://geocode.xyz

Dla USA, Meksyku i Kanady patrz geocoder.ca .

Na przykład:

Input: coś się dzieje w pobliżu skrzyżowania main i arthur kill rd new york

Wyjście:

<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 WWW lub uzyskać wyjście jako Json lub Jsonp. np. Szukam restauracji koło 123 Main Street, Nowy Jork

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

Nie ma kodu? Wstyd!

Oto prosty parser adresów JavaScript. To dość straszne z każdego powodu, który Matt podaje w swojej pracy powyżej (z czym 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 dobrze do parsowania większości wyników Esri dla findAddressCandidate, a także z innymi (odwrotnymi)geokoderami, które zwracają adres jednowierszowy, w którym ulica/Miasto/Stan są oddzielone przecinkami. Możesz rozszerzyć, jeśli chcesz lub napisać parsery specyficzne dla danego kraju. Lub po prostu 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ć Cache, walidację zip i wyszukiwanie stanu, a także kontekst lokalizacji użytkownika), ale działało to w moim przypadku użycia: Użytkownik końcowy widzi formularz, który parsuje odpowiedź wyszukiwania geocode do 4 pól tekstowych. Jeśli parsowanie adresów wychodzi źle (co jest rzadkie, chyba że dane źródłowe były słabe) to nic wielkiego - użytkownik może to zweryfikować i naprawić! (Ale dla zautomatyzowanych rozwiązań może albo odrzucić/zignorować lub oznaczyć jako błąd, więc dev może albo wspierać nowy format lub naprawić 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>
 1
Author: nothingisnecessary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-15 19:05:08

W jednym z naszych projektów użyliśmy następującego parsera adresu. Analizuje adresy większości krajów na świecie z dobrą dokładnością.

Http://address-parser.net/

Jest on dostępny jako samodzielna Biblioteka lub jako live API.

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

Jeśli chcesz polegać na danych OSM libpostal jest bardzo wydajny i obsługuje wiele najczęstszych zastrzeżeń z wejściami adresowymi.

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