Як розібрати безкоштовну форму вуличної / поштової адреси з тексту та на компоненти


136

Ми великим чином ведемо бізнес у Сполучених Штатах і намагаємось покращити користувацький досвід, поєднуючи всі адреси адреси в одну текстову область. Але є кілька проблем:

  • Адреса, яку вводять користувачі, може бути неправильною або у стандартному форматі
  • Адреса повинна бути розділена на частини (вулиця, місто, штат тощо) для обробки платежів кредитною карткою
  • Користувачі можуть вводити більше, ніж просто свою адресу (наприклад, їх ім’я чи компанію, що має цю компанію)
  • Google може це зробити, але Умови надання послуг та обмеження на запити є непомітними, особливо при обмеженому бюджеті

Мабуть, це поширене питання:

Чи є спосіб виділити адресу з навколишнього тексту і розбити її на частини? Чи є регулярний вираз для розбору адрес?


Нижче наведені відповіді корисніші, оскільки вони не ігнорують глобальної проблеми - адреси не відповідають загальній схемі.
Марк Максмайстер

Відповіді:


290

Це питання я багато бачив, коли працював у компанії, що підтверджує адресу. Тут я публікую відповідь, щоб зробити її більш доступною для програмістів, які шукають навколо з тим же запитанням. Компанія, в якій я переробляла мільярди адрес, і ми багато чого дізналися в процесі.

Спочатку нам потрібно зрозуміти кілька речей щодо адрес.

Адреси не регулярні

Це означає, що регулярні вирази відсутні. Я бачив це все, від простих регулярних виразів, які відповідають адресам у дуже конкретному форматі, до цього:

/ \ 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}) (суд | ct | вулиця | st | drive | dr | | | смуга | ln | дорога | 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 | OR | PA | RI | SC | SD | TN | TN | TX | UT | VA | VI | VT | WA | WI | WV | WY) ([\ s |, |.] +)? (\ S + \ d {5})? ([\ S |, |.]] +) / я

... до цього, коли файл 900-класового рядка з лінійкою генерує надмасивний регулярний вираз на льоту, щоб збігатися ще більше. Я не рекомендую їх (наприклад, ось загадка вищезгаданого регексу, яка робить багато помилок ). Не існує простої магічної формули, щоб змусити це працювати. У теорії і по теорії, це не представляється можливим , щоб відповідати адреси з регулярним виразом.

Публікація USPS 28 документує безліч можливих форматів адрес із усіма їх ключовими словами та варіантами. Найгірше, що адреси часто неоднозначні. Слова можуть означати більше ніж одне ("St" може бути "Saint" або "Street"), і є слова, які я впевнений, що вони вигадали. (Хто знав, що "Stravenue" - це вуличний суфікс?)

Вам знадобиться код, який дійсно розуміє адреси, і якщо цей код існує, це комерційна таємниця. Але ти, мабуть, можеш закатати своє, якщо ти справді займаєшся цим.

Адреси бувають несподіваних форм і розмірів

Ось кілька надуманих (але повних) адрес:

1)  102 main street
    Anytown, state

2)  400n 600e #2, 52173

3)  p.o. #104 60203

Навіть такі можливі:

4)  829 LKSDFJlkjsdflkjsdljf Bkpw 12345

5)  205 1105 14 90210

Очевидно, що вони не стандартизовані. Значення пунктуації та перерив рядків не гарантується. Ось що відбувається:

  1. Номер 1 є повним, оскільки містить адресу вулиці та місто та штат. З цією інформацією достатньо ідентифікувати адресу, і її можна вважати "доставною" (з деякою стандартизацією).

  2. Номер 2 є повним, оскільки він також містить адресу вулиці (із вторинним / одиничним номером) та 5-значний поштовий індекс, якого достатньо для ідентифікації адреси.

  3. Номер 3 - це повний формат поштової скриньки, оскільки містить поштовий індекс.

  4. Число 4 також є повним, оскільки поштовий індекс унікальний , це означає, що приватне підприємство чи корпорація придбали цей адресний простір. Унікальний поштовий індекс призначений для великих об'ємів або сконцентрованих місць доставки. Все, що адресовано поштовому індексу 12345, йде до General Electric у місті Шенектаді, штат Нью-Йорк. Цей приклад не охопить когось зокрема, але USPS все одно зможе його надати.

  5. Число 5 також повне, вірите чи ні. За допомогою лише цих цифр повну адресу можна виявити, якщо проаналізувати базу даних усіх можливих адрес. Заповнення пропущених напрямків, вторинного позначення та коду ZIP + 4 тривіально, коли ви бачите кожне число як компонент. Ось як це виглядає, повністю розширений та стандартизований:

205 N 1105 Вт 14 січня

Беверлі-Хіллз Каліфорнія 90210-5221

Дані адреси не є вашими власними

У більшості країн, які надають офіційні дані адреси ліцензованим постачальникам, самі дані адреси належать керівному агентству. У США адресам належить USPS. Те саме стосується Canada Post, Royal Mail та інших, хоча кожна країна застосовує або визначає право власності дещо по-різному. Знання цього важливе, оскільки зазвичай забороняє зворотну інженерію бази даних адрес. Ви повинні бути обережними, як отримувати, зберігати та використовувати дані.

Карти Google - це звичайна можливість швидкого виправлення адреси, але TOS досить заборонений; наприклад, ви не можете використовувати їх дані чи API, не показуючи карту Google, і лише для комерційних цілей (якщо ви не платите) і не можете зберігати дані (крім тимчасового кешування). Має сенс. Дані Google є одними з найкращих у світі. Однак Карти Google не підтверджують адресу. Якщо адреса не існує, він ще покаже вам , де адреса буде , якщо він зробив існує (спробувати на власній вулиці, використовуйте номер будинку , який ви знаєте , не існує). Іноді це корисно, але пам’ятайте про це.

Політика використання Nominatim аналогічно обмежує, особливо для великого обсягу та комерційного використання, і дані здебільшого беруться з безкоштовних джерел, тому вони не так добре підтримуються (така природа відкритих проектів) - однак, це все одно може відповідати ваші потреби. Його підтримує велика громада.

У самій USPS є API, але він значно знижується і не має гарантій та підтримки. Це також може бути важким у використанні. Деякі люди користуються нею помірно і без проблем. Але легко пропустити, що USPS вимагає, щоб ви використовували їх API лише для підтвердження адрес для доставки через них.

Люди очікують, що адреси будуть важкими

На жаль, ми зумовили наше суспільство очікувати складності адрес. У всьому Інтернеті про це є десятки хороших статей про UX, але факт полягає в тому, що якщо у вас є адресна форма з окремими полями, це очікують користувачі, хоча це ускладнює отримання кращих адрес, які не відповідають Формат очікується, або, можливо, форма вимагає поля, яке воно не повинно. Або користувачі не знають, куди слід вказати певну частину своєї адреси.

Я можу продовжувати і продовжувати розповідати про погані форми UX для оформлення замовлення в наші дні, але замість цього я просто скажу, що об'єднання адрес в одне поле буде бажаною зміною - люди зможуть ввести свою адресу, як вони вважають за потрібне , а не намагатися з'ясувати вашу тривалу форму. Однак ця зміна стане несподіваною, і користувачі можуть спочатку знайти її трохи неприємними. Просто пам’ятайте про це.

Частину цього болю можна полегшити, поставивши поле країни перед адресою. Коли вони спочатку заповнюють поле країни, ви знаєте, як зробити свою форму. Можливо, у вас є хороший спосіб розібратися з однопольовими американськими адресами, тому якщо вони вибрали Сполучені Штати, ви можете звести форму до одного поля, інакше покажіть поля компонентів. Просто речі, над якими потрібно думати!

Тепер ми знаємо, чому це важко; що ти можеш з цим зробити?

USPS ліцензує виробників через процес під назвою CASS ™ Certification для надання підтверджених адрес клієнтам. Ці постачальники мають доступ до бази даних USPS, що оновлюється щомісяця. Їх програмне забезпечення повинно відповідати жорстким стандартам, які підлягають сертифікації, і вони часто не вимагають згоди з такими обмежувальними умовами, як обговорювалося вище.

Існує багато компаній, сертифікованих CASS, які можуть обробляти списки або мати API: Melissa Data, Experian QAS і SmartyStreets.

(Через те, що ви отримаєте негативну інформацію про "рекламу", я відповів усією своєю відповіддю. Зараз ви вирішуєте знайти рішення, яке працює для вас.)

Правда: Дійсно, люди, я не працюю ні в одній із цих компаній. Це не реклама.


1
Що щодо південноамериканських (Уругвай) адрес? : D
Барт Калікто

11
@Brian - Можливо, тому, що користувач надав багато корисної інформації для тих, хто читає питання та відповіді, незалежно від того, чи вирішили він використовувати продукт його компанії чи ні.
Зарефет

7
@Brian Ці сайти є скребками вмісту. Вони шукають вміст, щоб отримати рейтинги SERP. Я ніколи їх раніше не бачив. Я ніколи ще не публікував цей вміст ні до, ні після цього.
Метт,

2
@khuderm я помітив саме зараз, коли читав ваш коментар, що всі невдоволені коментарі зникли; не впевнений, як / коли це сталося. Але все одно, перегляньте історію редагування моєї відповіді, і ви знайдете пряме посилання на витягувач адрес США, який може вам допомогти. Я створив його, коли працював на своїй останній роботі, але це власний код, тому я не можу поділитися ним ... але вони існують. Сподіваємось, це корисно.
Метт

2
На жаль Вибачте @Matt. Ну я почав слідкувати за вашими запитаннями, а також Github. Ви дуже вражаєте.
Сайка

28

libpostal: бібліотека з відкритим кодом для розбору адрес, навчання з даними з OpenStreetMap, OpenAddresses та OpenCage.

https://github.com/openvenues/libpostal ( детальніше про це )

Інші інструменти / послуги:


13

Існує багато розбірників вуличних адрес. Вони мають два основні смаки - той, у якому є бази даних назв топонімів та назв вулиць, та такий, який цього немає.

Регулярний аналізатор вуличного розбору адрес без особливих проблем може досягти приблизно 95% успішності. Тоді ви починаєте вражати незвичайні випадки. Перл у CPAN, "Гео :: StreetAddress :: US", - про це добре. Є порти Python та Javascript цього, усі з відкритим кодом. У мене вдосконалена версія в Python, яка трохи змінює рівень успішності, обробляючи більше випадків. Щоб отримати останні 3% права, вам знадобляться бази даних, щоб допомогти з розбірливістю.

База даних з 3-значними поштовими індексами та іменами штатів США та абревіатурами США - велика допомога. Коли аналізатор бачить послідовний поштовий індекс та ім'я штату, він може почати фіксуватися у форматі. Це дуже добре працює для США та Великобританії.

Правильний аналіз вулиці починається з кінця і працює назад. Ось так це роблять системи USPS. Адреси є найменш неоднозначними наприкінці, де назви країн, назви міст та поштові індекси можна легко розпізнати. Назви вулиць зазвичай можуть бути ізольованими. Розташування на вулицях є найбільш складним для розбору; там ви стикаєтеся з такими речами, як "П'ятий поверх" та "Штабельний Павільйон". Ось тоді база даних - це велика допомога.


Існує також модуль CPAN Lingua: EN :: AddressParse. Хоча повільніше, ніж "Гео :: StreetAddress :: США, це дає більш високий показник успіху.
Кім Райан

8

ОНОВЛЕННЯ: Geocode.xyz зараз працює у всьому світі. Для прикладів див. Https://geocode.xyz

Щодо США, Мексики та Канади, див. Geocoder.ca .

Наприклад:

Вхід: щось відбувається поблизу перехрестя майна та Артура, що вбиває нью-йорк

Вихід:

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

Ви також можете перевірити результати у веб-інтерфейсі або отримати вихід у вигляді Json або Jsonp. напр. Я шукаю ресторани біля 123 Main Street, Нью-Йорк


Як ви реалізували систему розбору адрес за допомогою openaddress? Ви використовуєте грубу силу стратегії?
Нітін К Аніль

1
Що ви маєте на увазі під «грубою силою»? Розбивання тексту на всі можливі комбінації можливих рядків адреси та порівняння кожного з базою даних адрес не є практичним, і для отримання відповіді буде потрібно більше часу, ніж це робить система. Оперативні адреси є одним із джерел даних для побудови "навчального набору" форматів адрес для алгоритму. Він використовує цю інформацію для розбору адрес із неструктурованого тексту.
Ервін Ручі

2
Ще одна подібна система - Geo :: libpostal ( perltricks.com/article/announcing-geo--libpostal ) Вони також використовують openstreetmap та openaddresses, схоже, для побудови шаблонів адрес на льоту
Ервін Ручі

Я щойно перевірив геопарсер geocode.xyz (надішліть текст, поверніть місце розташування) за сотнями фактичних адрес. З огляду на , пліч-о-пліч з API Google Map, і глобальний набір адрес, geocode.xyz«s scantextметод не вдалося більшу частину часу. Він завжди обирав "Женеву, США" над "Женевою, Швейцарія" і був загалом упереджений США.
Марк Максмайстер

Це залежить від контексту. geocode.xyz/?scantext=Geneva,%20Швейцарія створить: Місце розташування матчів у Женеві, Швейцарія, Оцінка довіри до CH: 0,8, тоді як geocode.xyz/?scantext=Geneva,%20USA створить розташування матчів у Женеві, Оцінка довіри до США: 1.0 Також, Ви можете змінити упередження так: geocode.xyz/?scantext=Geneva,%20USA®ion=CH
Ервін Ручі

4

Немає коду? За сором!

Ось простий аналізатор адрес JavaScript. З кожної окремої причини, яку Метт наводить у своїй дисертації вище, досить жахливо (з якою я майже на 100% згоден: адреси є складними типами, а люди роблять помилки; краще це передавати в аутсорсинг і автоматизувати - коли ви можете собі це дозволити).

Але замість того, щоб плакати, я вирішив спробувати:

Цей код працює добре для розбору більшості результатів Esri дляfindAddressCandidateа також з деякими іншими (зворотними) геокодерами, які повертають однолінійну адресу, де вулиця / місто / штат розділені комами. Ви можете розширити, якщо хочете, або написати парсери для конкретних країн. Або просто використайте це як тематичне дослідження того, наскільки складною може бути ця вправа або наскільки я вразливий у JavaScript. Я визнаю, що витратив на це лише близько тридцяти хвилин (майбутні ітерації могли б додати кеші, валідацію zip та перегляд стану, а також контекст розташування користувача), але це працювало для мого використання: Кінцевий користувач бачить форму, яка аналізує відповідь на пошук геокода на 4 текстові поля. Якщо розбір адреси виявляється неправильним (що рідко, якщо вихідні дані були поганими), це не велика справа - користувач отримує перевірку та виправлення! (Але автоматизовані рішення можуть або відкинути / ігнорувати, або позначити як помилку, тому dev може або підтримувати новий формат, або виправляти вихідні дані.)

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


відмова від відповідальності: мої клієнти володіють своїми адресними даними та запускають власні сервери Esri. Якщо ви захопите дані з google, OSM, ArcGisOnline чи де завгодно, переконайтесь, що правильно зберігати та використовувати їх (у багатьох сервісах є обмеження щодо їхнього зберігання та на тривалий час)
нічого не потрібно

Перша відповідь, наведена вище, наводить переконливий випадок, що ця проблема не вирішується за допомогою регулярних виразів, якщо ви маєте справу з глобальним списком адрес. 200 країн мають занадто багато винятків. У моєму тестуванні ви можете досить надійно визначити країну з рядка, а потім знайти специфічний регулярний вираз для кожної країни - це, мабуть, як краще API.
Марк Максмайстер


2

Ще один варіант для американських адрес - YAddress (зроблений компанією, в якій я працюю).

Багато відповідей на це питання пропонують інструменти геокодування як рішення. Важливо не плутати розбір адреси та геокодування; вони не однакові. Хоча геокодери можуть розбивати адресу на компоненти як побічну перевагу, вони зазвичай покладаються на нестандартні набори адрес. Це означає, що адреса, проаналізована геокодером, може бути не такою, як офіційна адреса. Наприклад, те, що API геокодування Google називає "6th Ave" на Манхеттені, USPS називає "Авеню Америк".


2

Для розгляду адреси США,

Я вважаю за краще використовувати пакунок з адресною адресою, який доступний у піп-програмі лише для посадки

python3 -m pip install usaddress

Документація
PyPi

Це було добре для мене на адресу США.

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

Запуск 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"}

0

В одному з наших проектів ми використовували наступний аналізатор адрес. З високою точністю аналізує адреси більшості країн світу.

http://address-parser.net/

Він доступний як окрема бібліотека, так і як живий API.


1
Але це платний продукт.
Джеремі Томпсон

0

Я спізнююся на вечірку, ось сценарій Excel VBA, який я писав років тому для Австралії. Його можна легко модифікувати для підтримки інших країн. Тут я створив сховище GitHub з кодом C #. Я розмістив його на своєму сайті, і завантажити його можна тут: http://jeremythompson.net/rocks/ParseAddress.xlsm

Стратегія

У будь-якій країні з PostCode, який є числовим чи може бути узгоджений з RegEx, моя стратегія працює дуже добре:

  1. Спочатку ми виявляємо Перше та Прізвище, які вважаються верхніми рядками. Його легко пропустити і почати з адреси, знімаючи прапорець (називається "Ім'я - це верхній рядок", як показано нижче).

  2. Далі безпечно очікувати, що Адреса, що складається з вулиці та номера, прийде до передмістя, а роздільник St, Pde, Ave, Av, Rd, Cres, петля тощо - це роздільник.

  3. Виявлення Передмістя проти штату і навіть країни може обманути найскладніших аналізаторів, оскільки можуть виникнути конфлікти. Щоб подолати це, я використовую пошук PostCode виходячи з того, що після зняття номерів вулиць та квартир / одиниць, а також PoBox, Ph, Fax , Mobile тощо залишиться лише номер PostCode . Це легко узгодити з regEx, щоб потім переглянути передмістя та країни.

Ваша Національна служба поштового зв’язку безкоштовно надасть перелік поштових індексів із Передмістям та Штатами, які ви можете зберігати у листі Excel, таблиці db, файлах text / json / xml тощо.

  1. Нарешті, оскільки деякі поштові коди мають кілька передмістя, ми перевіряємо, яке передмістя відображається в Адреса.

Приклад

введіть тут опис зображення

Код VBA

ВІДМОВА, Я знаю, що цей код не є ідеальним або навіть добре написаним, проте його дуже просто перетворити на будь-яку мову програмування та запустити в будь-якому типі додатків. Стратегія - це відповідь залежно від вашої країни та правил. Візьміть цей код як приклад :

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.