Где найти годный парсер адресов?

Имеется огромный список адресов в виде строк в базе данных.
Требуется распарсить строку.

Ключевая задача, разложить все по полочкам.
Подозреваю, наверное есть готовые решения, библиотеки.

Пример списка.
Адреса

Советский просп., 57, Кемерово
Молодёжный просп., 2, Кемерово
просп. Ленина, 134, Кемерово
Кузнецкий просп., 90, Кемерово
просп. Химиков, 19, Кемерово
просп. Ленина, 124, Кемерово
Кузнецкий просп., 79/2, Кемерово
Кузнецкий просп., 33/1, Кемерово
ул. Кирова, 37, Кемерово
Октябрьский просп., 34, Кемерово
Советский просп., 57, Кемерово
просп. Ленина, 90/1, Кемерово
просп. Ленина, 7, Кемерово
Октябрьский просп., 34, Кемерово
Советский просп., 47, Кемерово
бул. Строителей, 15, Кемерово
Молодёжный просп., 2, Кемерово
Октябрьский просп., 34, Кемерово
просп. Ленина, 135, Кемерово
просп. Ленина, 45, Кемерово
Советский просп., 33, Кемерово
просп. Ленина, 90/1, Кемерово
просп. Ленина, 59А, Кемерово
Октябрьский просп., 34, Кемерово
ул. Сибиряков-Гвардейцев, 11, Кемерово
Октябрьский просп., 34, Кемерово
Октябрьский просп., 34, Кемерово
просп. Ленина, 1, Кемерово
ул. Кирова, 37, Кемерово
Молодёжный просп., 2, Кемерово
просп. Ленина, 103, Кемерово
бул. Строителей, 28, Кемерово
просп. Ленина, 90/1, Кемерово
бул. Строителей, 28, Кемерово
бул. Строителей, 28, Кемерово
ул. Кирова, 16, Кемерово
просп. Ленина, 59А, Кемерово
Октябрьский просп., 34, Кемерово
Октябрьский просп., 9, Кемерово
просп. Ленина, 75, Кемерово
Ленинградский просп., 22, Кемерово
Россия, Кемерово, проспект Ленина
Октябрьский просп., 56, Кемерово
просп. Ленина, 75, Кемерово
ул. Ворошилова, 21, Кемерово
Весенняя ул., 21, Кемерово
Россия, Кемерово, улица Свободы
Октябрьский просп., 34, Кемерово
Октябрьский просп., 34, Кемерово
Октябрьский просп., 30, Кемерово
ул. Свободы, 3, Кемерово
Кузнецкий просп., 33Б, Кемерово
ул. Терешковой, 41Б, Кемерово
Советский просп., 70, Кемерово
Притомский просп., 7/3, Кемерово
ул. Гагарина, 124А, Кемерово
Октябрьский просп., 9, Кемерово
Советский просп., 72, Кемерово
Октябрьский просп., 65, Кемерово
Весенняя ул., 16, Кемерово
бул. Строителей, 55, Кемерово
Кузнецкий просп., 10А, Кемерово
Октябрьский просп., 30, Кемерово
Красная ул., 14А, Кемерово
ул. Сибиряков-Гвардейцев, 26, Кемерово
ул. Кирова, 41, Кемерово
ул. Сибиряков-Гвардейцев, 189/3, корп. 3, Кемерово
ул. Кирова, 41, Кемерово
Октябрьский просп., 30, Кемерово
ул. Тухачевского, 31/3, Кемерово
просп. Ленина, 98, Кемерово
Октябрьский просп., 28, Кемерово
ул. Рутгерса, 32, Кемерово
Ноградская ул., 5, Кемерово
Советский просп., 70, Кемерово
ул. Тухачевского, 22В, Кемерово
просп. Ленина, 49, Кемерово
Кузнецкий просп., 85, Кемерово
Советский просп., 70, Кемерово


До этого, я решал подобную задачу, но это решение оставляло желать лучшего.
Как я это делал.
Составил список регулярных выражений.

JSON

[
	{
		"reg_ex": "бульвар\\s+([А-яёъь 0-9\\.\\-]+)(,|$)",
		"type": "бульвар",
		"group": 1
	},
	{
		"reg_ex": ".*?,([А-яёъь 0-9\\.\\-]+)бульвар",
		"type": "бульвар",
		"group": 1
	},
	{
		"reg_ex": ".*?,([А-яёъь 0-9\\.\\-]+)пер($|,)",
		"type": "переулок",
		"group": 1
	},
	{
		"reg_ex": "улица\\s+([А-яёъь 0-9\\.\\-]+)(,|$)",
		"type": "улица",
		"group": 1
	},
	{
		"reg_ex": "ул\\.\\s+([А-яёъь 0-9\\.\\-]+)(,|$)",
		"type": "улица",
		"group": 1
	},
	{
		"reg_ex": "(ул).\\s+([А-яёъь 0-9\\.\\-]+)($|,|д\\.)",
		"type": "улица",
		"group": 2
	},
	{
		"reg_ex": ".*?,([А-яёъь 0-9\\.\\-]+)улица($|,)",
		"type": "улица",
		"group": 1
	},
	{
		"reg_ex": ".*?,([А-яёъь 0-9\\.\\-]+)ул\\.($|,)",
		"type": "улица",
		"group": 1
	},
	{
		"reg_ex": "ул\\.([А-яёъь 0-9\\.\\-]+)",
		"type": "улица",
		"group": 1
	},
	{
		"reg_ex": ",\\s+улица([A-zА-яёъь0-9\\.\\- ]+)",
		"type": "улица",
		"group": 1
	},
	{
		"reg_ex": "переулок\\s+(.*?)($|,)",
		"type": "переулок",
		"group": 1
	},
	{
		"reg_ex": ".*?,([A-zА-яёъь 0-9\\.\\-]+)переулок",
		"type": "переулок",
		"group": 1
	},
	{
		"reg_ex": "площадь\\s+([A-zА-яёъь 0-9\\.\\-]+)($|,)",
		"type": "площадь",
		"group": 1
	},
	{
		"reg_ex": ".*?,([А-яёъь 0-9\\.\\-]+)площадь",
		"type": "площадь",
		"group": 1
	},
	{
		"reg_ex": "проезд\\s+(.*?)($|,)",
		"type": "проезд",
		"group": 1
	},
	{
		"reg_ex": ",([А-яёъь 0-9\\-]+)проезд",
		"type": "проезд",
		"group": 1
	},
	{
		"reg_ex": ".*?,([A-zА-яёъь 0-9 \\-]+)пр.*?д",
		"type": "проезд",
		"group": 1
	},
	{
		"reg_ex": ".*?,([A-zА-яёъь0-9\\.\\-]+)проезд",
		"type": "проезд",
		"group": 1
	},
	{
		"reg_ex": ".*?,([A-zА-яёъь 0-9 \\-]+)переезд",
		"type": "переезд",
		"group": 1
	},
	{
		"reg_ex": "шоссе\\s+(.*?)($|,)",
		"type": "шоссе",
		"group": 1
	},
	{
		"reg_ex": ",\\s+([А-яA-z0-9 \\.\\-]+)\\s+ш.",
		"type": "шоссе",
		"group": 1
	},
	{
		"reg_ex": ".*?,([A-zА-яёъь 0-9\\.\\-]+)шоссе($|,)",
		"type": "шоссе",
		"group": 1
	},
	{
		"reg_ex": "проспект\\s+(.*?)($|,)",
		"type": "проспект",
		"group": 1
	},
	{
		"reg_ex": ",\\s+([A-zА-я.0-9\\.\\- ]+)\\s+просп.",
		"type": "проспект",
		"group": 1
	},
	{
		"reg_ex": ",\\s+пр.*?т\\s+([A-zА-я.0-9\\.\\- ]+)",
		"type": "проспект",
		"group": 1
	},
	{
		"reg_ex": ".*?,([А-яёъь 0-9\\.\\- ]+)проспект($|,)",
		"type": "проспект",
		"group": 1
	},
	{
		"reg_ex": ",\\s+просп.\\s+([A-zА-я.0-9\\.\\- ]+)",
		"type": "проспект",
		"group": 1
	},
	{
		"reg_ex": ",\\s+([A-zА-яёъь0-9 \\-\\.]+)\\s+пр\\.",
		"type": "проспект",
		"group": 1
	},
	{
		"reg_ex": "дорога\\s+(.*?)($|,)",
		"type": "дорога",
		"group": 1
	},
	{
		"reg_ex": ",\\s+([A-zА-я.0-9\\.\\- ]+)\\s+дорога",
		"type": "дорога",
		"group": 1
	},
	{
		"reg_ex": "набережная\\s+(.*?)($|,)",
		"type": "набережная",
		"group": 1
	},
	{
		"reg_ex": ".*?,([А-яёъь 0-9\\.\\- ]+)набережная($|,)",
		"type": "набережная",
		"group": 1
	},
	{
		"reg_ex": ".*?,([А-яёъь 0-9\\.\\- ]+)магистраль($|,)",
		"type": "магистраль",
		"group": 1
	},
	{
		"reg_ex": "квартал\\s+([А-яёъь 0-9\\.\\- ]+)($|,)",
		"type": "квартал",
		"group": 1
	},
	{
		"reg_ex": ".*?аллея([А-яёъь 0-9\\.\\- ]+)",
		"type": "аллея",
		"group": 1
	},
	{
		"reg_ex": ",\\s+аллея([А-я0-9\\.\\- ]+)",
		"type": "аллея",
		"group": 1
	},
	{
		"reg_ex": ",\\s+([A-zА-я.0-9 ]+)\\s+аллея",
		"type": "аллея",
		"group": 1
	},
	{
		"reg_ex": ".*?,([А-яёъь 0-9 \\-]+)тупик",
		"type": "тупик",
		"group": 1
	},
	{
		"reg_ex": ".*?,([А-яёъь 0-9 \\-]+)парк",
		"type": "парк",
		"group": 1
	},
	{
		"reg_ex": ",\\s+([A-zА-я0-9 ]+)\\s+просек",
		"type": "просек",
		"group": 1
	},
	{
		"reg_ex": ",\\s+([A-zА-я.0-9 \\-\\.]+)\\s+тракт",
		"type": "тракт",
		"group": 1
	},
	{
		"reg_ex": ",\\s+([A-zА-яёъь0-9 \\-\\.]+)\\s+сквер",
		"type": "сквер",
		"group": 1
	},
	{
		"reg_ex": ",\\s+([A-zА-яёъь0-9 \\-\\.]+)\\s+пер\\.",
		"type": "пер",
		"group": 1
	},
	{
		"reg_ex": ",\\s+(.*(линия)[А-я ]+)",
		"type": "линия",
		"group": 1
	},
	{
		"reg_ex": ",\\s+([A-zА-яёъь0-9\\-\\. ]+)\\s+линия",
		"type": "линия",
		"group": 1
	},
	{
		"reg_ex": ",\\s+пр\\s+([A-zА-яёъь0-9\\-\\. ]+)",
		"type": "проезд",
		"group": 1
	},
	{
		"reg_ex": ",\\s+посёлок\\s+([A-zА-яёъь0-9\\-\\. ]+)",
		"type": "посёлок",
		"group": 1
	},
	{
		"reg_ex": ",\\s+пл\\.\\s+([A-zА-яёъь0-9\\-\\. ]+)",
		"type": "площадь",
		"group": 1
	},
	{
		"reg_ex": ",\\s+([A-zА-яёъь0-9\\-\\. ]+)спуск",
		"type": "спуск",
		"group": 1
	},
	{
		"reg_ex": ",\\s+сквер\\s+([A-zА-яёъь0-9\\-\\. ]+)",
		"type": "сквер",
		"group": 1
	},
	{
		"reg_ex": ",\\s+станция\\s+([A-zА-яёъь0-9\\-\\. ]+)",
		"type": "станция",
		"group": 1
	},
	{
		"reg_ex": ",\\s+([A-zА-яёъь0-9\\-\\. ]+)(К|к)вартал",
		"type": "квартал",
		"group": 1
	}
]



И каждую строку прогонял через определённый алгоритм.
Точность конечно так себе была.

Надеюсь, что есть готовые решения.
Если кто встречал таковое, дайте знать.

Спасибо.
  • Вопрос задан
  • 3578 просмотров
Пригласить эксперта
Ответы на вопрос 3
AntonKravchenko
@AntonKravchenko
воспользуйтесь сервисом dadata.ru?
Ответ написан
trapwalker
@trapwalker
Программист, энтузиаст
У вас в примере очень чистые аккуратные адреса.
Не очень понятно что вы имели в виду под "разложить по полочкам" и какие именно вам нужны полочки.
Общий подход для переработки таких массивов слабо структурированных данных следующий.
  1. Процесс переработки должен быть поэтапный. Каждый этап должен вносить минимальные не калечащие изменения в как можно бОльшую часть датасета.
  2. Каждый этап должен быть прозрачным. Затронутые модификацией значения можно для удобства контроля (глазами) дедуплицировать и сортировать. Это позволит легко увидеть любые аномалии.
  3. Нужно вести подробную статистику изменений на каждом этапе. Все выбросы статистики должны проверяться глазами: слишком короткое минимальное слово или число слов, слишком длинные слова или наборы, слишком большие или малые числа...
  4. На каждом этапе нужно сохранять предыдущее состояние датасета или держать исходное и иметь удобный инструментарий для быстрого полного накатывания всех этапов на исходный датасет. Иногда этапы имеет смысл переставить местами, потому что обнаружилось незамеченное ранее калечащее изменение пару этапов назад.
  5. Серия мелких правок простыми операциями предпочтительнее сложных алгоритмов и регэкспов. Например в вашем случае гораздо лучше серией прсотых и понятных реплейсов заменить "ул." на "улица", "пл." на "площадь" и т.д.
    Чтобы не городить сложные регекспы имеет смысл сплитнуть строки по словам и заменять слова целиком, либо одним из шагов добавить в начало и конец строки пробелы. Это упростит регекспы и не даст наделать незаметных сразу косяков.
    Затронутые реплейсами строки можно отсортирвоать и дедуплицировать для просмотра глазами. Вдруг у вас есть много переулков Плава-Лагуны, которые принято тоже так сокращать.
  6. Если у вас есть справочник улиц, можно все записи в обработанном датасете, для которых нашлось однозначное соответствие улицы из справочника, пометить специальным флагом и больше не трогать в них улицы. Остаток можно снова подвергнуть сортировке и дедупликации (заодно можно частотность посчитать) и вычищать массово или точечно начиная с чамых частых случаев реплейсами, регекспами, и т.д.

В вашем случае:
  1. Разделите адреса по запятой, сохраните в отдельные поля. лучше CSV, а не в БД. Проще. При обнаружении больше двух (в вашем случае) запятых сразу падайте, кричите, считайте число таких случаев, решайте вручную, если их не много, делайте отдельный фильтр с фиксом на отдельном этапе если много.
  2. Отсортируйте и дедуплицируйте (на секундочку) все три столбца по отдельности, просмотрите глазами получившиеся множества, поищите проблемы. Автоматизируйте этот шаг, он вам понадобится еще не раз.
  3. Обработайте датасет серией очевидных реплейсов, которые снизят разнообразие. Устранятся сокращения, множественные варианты одного и того же написания, различия в регистрах букв.
  4. Вытащите из ФИАС каталог улиц. Можно взять в формате КЛАДР, там попроще может оказаться и в отдельном файле. Разметьте очищаемый датасет ссылками на найденные в справочнике улицы. Просмотрите дедуплицированные и отсортированные (по алфавиту и по частотности) оставшиеся улицы. Поищите стандартные проблемы, которые можно пофиксить автоматически, внесите ручные правки для узких частных случаев (но программно реплейсами в отдельном этапе, чтобы можно было повторно обработать исходный датасет).


Если у вас датасет весь примерно такой, как в примере, то я за пару тысяч рублей его вам почищу независимо от его размера. Почти независимо.=)
Ответ написан
firedragon
@firedragon
Не джун-мидл-сеньор, а трус-балбес-бывалый.
Ищите на гитхабе по фразе ФИАС
https://github.com/zabralex85/fias.parser

Из бонусов будет абсолютно точный адрес, из минусов даже оптимизированная база занимает 10 гигов.
Впрочем я у себя ужал данные до 100 килобайт, но мне нужно было только регионы и города
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы