Как разобрать произвольную улицу / почтовый адрес из текста и по компонентам

136

Мы ведем бизнес в основном в Соединенных Штатах и ​​пытаемся улучшить взаимодействие с пользователем, объединяя все поля адреса в одну текстовую область. Но есть несколько проблем:

  • Адрес, который вводит пользователь, может быть неправильным или в стандартном формате.
  • Адрес должен быть разделен на части (улица, город, штат и т. Д.) Для обработки платежей по кредитным картам.
  • Пользователи могут вводить не только свой адрес (например, свое имя или компанию с ним)
  • Google может сделать это, но Условия предоставления услуг и ограничения запросов являются непомерными, особенно при ограниченном бюджете

Видимо, это общий вопрос:

Есть ли способ изолировать адрес от текста вокруг него и разбить его на куски? Есть ли регулярное выражение для разбора адресов?

Matt
источник
Ответы ниже более полезны, потому что они не игнорируют глобальную проблему - адреса не соответствуют общему шаблону.
Марк Максмейстер

Ответы:

290

Я часто видел этот вопрос, когда работал в компании по проверке адресов. Я публикую ответ здесь, чтобы сделать его более доступным для программистов, которые ищут тот же вопрос. Компания, в которой я работал, обрабатывала миллиарды адресов, и мы многому научились в процессе.

Во-первых, нам нужно кое-что понять об адресах.

Адреса не регулярные

Это означает, что регулярные выражения отсутствуют. Я видел все это, от простых регулярных выражений, которые соответствуют адресам в очень конкретном формате, до этого:

/ \ S + (\ d {2,5} \ S +) ([а | р]?! Т \ б) (([A-Za-Z | \ S +] {1,5}) {1,2}) ? ([\ s |, |.] +) (([A-Za-Z | \ s +] {1,30}) {1,4})? (суд | кт | улица | й | привод | др | полоса | пер | дорога | й | б-р) ([\ s |, | |;.]? +) (([A-Za-Z | \ s +] {1,30}) {1,2}) ([ \ s |, |.] +) \ б (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 | МО | MS | MT | NC | ND | NE | NH | NJ | NM | NV | NY | OH | OK | ИЛИ | PA | RI | SC | SD | TN | TX | UT | VA | VI | VT | WA | WI | WV | WY) ([\ s |, |.] +) (\ s + \ д {5}) ([\ s |, |.]? +) / я

... к этому, где файл класса строк из 900+ генерирует сверхмассивное регулярное выражение на лету, чтобы соответствовать еще большему количеству. Я не рекомендую их (например, вот скрипка из приведенного выше регулярного выражения, которая допускает множество ошибок ). Нет простой волшебной формулы, чтобы заставить это работать. В теории и по теории, это не представляется возможным , чтобы соответствовать адреса с регулярным выражением.

Публикация USPS 28 документирует множество возможных форматов адресов со всеми их ключевыми словами и вариантами. Хуже всего то, что адреса часто неоднозначны. Слова могут означать больше, чем одно («Святой» может быть «Святой» или «Улица»), и есть слова, которые, я уверен, они изобрели. (Кто знал, что "Стрэйвен" был уличным суффиксом?)

Вам понадобится код, который действительно понимает адреса, и если этот код существует, это коммерческая тайна. Но вы, вероятно, можете свернуть свои собственные, если вы действительно в этом.

Адреса бывают неожиданной формы и размера

Вот некоторые придуманные (но полные) адреса:

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 W, кв. 14

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

Адресные данные не ваши

В большинстве стран, которые предоставляют официальные адресные данные лицензированным поставщикам, сами адресные данные принадлежат управляющему агентству. В США USPS владеет адресами. То же самое верно и для Почты Канады, Королевской почты и других, хотя в каждой стране право собственности определяется или определяется по-своему. Знание этого важно, так как обычно оно запрещает обратный инжиниринг базы данных адресов. Вы должны быть осторожны при получении, хранении и использовании данных.

Карты Google - обычное средство для быстрого исправления адресов, но TOS довольно запретительны; например, вы не можете использовать их данные или API-интерфейсы без отображения карты Google и только для некоммерческих целей (если вы не платите), а также вы не можете хранить данные (за исключением временного кэширования). Имеет смысл. Данные Google являются одними из лучших в мире. Однако Google Maps не проверяет адрес. Если адрес не существует, он еще покажет вам , где адрес будет , если он действительно существует (попробовать на собственной улице, используйте номер дома , который вы знаете , не существует). Иногда это полезно, но помните об этом.

Политика использования Nominatim также ограничивает, особенно для больших объемов и коммерческого использования, и данные в основном берутся из бесплатных источников, поэтому они не так хорошо поддерживаются (такова природа открытых проектов) - однако, это может все же подойти твои нужды. Его поддерживает большое сообщество.

У самого USPS есть API, но он сильно падает и не имеет никаких гарантий и поддержки. Это также может быть сложно использовать. Некоторые люди используют его экономно, без проблем. Но легко не заметить, что USPS требует, чтобы вы использовали их API только для подтверждения адресов для доставки через них.

Люди ожидают, что адреса будут сложными

К сожалению, мы заставили наше общество ожидать, что адреса будут сложными. По всему Интернету есть десятки хороших статей по UX, но факт в том, что если у вас есть адресная форма с отдельными полями, это то, чего ожидают пользователи, даже если это усложняет работу с крайними адресами, которые не соответствуют отформатируйте ожидаемую форму, или, возможно, для формы требуется поле, которого не должно быть. Или пользователи не знают, где разместить определенную часть своего адреса.

В наши дни я мог бы продолжать и говорить о плохом UX форм оформления заказа, но вместо этого я просто скажу, что объединение адресов в одно поле будет долгожданным изменением - люди смогут вводить свой адрес так, как они считают нужным. вместо того, чтобы пытаться выяснить вашу длинную форму. Однако это изменение будет неожиданным и поначалу может показаться пользователям немного неприятным. Просто знайте об этом.

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

Теперь мы знаем, почему это сложно; Что вы можете сделать по этому поводу?

USPS лицензирует поставщиков через процесс, называемый CASS ™ Certification, для предоставления клиентам проверенных адресов. Эти поставщики имеют доступ к базе данных USPS, обновляемой ежемесячно. Их программное обеспечение должно соответствовать строгим стандартам для сертификации, и они не часто требуют согласия на такие ограничивающие условия, как описано выше.

Существует много компаний, сертифицированных CASS, которые могут обрабатывать списки или иметь API: Melissa Data, Experian QAS и SmartyStreets.

(В связи с тем, что я получил «рекламу», я обрезал свой ответ на этом этапе. Вам решать, какое решение подойдет вам.)

Правда: действительно, ребята, я не работаю ни в одной из этих компаний. Это не реклама.

Matt
источник
1
А как насчет адресов Южной Америки (Уругвай)? : D
Барт Каликсто
11
@Brian - Возможно, потому что пользователь предоставил много полезной информации для тех, кто читает вопрос и ответ, независимо от того, решат ли они использовать продукт своей компании.
Зарепет
7
@Brian Эти сайты - парсеры контента. Они собирают контент, чтобы получить рейтинг в поисковой выдаче. Я никогда их раньше не видел. Я никогда не публиковал этот контент ни до, ни после.
Мэтт
2
@khuderm Я только что заметил, когда прочитал ваш комментарий, что все несогласные комментарии исчезли; не знаю, как и когда это произошло. Но в любом случае посмотрите историю редактирования моего ответа, и вы найдете прямую ссылку на средство извлечения адресов в США, которое может вам помочь. Я построил его, когда работал на последней работе, но это проприетарный код, поэтому я не могу им поделиться ... но они существуют. Надеюсь, это поможет.
Мэтт
2
К сожалению. Извините @Matt. Ну, я начал следить за тобой через твои вопросы, а также за Github. Вы впечатляет.
Sayka 04
28

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

https://github.com/openvenues/libpostal ( дополнительная информация об этом )

Другие инструменты / услуги:

Дэвид Портабелла
источник
13

Есть много парсеров адресов. Они бывают двух основных типов: те, которые имеют базы данных названий мест и улиц, и те, которые не имеют.

Синтаксический анализатор уличных адресов с регулярным выражением может достигать 95% успеха без особых проблем. Тогда вы начинаете поражать необычные случаи. Perl в CPAN, "Geo :: StreetAddress :: US", примерно так хорош. Есть порты Python и Javascript, все с открытым исходным кодом. У меня есть улучшенная версия в Python, которая немного увеличивает вероятность успеха, обрабатывая больше случаев. Однако, чтобы получить последние 3%, вам нужны базы данных, чтобы помочь в устранении неоднозначности.

База данных с 3-значными почтовыми индексами, а также названиями и сокращениями штатов США очень помогает. Когда анализатор видит непротиворечивый почтовый индекс и название штата, он может начать привязываться к формату. Это очень хорошо работает для США и Великобритании.

Правильный синтаксический анализ адреса начинается с конца и работает в обратном направлении. Вот как это делают системы USPS. Адреса наименее неоднозначны в конце, где названия стран, названия городов и почтовые индексы относительно легко распознать. Названия улиц обычно можно выделить отдельно. Места на улицах сложнее всего анализировать; там вы встретите такие вещи, как «Пятый этаж» и «Стейплс Павильон». Вот когда база данных очень помогает.

Джон Нэгл
источник
Существует также модуль CPAN Lingua: EN :: AddressParse. Хотя он медленнее, чем «Geo :: StreetAddress :: US», он дает более высокий уровень успеха.
Ким Райан
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Швейцария произведет: Match Location Geneva, Switzerland, CH Confidence Score: 0,8, в то время как geocode.xyz/?scantext=Geneva,%20USA произведет Match Location Geneva, рейтинг достоверности США: 1.0 Также, Вы можете сместить
Эрвин Ручи
4

Нет кода? Стыдно!

Вот простой парсер адресов JavaScript. Это довольно ужасно по каждой причине, которую Мэтт приводит в своей диссертации выше (с чем я почти на 100% согласен: адреса - это сложные типы, а люди совершают ошибки; лучше, если вы можете себе это позволить, использовать это на стороне и автоматизировать).

Но вместо того, чтобы плакать, я решил попробовать:

Этот код работает нормально для анализа большинства результатов Esri дляfindAddressCandidateа также с некоторыми другими (обратными) геокодерами, которые возвращают однострочный адрес, где улица / город / штат разделены запятыми. Вы можете расширить, если хотите, или написать парсер для конкретной страны. Или просто используйте это как пример того, насколько сложным может быть это упражнение или насколько я паршив в JavaScript. Признаюсь, я потратил на это всего около тридцати минут (будущие итерации могут добавить кеши, проверку почтового индекса и поиск состояния, а также контекст местоположения пользователя), но это сработало для моего варианта использования: конечный пользователь видит форму, которая анализирует ответ поиска геокода на 4 Textboxes. Если разбор адреса оказывается неправильным (что бывает редко, если исходные данные не были плохими), ничего страшного - пользователь может проверить и исправить это! (Но для автоматизированных решений можно либо отбросить / игнорировать, либо пометить как ошибку, чтобы разработчик мог либо поддерживать новый формат, либо исправлять исходные данные.)

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

nothingisnecessary
источник
отказ от ответственности: мои клиенты владеют своими адресными данными и используют свои собственные серверы Esri. Если вы берете данные из Google, OSM, ArcGisOnline или где-либо еще, убедитесь, что их можно хранить и использовать (у многих сервисов есть ограничения на то, как вы можете хранить и как долго)
ничего не нужно
Первый ответ выше убедительно свидетельствует о том, что эта проблема неразрешима с помощью регулярных выражений, если вы имеете дело с глобальным списком адресов. В 200 странах слишком много исключений. В моем тестировании вы можете достаточно надежно определить страну по строке, а затем найти конкретное регулярное выражение для каждой страны - вероятно, именно так работают лучшие API.
Марк Максмайстер
2

Если вы хотите полагаться на данные OSM, libpostal очень мощный и обрабатывает множество наиболее распространенных ошибок при вводе адреса.

Витор Магальяйнш
источник
Я думаю, что ваш ответ является дубликатом этого поста. Однако хорошее предложение.
Майкл
2

Другой вариант для адресов в США - это YAddress (созданный компанией, в которой я работаю).

Многие ответы на этот вопрос предлагают в качестве решения инструменты геокодирования. Важно не путать парсинг адресов и геокодирование; Они не одинаковы. Хотя геокодеры могут разбивать адрес на компоненты в качестве побочного преимущества, они обычно полагаются на нестандартные наборы адресов. Это означает, что адрес, проанализированный геокодером, может не совпадать с официальным адресом. Например, то, что API геокодирования Google называет «6-й авеню» на Манхэттене, USPS называет «авеню Америки».

Майкл Дёмин
источник
2

Для анализа адресов США

Я предпочитаю использовать пакет usaddress, который доступен в pip только для usaddress

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"}
theBuzzyCoder
источник
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, loop и т. Д. Являются разделителями.

  3. Обнаружение пригорода против штата и даже страны может обмануть самые сложные парсеры, поскольку могут возникнуть конфликты. Чтобы преодолеть это, я использую поиск по PostCode, основываясь на том факте, что после удаления номеров улиц и квартир / квартир, а также PoBox, Ph, Fax , Mobile и т. Д. Останется только номер PostCode. Это легко сопоставить с regEx, чтобы потом искать пригород (ы) и страну.

Ваша Национальная почтовая служба бесплатно предоставит список почтовых индексов в пригородах и штатах, которые вы можете сохранить в листе Excel, в таблице БД, в файле 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
Джереми Томпсон
источник