Из инженерного блога Омни Карт · ← На главную
Блог / Инженерия данных

1,73 млн POI по Москве: чему нас научила нормализация и дедупликация городских данных

Мы собрали данные о городе из пяти источников. Наивное объединение дало почти 2,6 млн записей с дублями и противоречиями. Рассказываем, как из этого получилось 1 732 054 чистых объекта.

27 мая 2025 г. ≈ 9 мин чтения
Короткий ответ обновляется ежедневно

Нормализация геоданных — это приведение записей о городских объектах из разных источников к единому виду: один формат адреса, одна категория, одни координаты и одна запись на каждый реальный бизнес. Мы собирали данные о Москве из пяти источников, и наивное объединение дало почти 2,6 млн записей: с дублями, противоречивыми адресами, давно закрытыми точками и категориями из несовместимых классификаторов. После дедупликации POI и конфляции данных осталось 1 732 054 объекта, у каждого 28+ атрибутов.

Дальше — как мы туда пришли и где было особенно больно.

Откуда берутся данные о городе и почему их нельзя просто склеить

У нас пять источников, и каждый врёт по-своему.

Открытые данные (OpenStreetMap) дают широкое покрытие и бесплатны, но качество скачет от района к району: в центре подробно, на окраинах дырки. Координаты часто есть, а нормального адреса нет. Плюс OSM помнит город таким, каким его когда-то занесли волонтёры, и не всегда замечает, что заведение закрылось.

Партнёрские фиды (например 2ГИС, по коммерческому соглашению) — наоборот, структурированы и свежи, с телефонами и часами работы. Но у них своя рубрикация, которая не совпадает ни с OSM, ни с нашей.

Собственный краулинг приносит то, чего ещё нет в фидах: только что открывшиеся точки, временные изменения. Цена за свежесть — шум: опечатки, дубли, юридические названия вместо вывесок.

Мобильные SDK-панели — это не список POI, а косвенный признак того, что место вообще живёт. Анонимизированные геоданные показывают, ходят ли вообще люди в это место (модель, похожая на Placer.ai и Foursquare на западном рынке). Для самой записи это не имя и не адрес, но отличный способ понять, жива точка или это призрак.

Спутниковые снимки помогают там, где источники спорят о координатах: где физически стоит здание, где въезд, на месте ли вообще постройка. Росреестр через официальные каналы даёт владение и историю переходов права — это уже про коммерческую недвижимость, а не про вывеску кафе.

Теперь представьте, что вы просто свалили всё это в одну таблицу. Одна и та же «Кофемания» приедет к вам три раза в трёх написаниях, её адрес будет в четырёх форматах (включая пустой), категория — в трёх классификаторах, а рядом будет лежать кофейня, которая закрылась в 2019-м. Склеить пять источников — это не UNION ALL. Это отдельная инженерная задача, и вот её карта.

Пайплайн // дедупликация + конфляция
Как пять источников становятся одной базой
live pipeline
Источники ≈ 2,6 млн сырых записей
OSM2ГИСкраулингSDK-панелиспутникРосреестр
01
Нормализация
адреса → ГАР/ФИАС · имена → транслит · категории → онтология
02
Блокинг
geohash-сетка · кандидаты только внутри ячейки · ≈10¹² → ≈10⁷ пар
03
Матчинг− 870K дублей
имя + дистанция + категория + телефон · скоринг ≥ порога
04
Конфляция
золотая запись · приоритет источника по полям · провенанс
05
Контроль качества
закрытые точки · ложные дубли · спорное → на ручную проверку
Выход 1 732 054 POI · 28+ полей · API · MCP · SQL · ежедневно

Каждый источник проходит нормализацию полей, затем блокинг и матчинг; совпавшие записи сливаются в одну золотую с историей происхождения каждого поля. На входе — ≈ 2,6 млн сырых записей, на выходе — 1 732 054 объекта.

Дедупликация POI: один бизнес и пять записей о нём

Самая частая поломка при объединении источников — дубли. Один и тот же объект приходит из разных мест и притворяется разными объектами.

Канонический пример из нашей базы — кофейня на Малой Бронной. В OSM она лежит как «кафе Кофемания», в партнёрском фиде — как «Кофемания», в краулинге — как «КОФЕМАНИЯ (ООО „КМ“)». Одно место, три записи. Умножьте на 2,6 миллиона входных строк, и масштаб проблемы становится понятен.

Почему «каждый с каждым» не работает

Очевидное решение — сравнить каждую запись с каждой и поискать совпадения. На 2,6 млн записей это около трёх триллионов пар. Даже если одно сравнение занимает микросекунды, вы будете ждать неделями. Так не пойдёт.

Поэтому сначала мы режем пространство кандидатов через блокинг. Идея простая: два объекта могут быть одним бизнесом, только если они физически рядом. Кофейня на Малой Бронной не дубль кофейни в Бутово. Мы накрываем город сеткой на основе geohash (способ закодировать координаты в короткую строку так, что у соседних точек совпадает начало кода) и сравниваем записи только внутри одной ячейки и её соседей. Это снижает число пар-кандидатов с порядка 1012 до примерно 107, то есть в сотни тысяч раз. Дальше уже можно считать честно.

Как мы решаем, что две записи — это один объект

Внутри ячейки мы для каждой пары считаем оценку похожести по нескольким сигналам сразу:

  • близость названий, но не буквальная, а с нормализацией: приводим к нижнему регистру, выкидываем юридические формы (ООО, ИП, ЗАО), снимаем категорию, прилипшую к имени («кафе Кофемания» → «кофемания»), сводим ё и е, разбираем транслитерацию (Coffeemania и Кофемания — одно и то же) и ловим омоглифы, когда латинская a притворяется кириллической;
  • расстояние между координатами (порог в десятки метров для одной категории);
  • совместимость категорий;
  • совпадение телефона и нормализованного адреса.

Сигналы складываются во взвешенную оценку, и если она выше порога — это один объект.

Но есть ловушка в обратную сторону, и она опаснее. Если матчить только по расстоянию, начинается переслияние. В городе полно точек внутри точек: кофейня в книжном магазине, банкомат в лобби банка, островок с маникюром в торговом центре. Координаты у них почти совпадают, но это разные бизнесы. Сольёшь их по дистанции — и уничтожишь реальные POI.

Поэтому близость нужна, но её недостаточно: чтобы оставить записи раздельными, у них должны сильно расходиться имя и категория. И наоборот — сетевые точки требуют аккуратности. «Аптека 36,6» — это сотни реальных филиалов по городу. Внутри одного источника их нельзя схлопывать в одну запись, а между источниками нужно слить дубль каждого конкретного филиала. Грань тонкая, и большая часть работы матчера — именно про неё.

Нормализация // одна запись
Грязная запись → чистая запись
Кандидаты 3 записи об одном месте + 1 отклонённая
OSM кафе Кофеманияул. М. Бронная, 22 · amenity=cafe нет № строения
2ГИС КофеманияМалая Бронная, д.22, стр.2 · Кафе / Кофейни иная таксономия
краулинг КОФЕМАНИЯ (ООО «КМ»)адрес — · общепит юр. форма · пустой адрес
КофехаузБ. Бронная, 8 закрыто в 2019 · нет трафика
матчинг · ГАР · конфляция · 3 → 1
Золотая запись poi_id 7f3a91…
Кофемания
категориякафе
адресМалая Бронная, 22, стр. 2
районПресненский · ЦАО
метроМаяковская · 240 м
координаты55.7641, 37.5952
статусоткрыто ✓
phonehoursprice_tierchainverified+ ещё 21 поле
источники: координаты ← спутник · адрес ← ГАР · часы ← 2ГИС · имя ← консенсус · 28+ полей, обновление ежедневно

Три кандидата из разных источников и одна закрытая точка, которую матчер отклонил, превращаются в одну запись с понятным происхождением каждого поля — всего 28+ атрибутов.

Очистка адресных данных: «Малая Бронная, 22 стр. 2» в пяти вариантах

Если дедупликация — это про то, какие записи об одном объекте, то очистка адресных данных — про то, как вообще понять, где этот объект стоит. И с русскими адресами это отдельный жанр боли.

Один и тот же адрес приезжает к нам так: «ул. Малая Бронная, 22», «Малая Бронная, д.22, стр.2», «М. Бронная 22с2» и, классика, пустой строкой. Сокращения живут в зоопарке: ул. и улица, д. и дом, стр. (строение), корп. (корпус), литеры. Номера домов теряются: примерно у трети записей из открытого слоя нормального номера дома просто нет.

Мы разбираем адрес на структурные части и сводим к канону через государственный адресный реестр (ГАР, бывший ФИАС), а затем геокодируем в эталонные координаты. После этого «22 стр. 2» в любом из пяти написаний — это одна и та же точка на карте.

Отдельный случай — бизнес-центры. Одно здание, один адрес, а внутри десятки организаций на разных этажах. Адрес тут перестаёт различать объекты, и приходится опираться на координаты, имя и, где есть, этаж. Адрес — необходимое поле, но не идентификатор.

Конфляция данных: как из пяти записей собрать одну золотую

Когда матчер решил, что несколько записей — это один объект, их надо слить. Это и есть конфляция данных, и её часто путают с дедупликацией. Разница простая: дедупликация отвечает на вопрос «это один и тот же бизнес?», а конфляция — «какое значение каждого поля в итоговой записи правильное?».

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

  • координаты — со спутника и собственной съёмки, они точнее всего;
  • часы работы и телефон — из партнёрского фида, он свежий и структурированный;
  • название — по консенсусу источников, с предпочтением вывески, а не юрлица;
  • владение — из Росреестра;
  • категория — из нашего сведённого классификатора.

При этом мы храним провенанс: какое поле от какого источника пришло. Это нужно, чтобы потом можно было проверить запись и откатить ошибку, если источник наврал. На выходе получается одна запись с 28 с лишним атрибутами, и про каждый известно, откуда он взялся.

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

Призраки — это тихая беда геоданных. Заведение закрылось, а в OSM и старых краулах оно живёт ещё годами. По нашим прикидкам, около 7% записей в открытом слое на момент сборки были такими мертвецами.

Главная сложность в том, что закрытие почти никто не фиксирует явно. Поэтому мы собираем сигналы:

  • запись пропала из свежего фида или краула, хотя раньше была;
  • у источника стоит явный флаг «закрыто навсегда»;
  • нет свежей активности — отзывов, обновлений;
  • и самый сильный сигнал: трафик по SDK-панелям упал до нуля. Если в точку месяцами никто не заходит, она мертва, даже если формально числится открытой.

Та самая «Кофехауз» на Большой Бронной, которая закрылась в 2019-м, отлетает именно так: её нет в свежих источниках, и людей там нет. Ежедневное обновление гоняет эти проверки заново, так что база не накапливает кладбище.

Разнобой в категориях: одна онтология поверх чужих таксономий

Последняя крупная поломка — категории. Кофейня в OSM размечена тегом amenity=cafe, в партнёрском фиде — рубрикой «Кафе / Кофейни», в крауле — словом «общепит». Три источника, три классификатора, ноль совместимости.

Мы строим собственную онтологию категорий и поверх неё — слой соответствий, который переводит таксономию каждого источника в наш канон. Тогда агент, который спрашивает «кофейни в радиусе 500 м», получает их независимо от того, как изначально была размечена точка.

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

Что получилось и где мы всё ещё ошибаемся

В цифрах путь выглядит так:

2,6 млн
сырых записей
на входе
870K
дублей
схлопнуто
1 732 054
чистых объекта
на выходе
95%+
записей с
пригодным адресом

Доля записей с пригодным адресом выросла примерно с двух третей до уверенных 95% с лишним. Около 7% призраков убрали. Пайплайн пересобирается ежедневно, причём матчинг и блокинг гоняются по дельтам, а не по всей базе с нуля.

Честно про слабые места, без которых картинка была бы рекламной:

  • Только что открывшиеся точки отстают. Краулинг находит их раньше, чем подтверждает партнёрский фид, поэтому у свежих записей мы держим пониженную уверенность, а не выдаём их как проверенный факт.
  • Бизнес-центры всё ещё иногда дают коллизии адресов, когда несколько организаций делят одну точку и скупой набор полей.
  • Редкие ложные слияния случаются у сетей с одинаковым именем впритык друг к другу — две «Шоколадницы» в одном ТЦ матчер может принять за одну. Такие случаи уходят на ручную проверку.
  • Транслитерация спотыкается на нестандартных написаниях брендов, где латиница и кириллица смешаны нарочно.

Поэтому у каждой записи есть флаг уверенности и признак верификации. Мы не делаем вид, что данные идеальны, — мы отдаём агенту честную оценку качества, чтобы он сам решал, насколько на неё опираться.

Датасет // распределение
1,73 млн POI по Москве: по категориям
1 732 054 POI
Ритейл и магазины 440 000
Бытовые услуги 320 000
Кафе, бары и рестораны 250 000
Здоровье и аптеки 160 000
Прочее 145 000
Транспорт и АЗС 125 000
Финансы и банки 100 000
Образование 75 000
Спорт и фитнес 60 000
Культура и досуг 55 000

Так выглядит датасет после дедупликации и конфляции, сведённый к единой онтологии категорий. Сумма по категориям — ≈ 1,73 млн; точный счёт после очистки — 1 732 054 POI.

FAQЧастые вопросы

Нормализация геоданных — это приведение записей о городских объектах из разных источников к единому виду: один формат адреса, одна система категорий, одни координаты и одна запись на каждый реальный объект. Без неё данные из разных источников противоречат друг другу и не годятся для автоматической обработки.
Дедупликация POI решает, какие записи относятся к одному и тому же объекту. Конфляция данных делает следующий шаг: сливает эти записи в одну золотую, выбирая правильное значение для каждого поля и запоминая, из какого источника оно взято.
Сначала блокинг по geohash оставляет только пары, которые физически рядом. Потом матчер считает взвешенную оценку по нескольким сигналам: похожесть нормализованных названий, расстояние между координатами, совместимость категорий, совпадение телефона и адреса. Если оценка выше порога, записи считаются одним объектом.
Не сливаем по одному только расстоянию. У объектов с почти одинаковыми координатами должны заметно расходиться имя и категория, чтобы остаться раздельными записями. Иначе матчер уничтожил бы реальные бизнесы, которые делят один адрес.
Сырые проприетарные данные мы не перераспространяем. Базовый слой — собственный сбор (SDK-панели, спутник, краулинг) плюс открытые данные под лицензией ODbL, которые мы используем как Produced Works, не распространяя производную базу; атрибуция OpenStreetMap соблюдается. Лицензированные партнёрские фиды (например 2ГИС) идут по отдельному коммерческому соглашению. Государственные данные — через официальные каналы Росреестра, с обезличиванием персональных данных в соответствии с 152-ФЗ.
Ежедневно. На каждом прогоне заново проверяются закрытые точки, а матчинг и блокинг пересчитываются по изменившимся данным, а не по всей базе целиком.
// Дальше читать

Из команды Омни Карт.

// Начать

Дайте агенту 1,73 млн чистых POI по Москве.

Запрос в реальном времени через API и MCP или скачивание датасета. Чистого, без дублей.

POIПЕШИЙ ТРАФИКАВТО-ТРАФИКНЕДВИЖИМОСТЬДЕМОГРАФИЯ