1,73 млн POI по Москве: чему нас научила нормализация и дедупликация городских данных
Мы собрали данные о городе из пяти источников. Наивное объединение дало почти 2,6 млн записей с дублями и противоречиями. Рассказываем, как из этого получилось 1 732 054 чистых объекта.
Нормализация геоданных — это приведение записей о городских объектах из разных источников к единому виду: один формат адреса, одна категория, одни координаты и одна запись на каждый реальный бизнес. Мы собирали данные о Москве из пяти источников, и наивное объединение дало почти 2,6 млн записей: с дублями, противоречивыми адресами, давно закрытыми точками и категориями из несовместимых классификаторов. После дедупликации POI и конфляции данных осталось 1 732 054 объекта, у каждого 28+ атрибутов.
Дальше — как мы туда пришли и где было особенно больно.
Откуда берутся данные о городе и почему их нельзя просто склеить
У нас пять источников, и каждый врёт по-своему.
Открытые данные (OpenStreetMap) дают широкое покрытие и бесплатны, но качество скачет от района к району: в центре подробно, на окраинах дырки. Координаты часто есть, а нормального адреса нет. Плюс OSM помнит город таким, каким его когда-то занесли волонтёры, и не всегда замечает, что заведение закрылось.
Партнёрские фиды (например 2ГИС, по коммерческому соглашению) — наоборот, структурированы и свежи, с телефонами и часами работы. Но у них своя рубрикация, которая не совпадает ни с OSM, ни с нашей.
Собственный краулинг приносит то, чего ещё нет в фидах: только что открывшиеся точки, временные изменения. Цена за свежесть — шум: опечатки, дубли, юридические названия вместо вывесок.
Мобильные SDK-панели — это не список POI, а косвенный признак того, что место вообще живёт. Анонимизированные геоданные показывают, ходят ли вообще люди в это место (модель, похожая на Placer.ai и Foursquare на западном рынке). Для самой записи это не имя и не адрес, но отличный способ понять, жива точка или это призрак.
Спутниковые снимки помогают там, где источники спорят о координатах: где физически стоит здание, где въезд, на месте ли вообще постройка. Росреестр через официальные каналы даёт владение и историю переходов права — это уже про коммерческую недвижимость, а не про вывеску кафе.
Теперь представьте, что вы просто свалили всё это в одну таблицу. Одна и та же «Кофемания» приедет к вам три раза в трёх написаниях, её адрес будет в четырёх форматах (включая пустой), категория — в трёх классификаторах, а рядом будет лежать кофейня, которая закрылась в 2019-м. Склеить пять источников — это не UNION ALL. Это отдельная инженерная задача, и вот её карта.
Каждый источник проходит нормализацию полей, затем блокинг и матчинг; совпавшие записи сливаются в одну золотую с историей происхождения каждого поля. На входе — ≈ 2,6 млн сырых записей, на выходе — 1 732 054 объекта.
Дедупликация POI: один бизнес и пять записей о нём
Самая частая поломка при объединении источников — дубли. Один и тот же объект приходит из разных мест и притворяется разными объектами.
Канонический пример из нашей базы — кофейня на Малой Бронной. В OSM она лежит как «кафе Кофемания», в партнёрском фиде — как «Кофемания», в краулинге — как «КОФЕМАНИЯ (ООО „КМ“)». Одно место, три записи. Умножьте на 2,6 миллиона входных строк, и масштаб проблемы становится понятен.
Почему «каждый с каждым» не работает
Очевидное решение — сравнить каждую запись с каждой и поискать совпадения. На 2,6 млн записей это около трёх триллионов пар. Даже если одно сравнение занимает микросекунды, вы будете ждать неделями. Так не пойдёт.
Поэтому сначала мы режем пространство кандидатов через блокинг. Идея простая: два объекта могут быть одним бизнесом, только если они физически рядом. Кофейня на Малой Бронной не дубль кофейни в Бутово. Мы накрываем город сеткой на основе geohash (способ закодировать координаты в короткую строку так, что у соседних точек совпадает начало кода) и сравниваем записи только внутри одной ячейки и её соседей. Это снижает число пар-кандидатов с порядка 1012 до примерно 107, то есть в сотни тысяч раз. Дальше уже можно считать честно.
Как мы решаем, что две записи — это один объект
Внутри ячейки мы для каждой пары считаем оценку похожести по нескольким сигналам сразу:
- близость названий, но не буквальная, а с нормализацией: приводим к нижнему регистру, выкидываем юридические формы (ООО, ИП, ЗАО), снимаем категорию, прилипшую к имени («кафе Кофемания» → «кофемания»), сводим
ёие, разбираем транслитерацию (Coffeemania и Кофемания — одно и то же) и ловим омоглифы, когда латинскаяaпритворяется кириллической; - расстояние между координатами (порог в десятки метров для одной категории);
- совместимость категорий;
- совпадение телефона и нормализованного адреса.
Сигналы складываются во взвешенную оценку, и если она выше порога — это один объект.
Но есть ловушка в обратную сторону, и она опаснее. Если матчить только по расстоянию, начинается переслияние. В городе полно точек внутри точек: кофейня в книжном магазине, банкомат в лобби банка, островок с маникюром в торговом центре. Координаты у них почти совпадают, но это разные бизнесы. Сольёшь их по дистанции — и уничтожишь реальные POI.
Поэтому близость нужна, но её недостаточно: чтобы оставить записи раздельными, у них должны сильно расходиться имя и категория. И наоборот — сетевые точки требуют аккуратности. «Аптека 36,6» — это сотни реальных филиалов по городу. Внутри одного источника их нельзя схлопывать в одну запись, а между источниками нужно слить дубль каждого конкретного филиала. Грань тонкая, и большая часть работы матчера — именно про неё.
Три кандидата из разных источников и одна закрытая точка, которую матчер отклонил, превращаются в одну запись с понятным происхождением каждого поля — всего 28+ атрибутов.
Очистка адресных данных: «Малая Бронная, 22 стр. 2» в пяти вариантах
Если дедупликация — это про то, какие записи об одном объекте, то очистка адресных данных — про то, как вообще понять, где этот объект стоит. И с русскими адресами это отдельный жанр боли.
Один и тот же адрес приезжает к нам так: «ул. Малая Бронная, 22», «Малая Бронная, д.22, стр.2», «М. Бронная 22с2» и, классика, пустой строкой. Сокращения живут в зоопарке: ул. и улица, д. и дом, стр. (строение), корп. (корпус), литеры. Номера домов теряются: примерно у трети записей из открытого слоя нормального номера дома просто нет.
Мы разбираем адрес на структурные части и сводим к канону через государственный адресный реестр (ГАР, бывший ФИАС), а затем геокодируем в эталонные координаты. После этого «22 стр. 2» в любом из пяти написаний — это одна и та же точка на карте.
Отдельный случай — бизнес-центры. Одно здание, один адрес, а внутри десятки организаций на разных этажах. Адрес тут перестаёт различать объекты, и приходится опираться на координаты, имя и, где есть, этаж. Адрес — необходимое поле, но не идентификатор.
Конфляция данных: как из пяти записей собрать одну золотую
Когда матчер решил, что несколько записей — это один объект, их надо слить. Это и есть конфляция данных, и её часто путают с дедупликацией. Разница простая: дедупликация отвечает на вопрос «это один и тот же бизнес?», а конфляция — «какое значение каждого поля в итоговой записи правильное?».
Наивно взять любую из записей и выкинуть остальные. Но у разных источников разные сильные стороны, поэтому мы собираем золотую запись по полям, и для каждого поля у источника свой приоритет доверия:
- координаты — со спутника и собственной съёмки, они точнее всего;
- часы работы и телефон — из партнёрского фида, он свежий и структурированный;
- название — по консенсусу источников, с предпочтением вывески, а не юрлица;
- владение — из Росреестра;
- категория — из нашего сведённого классификатора.
При этом мы храним провенанс: какое поле от какого источника пришло. Это нужно, чтобы потом можно было проверить запись и откатить ошибку, если источник наврал. На выходе получается одна запись с 28 с лишним атрибутами, и про каждый известно, откуда он взялся.
Закрывшиеся точки: как поймать то, чего больше нет
Призраки — это тихая беда геоданных. Заведение закрылось, а в OSM и старых краулах оно живёт ещё годами. По нашим прикидкам, около 7% записей в открытом слое на момент сборки были такими мертвецами.
Главная сложность в том, что закрытие почти никто не фиксирует явно. Поэтому мы собираем сигналы:
- запись пропала из свежего фида или краула, хотя раньше была;
- у источника стоит явный флаг «закрыто навсегда»;
- нет свежей активности — отзывов, обновлений;
- и самый сильный сигнал: трафик по SDK-панелям упал до нуля. Если в точку месяцами никто не заходит, она мертва, даже если формально числится открытой.
Та самая «Кофехауз» на Большой Бронной, которая закрылась в 2019-м, отлетает именно так: её нет в свежих источниках, и людей там нет. Ежедневное обновление гоняет эти проверки заново, так что база не накапливает кладбище.
Разнобой в категориях: одна онтология поверх чужих таксономий
Последняя крупная поломка — категории. Кофейня в OSM размечена тегом amenity=cafe, в партнёрском фиде — рубрикой «Кафе / Кофейни», в крауле — словом «общепит». Три источника, три классификатора, ноль совместимости.
Мы строим собственную онтологию категорий и поверх неё — слой соответствий, который переводит таксономию каждого источника в наш канон. Тогда агент, который спрашивает «кофейни в радиусе 500 м», получает их независимо от того, как изначально была размечена точка.
Сложнее всего на стыках. Заведение бывает одновременно баром и рестораном. Кофейня, которая продаёт зёрна навынос, — это и общепит, и ритейл. Поэтому у записи есть основная категория и дополнительные метки, а не одна жёсткая ячейка.
Что получилось и где мы всё ещё ошибаемся
В цифрах путь выглядит так:
на входе
схлопнуто
на выходе
пригодным адресом
Доля записей с пригодным адресом выросла примерно с двух третей до уверенных 95% с лишним. Около 7% призраков убрали. Пайплайн пересобирается ежедневно, причём матчинг и блокинг гоняются по дельтам, а не по всей базе с нуля.
Честно про слабые места, без которых картинка была бы рекламной:
- Только что открывшиеся точки отстают. Краулинг находит их раньше, чем подтверждает партнёрский фид, поэтому у свежих записей мы держим пониженную уверенность, а не выдаём их как проверенный факт.
- Бизнес-центры всё ещё иногда дают коллизии адресов, когда несколько организаций делят одну точку и скупой набор полей.
- Редкие ложные слияния случаются у сетей с одинаковым именем впритык друг к другу — две «Шоколадницы» в одном ТЦ матчер может принять за одну. Такие случаи уходят на ручную проверку.
- Транслитерация спотыкается на нестандартных написаниях брендов, где латиница и кириллица смешаны нарочно.
Поэтому у каждой записи есть флаг уверенности и признак верификации. Мы не делаем вид, что данные идеальны, — мы отдаём агенту честную оценку качества, чтобы он сам решал, насколько на неё опираться.
Так выглядит датасет после дедупликации и конфляции, сведённый к единой онтологии категорий. Сумма по категориям — ≈ 1,73 млн; точный счёт после очистки — 1 732 054 POI.