КАК СКАЧАТЬ СПИСОК АДРЕСОВ ИЗ ФИАС

Да, это полная официальная адресная база России, просто в открытом доступе, никто ничего не спрашивает, просто раздают. Сделали на наши налоги, и честно всем, как скамейку в парке, отдают в пользование. Прекрасно? Да!

«В чем же подвох?», — спросите вы, прищурившись.

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

Введение

Год назад ФНС прекратила выкладывать базу ФИАС в формате DBF, теперь база доступна только в формате XML. Данные из DBF можно было вставлять в базу как есть, с XML так не получается, перед вставкой в базу, XML надо парсить, и это занимает время. Полная база ФИАС занимает 290 GB, парсинг такого объёма занимает значительное
время.

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

Я разработал несколько скриптов для создания и импорта базы. Эта статья будет о том как с помощью этих скриптов развернуть базу ФИАС за 9 часов на 6-ти ядерном процессоре.

Про ГИС ЖКХ:  Что за учреждение находится по этому адресу

Что это за база и зачем ее парсить

Это полностью открытая и бесплатная база всех адресов нашей страны от ФНС России
.

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

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

Мы используем ФИАС у себя в проде больше двух лет. Некоторое время назад ФИАС полностью перешел на формат ГАР, о чем они ранее предупреждали, и мы решили освежить свои скрипты, а заодно и прочувствовать по новой «красоту и единообразие» нашей адресной системы. Надо сказать, что она стала капельку лучше (но легче от этого стало незначительно). Так же мы изменили подход к сбору адресных цепочек и решили поделиться своим безудержным весельем со всеми желающими.

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


КАК СКАЧАТЬ СПИСОК АДРЕСОВ ИЗ ФИАС


КАК СКАЧАТЬ СПИСОК АДРЕСОВ ИЗ ФИАС



Федеральная информационная

адресная система

Федеральный закон от 28.12.2013 №443-ФЗ «О федеральной информационной адресной системе и о внесении изменений в Федеральный закон «Об общих принципах организации местного самоуправления в Российской Федерации» (далее – 443-ФЗ), который вступил в силу 1 июля 2014 года устанавливает:

1. Обязанность ОМСУ присваивать в соответствии с Правилами присвоения, изменения и аннулирования адресов (Постановление Правительства Российской Федерации от 19.11.2014 №1221 (далее – Правила), и размещать адресную информацию в государственном адресном реестре (далее — ГАР), который формируется в федеральной информационной адресной системе (ч. 3 ст. 5 443-ФЗ);

2. Обязанность органов государственной власти любого уровня использовать адресную информацию из государственного адресного реестра при оказании государственных и муниципальных услуг (ст. 9 443-ФЗ)

Правилами установлена единая структура адреса на основе территориальных принципов организации местного самоуправления, которая включает в себя следующую иерархическую последовательность адресообразующих элементов:

1. Наименование страны (Российская Федерация);

2. Наименование субъекта Российской Федерации;

3. Наименование муниципального района, городского округа или внутригородской территории (для городов федерального значения) в составе субъекта Российской Федерации;

4. Наименование городского или сельского поселения в составе муниципального района (для муниципального района) или внутригородского района городского округа;

5. Наименование населенного пункта;

6. Наименование элемента планировочной структуры;

7. Наименование элемента улично-дорожной сети;

8. Номер земельного участка;

9. Тип и номер здания, сооружения или объекта незавершенного строительства;

10. Тип и номер помещения, расположенного в здании или сооружении.

В целях реализации ст.9 443-ФЗ установлены форматы выгрузки адресных сведений из ФИАС, которые еженедельно обновляются на портале Федеральной Информационной Адресной Системы в объеме полной выгрузки ГАР и изменений сведений за период в формате XML и DBF.

Вместе с тем, до вступления в силу 443-ФЗ (в частности установлены требования к структуре адреса) использовались форматы ведомственного классификатора адресов (далее – КЛАДР), которые в настоящее время не отражают актуальные адресные сведения, содержащиеся в государственном адресном реестре, имеющие четкую утвержденную действующим законодательством структуру адреса.

В силу того, что сведения из адресного реестра имеют широкое использование государственными и коммерческими органами и организациями Оператор ФИАС продолжает размещение адресных сведений на портале Федеральной Информационной Адресной Системы и в формате КЛАР 4.0.

В целях унификации адресных сведений, содержащихся в различных информационных ресурсах, с учетом предоставления переходного периода для модификации соответствующих информационных систем ФНС России сообщает:

выгрузка адресных сведений в формате КЛАДР 4.0 будет осуществляться до 31.12.2017г. После 31.12.2017г сведения из ГАР будут выгружаться только в формате ФИАС.

И что дальше

Публичные бесплатные данные — это прекрасно. И понятно, почему ФИАС такой всратый — потому что легаси, потому что унификация большой системы — это сложно, потому что всегда в подчинении оказываются немотивированные элементы, которые на все годные движухи хотели класть болта, потому что верхнее звено в субординации не откликается на зов подчиненных, и т.д. и т.п. Тем не менее, база становится все лучше, и однажды она наверняка станет замечательной, и может, координаты туда завезут из коробки, главное — пользуйтесь сами. Правильно ли кормить тех, кто протер скамейку в парке, првязал к ней бантик и просит теперь 10 рублей в час за нее? Так, глядишь, продадут народную скамейку, как Союзмультфильм продали.

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

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

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

  1. Установка зависимостей. Ну там все просто, может, придется рестартнуть райнтайм, если попросят.
  2. Монтируем гугл диск, клонируем репку, распаковываем данные из архива.
  3. Импортируем зависимости.
  4. Определяем утилиты. Это просто удобно — иметь все вспомогательные функции в одном месте. У них есть краткие описания. Например, все xml-файлы в ФИАС имеют похожую структуру, и конвертировать их в датафрейм можно одной из утилит.
  5. Основной раздел, работа на примере конкретного региона.
    1. Определяем регион.
    2. Читаем файл с адресными объектами. Попутно читаем и джойним все дополняющие файлы (типы и уровни объектов, параметры).
    3. Читаем файл со зданиями, в принципе, процесс особо не отличается, добавляются колонки для всяких корпусов и строений. Конкатенируем здания и адресные объекты в один датафрейм.
    4. Читаем файл с административным подчинением. Запоминаем, насколько нам повезло: есть ли колонка с готовыми адресными цепочками.
    5. Если повезло, парсим готовые цепочки. А если нет, то рекурсивно их строим сами. Основная магия в утилите get_adms_rec_rev
      . Чистим цепочки, если один объект попадает в несколько штук сразу. Создаем финальный фрейм с цепочками. Структурируем виды цепочек, джойним с финальным фреймом.
    6. Сохраняем это все.
  6. Несколько независимая часть: после успешного прогона ячеек выше, рантайм можно перезапустить, и работать в данном разделе. Читаем наши результаты, преобразовываем адреса в плейнтекст и структурированный форматы, смотрим на то, что получилось.

Надеюсь, кому-то это окажется полезным. Ну а что делать с этим дальше, решать уже вам.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите
, пожалуйста.

Знали ли вы про ФИАС?

Знали, НЕ пользовались

В Интернете никто не знает, что я кот

Проголосовали 217 пользователей.

Воздержались 18 пользователей.

Обновление базы

Обновление происходит аналогичным образом. В конфиге (config.env), в XML_FILES_PATH
записываем путь к файлам обновления, меняем значение DO_IMPORT_WITH_CHECK
на TRUE
, запускаем скрипт, ждём, пользуемся.

Альтернативы

Ну есть OSM, бесплатный, да, не для всей России, не всегда актуален, держится на усилиях очень идейных людей, судя по всему. Названия там зачастую отличаются от официальных и порой встречаются сразу несколько вариантов одного и того же топонима. Поэтому несколько годен для сбора синонимов и разговорных названий. Выхлоп небольшой, но есть. По количеству усилий на очистку и слияние с официальной базой работать с ним куда более неприятно, чем с ФИАС. Но работать можно, и жаловаться грешно.

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

Остальные базы платные, предоставляют доступ к API и обязательно ставят заградительные цены на всю базу (что абсолютно логично, кому нужны конкуренты?), либо прямо запрещают ее хранить и собирать. Там бывают бесплатные лимиты (например, до 10 000 запросов в сутки), или они требуют указывать, откуда вы получили данные, в своем приложении. Перечислять их смысла нет.

How To

Настало время рассказать в подробностях.

Ставим пакеты

 composer require sbwerewolf/fias-gar-schema-deploy-tool
composer require sbwerewolf/fias-gar-data-import-tool 

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

 composer require monolog/monolog 

Логи можно вообще не писать, тогда в скрипте в качестве логера надо использовать \Psr\Log\NullLogger
.

Копируем скрипты и SQL шаблоны создания таблиц

 cp ./vendor/sbwerewolf/fias-gar-data-import-tool/test/* ./
cp ./vendor/sbwerewolf/fias-gar-schema-deploy-tool/test/* ./
cp ./config.env.example ./config.env 

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

Файл «config.env.example» нужен из исходников «fias-gar-data-import-tool».

Проверьте, что директории скопировались вместе с файлами, в директории templates должно быть 28 директорий, и в каждой директории 2-4 файла.

Правим конфиг

Открываем config.env
, правим настройки.

LOGIN
PASSWORD
DSN
меняем под СУБД, в выбранной БД создаём схему для ФИАС, название схемы пишем в SCHEMA
. У меня СУБД — Постгрес 14.

СУБД должна поддерживать секционирование. Может быть MySQL тоже умеет, но я не уверен за правильность синтаксиса. Синтаксис придётся править в шаблонах, для каждой из 28-ми таблиц.

В моих скриптах не используется ORM, поэтому извините, под вашу СУБД придётся поработать руками.

DO_IMPORT_WITH_CHECK
присваиваем FALSE
— чтобы перед вставкой не проверять существование записи, это будет лишний SELECT.
При импорте значение FALSE
, при обновлении базы — TRUE
.

XML_FILES_PATH
записываем путь к развёрнутому архиву ФИАС.

BATCH_SIZE
это количество записей до выполнения COMMIT
, устанавливаем на свой вкус, по умолчанию 100 000. Я ставил 1 000 000, в этом случае, пауза между камитами просто увеличилась в 10 раз, ускорения не заметил.

Настраиваем скрипты

install-storage.php

Скрипт создания таблиц.

Настраиваем журналирование

Ниже создаётся логгер, запись будет и в файл и в консоль.

 $logger = new Logger('common');
$pathComposer = new Path(__DIR__);
$logsPath = $pathComposer->make( [ 'logs', pathinfo(__FILE__, PATHINFO_FILENAME) . '-' . time() . '.log', ]
);
$writeHandler = new StreamHandler($logsPath);
$logger->pushHandler($writeHandler);
$logger->pushProcessor(function ($record) { /** @var LogRecord $record */ echo "{$record->datetime} {$record->message}" . PHP_EOL; return $record;
}); 

Логи можно вообще не писать, тогда в скрипте в качестве логера надо использовать \Psr\Log\NullLogger
.

Настройку логгера надо повторить в каждом скрипте (три скрипта — три раза).

Задаём путь к шаблонам SQL скриптов создания таблиц, директории перечисляем как элементы массива.

 $templatesPath = $pathComposer->make(['template']); 

Корневая директория задаётся при создании экземпляра $pathComposer = new Path(__DIR__);

Задаём список таблиц (имена папок с шаблонами скриптов для конкретной таблицы)

 $templatesKitList = [ 'ADDHOUSETYPES', 'ADDR_OBJ_DIVISION', 'ADDR_OBJ_PARAMS', 'ADDRESSOBJECTS', 'ADDRESSOBJECTTYPES', 'ADM_HIERARCHY', 'APARTMENTS', 'APARTMENTS_PARAMS', 'APARTMENTTYPES', 'CARPLACES', 'CARPLACES_PARAMS', 'CHANGE_HISTORY', 'HOUSES', 'HOUSES_PARAMS', 'HOUSETYPES', 'MUN_HIERARCHY', 'NDOCKINDS', 'NDOCTYPES', 'NORMDOCS', 'OBJECTLEVELS', 'OPERATIONTYPES', 'PARAMTYPES', 'REESTR_OBJECTS', 'ROOMS', 'ROOMS_PARAMS', 'ROOMTYPES', 'STEADS', 'STEADS_PARAMS',
];
$command->run($templatesKitList, 'create-table.php') 

В зависимости от того как вы собираетесь использовать базу, можно удалить создание каких то таблиц, например «*_PARAMS», «NORMDOCS», «CARPLACES», «ROOMS», «STEADS».

Задаём таблицы для которых нужно сделать секционирование по региону (таблицы с колонкой REGION
), файлы этих таблиц лежат в директории региона.

 $templatesKitList = [ 'ADDR_OBJ_DIVISION', 'ADDR_OBJ_PARAMS', 'ADDRESSOBJECTS', 'ADM_HIERARCHY', 'APARTMENTS', 'APARTMENTS_PARAMS', 'CARPLACES', 'CARPLACES_PARAMS', 'CHANGE_HISTORY', 'HOUSES', 'HOUSES_PARAMS', 'MUN_HIERARCHY', 'NORMDOCS', 'REESTR_OBJECTS', 'ROOMS', 'ROOMS_PARAMS', 'STEADS', 'STEADS_PARAMS',
];
$command->run( $templatesKitList, 'create-partition.php', false, 99, 1
); 

Соответственно, удаляем таблицы которые были удалены из первого списка.

data-import.php

Скрипт для импорта XML файлов в БД.

Делаем пять копий (для параллельной обработки в пять потоков).

 cp ./data-import.php ./data-import-1.php
cp ./data-import.php ./data-import-2.php
cp ./data-import.php ./data-import-3.php
cp ./data-import.php ./data-import-4.php
cp ./data-import.php ./data-import-5.php 

В каждой копии скрипта импорта, настраиваем параметры выполнения команды импорта (класс $options = new ImportOptions()
).

 $options = new ImportOptions( $doAddNewWithCheck, [ AddHouseTypes::class => 'AS_ADDHOUSE_TYPES_20*.{x,X}{m,M}{l,L}', AddressObjectTypes::class => 'AS_ADDR_OBJ_TYPES_20*.{x,X}{m,M}{l,L}', ApartmentTypes::class => 'AS_APARTMENT_TYPES_20*.{x,X}{m,M}{l,L}', HouseTypes::class => 'AS_HOUSE_TYPES_20*.{x,X}{m,M}{l,L}', NormativeDocumentsKinds::class => 'AS_NORMATIVE_DOCS_KINDS_20*.{x,X}{m,M}{l,L}', NormativeDocumentsTypes::class => 'AS_NORMATIVE_DOCS_TYPES_20*.{x,X}{m,M}{l,L}', ObjectLevels::class => 'AS_OBJECT_LEVELS_20*.{x,X}{m,M}{l,L}', OperationTypes::class => 'AS_OPERATION_TYPES_20*.{x,X}{m,M}{l,L}', ParamTypes::class => 'AS_PARAM_TYPES_20*.{x,X}{m,M}{l,L}', RoomTypes::class => 'AS_ROOM_TYPES_20*.{x,X}{m,M}{l,L}', ], '{1,2,3,4,5,6,7,8,9,0}{1,2,3,4,5,6,7,8,9,0}', [ AddressObjects::class => 'AS_ADDR_OBJ_20*.{x,X}{m,M}{l,L}', AddressObjectDivision::class => 'AS_ADDR_OBJ_DIVISION_20*.{x,X}{m,M}{l,L}', AddressObjectParams::class => 'AS_ADDR_OBJ_PARAMS_20*.{x,X}{m,M}{l,L}', AdministrativeHierarchy::class => 'AS_ADM_HIERARCHY_20*.{x,X}{m,M}{l,L}', Apartments::class => 'AS_APARTMENTS_20*.{x,X}{m,M}{l,L}', ApartmentsParams::class => 'AS_APARTMENTS_PARAMS_20*.{x,X}{m,M}{l,L}', CarPlaces::class => 'AS_CARPLACES_20*.{x,X}{m,M}{l,L}', CarPlacesParams::class => 'AS_CARPLACES_PARAMS_20*.{x,X}{m,M}{l,L}', ChangeHistory::class => 'AS_CHANGE_HISTORY_20*.{x,X}{m,M}{l,L}', Houses::class => 'AS_HOUSES_20*.{x,X}{m,M}{l,L}', HousesParams::class => 'AS_HOUSES_PARAMS_20*.{x,X}{m,M}{l,L}', MunicipalHierarchy::class => 'AS_MUN_HIERARCHY_20*.{x,X}{m,M}{l,L}', NormativeDocuments::class => 'AS_NORMATIVE_DOCS_20*.{x,X}{m,M}{l,L}', ReestrObjects::class => 'AS_REESTR_OBJECTS_20*.{x,X}{m,M}{l,L}', Rooms::class => 'AS_ROOMS_20*.{x,X}{m,M}{l,L}', RoomsParams::class => 'AS_ROOMS_PARAMS_20*.{x,X}{m,M}{l,L}', Steads::class => 'AS_STEADS_20*.{x,X}{m,M}{l,L}', SteadsParams::class => 'AS_STEADS_PARAMS_20*.{x,X}{m,M}{l,L}', ],
); 

Первый аргумент (логическая переменная) отвечает за выполнение проверки существования записи перед её добавлением. В скрипте значение этого аргумента задаётся переменной окружения DO_IMPORT_WITH_CHECK
.

 $doAddNewWithCheck, 

Соответственно при разворачивании базы нам эта проверка не нужна, передаём значение false
. При обновлении базы, значение должно быть true
.

Второй аргумент (массив) это справочники. Справочники общие на все регионы, для них не надо создавать партиции, они импортируются один раз. Импорт справочников много времени не занимает, но при желании можно убрать из списка справочники которые вам не нужны.

  [ AddHouseTypes::class => 'AS_ADDHOUSE_TYPES_20*.{x,X}{m,M}{l,L}', AddressObjectTypes::class => 'AS_ADDR_OBJ_TYPES_20*.{x,X}{m,M}{l,L}', ApartmentTypes::class => 'AS_APARTMENT_TYPES_20*.{x,X}{m,M}{l,L}', HouseTypes::class => 'AS_HOUSE_TYPES_20*.{x,X}{m,M}{l,L}', NormativeDocumentsKinds::class => 'AS_NORMATIVE_DOCS_KINDS_20*.{x,X}{m,M}{l,L}', NormativeDocumentsTypes::class => 'AS_NORMATIVE_DOCS_TYPES_20*.{x,X}{m,M}{l,L}', ObjectLevels::class => 'AS_OBJECT_LEVELS_20*.{x,X}{m,M}{l,L}', OperationTypes::class => 'AS_OPERATION_TYPES_20*.{x,X}{m,M}{l,L}', ParamTypes::class => 'AS_PARAM_TYPES_20*.{x,X}{m,M}{l,L}', RoomTypes::class => 'AS_ROOM_TYPES_20*.{x,X}{m,M}{l,L}', ], 

Таблицы справочников надо импортировать только один раз, их импортировать должен только один скрипт, в других скриптах этот список должен быть пустым.

Третий аргумент (строка) определяет маску для выбора директорий с регионами. Соответственно делаем копии этого скрипта по количеству потоков обработки.

 '{1,2,3,4,5,6,7,8,9,0}{1,2,3,4,5,6,7,8,9,0}', 

У меня было пять копий, с такими значениями:

 '{01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16,17,18,19,20,21,22}', 
 '{23,24,25,26,27,28,29,30,31,32,33,34,35,36}', 
 '{37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52}', 
 '{53,54,55,56,57,58,59,60,61,62,63,64,65,66,67}', 
 '{99,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95}', 

Четвёртый аргумент (массив), это список таблиц для каждого региона, лишние можно удалить, для каждого региона в этих таблицах будут созданы партиции.

  [ AddressObjects::class => 'AS_ADDR_OBJ_20*.{x,X}{m,M}{l,L}', AddressObjectDivision::class => 'AS_ADDR_OBJ_DIVISION_20*.{x,X}{m,M}{l,L}', AddressObjectParams::class => 'AS_ADDR_OBJ_PARAMS_20*.{x,X}{m,M}{l,L}', AdministrativeHierarchy::class => 'AS_ADM_HIERARCHY_20*.{x,X}{m,M}{l,L}', Apartments::class => 'AS_APARTMENTS_20*.{x,X}{m,M}{l,L}', ApartmentsParams::class => 'AS_APARTMENTS_PARAMS_20*.{x,X}{m,M}{l,L}', CarPlaces::class => 'AS_CARPLACES_20*.{x,X}{m,M}{l,L}', CarPlacesParams::class => 'AS_CARPLACES_PARAMS_20*.{x,X}{m,M}{l,L}', ChangeHistory::class => 'AS_CHANGE_HISTORY_20*.{x,X}{m,M}{l,L}', Houses::class => 'AS_HOUSES_20*.{x,X}{m,M}{l,L}', HousesParams::class => 'AS_HOUSES_PARAMS_20*.{x,X}{m,M}{l,L}', MunicipalHierarchy::class => 'AS_MUN_HIERARCHY_20*.{x,X}{m,M}{l,L}', NormativeDocuments::class => 'AS_NORMATIVE_DOCS_20*.{x,X}{m,M}{l,L}', ReestrObjects::class => 'AS_REESTR_OBJECTS_20*.{x,X}{m,M}{l,L}', Rooms::class => 'AS_ROOMS_20*.{x,X}{m,M}{l,L}', RoomsParams::class => 'AS_ROOMS_PARAMS_20*.{x,X}{m,M}{l,L}', Steads::class => 'AS_STEADS_20*.{x,X}{m,M}{l,L}', SteadsParams::class => 'AS_STEADS_PARAMS_20*.{x,X}{m,M}{l,L}', ], 

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

create-indexes.php

Скрипт создания индексов.

Снова редактируем список таблиц для которых будем создавать индексы.

 $templatesKitList = [ 'ADDHOUSETYPES', 'ADDRESSOBJECTTYPES', 'APARTMENTTYPES', 'HOUSETYPES', 'NDOCKINDS', 'NDOCTYPES', 'OBJECTLEVELS', 'OPERATIONTYPES', 'PARAMTYPES', 'ROOMTYPES', 'ADDR_OBJ_DIVISION', 'ADDR_OBJ_PARAMS', 'ADDRESSOBJECTS', 'ADM_HIERARCHY', 'APARTMENTS', 'APARTMENTS_PARAMS', 'CARPLACES', 'CARPLACES_PARAMS', 'CHANGE_HISTORY', 'HOUSES', 'HOUSES_PARAMS', 'MUN_HIERARCHY', 'NORMDOCS', 'REESTR_OBJECTS', 'ROOMS', 'ROOMS_PARAMS', 'STEADS', 'STEADS_PARAMS',
];
$command->run($templatesKitList, 'create-index.php'); 

Какие индексы будут созданы можно посмотреть и переделать под себя в шаблонах.

Бонус

PgAdmin, загрузка по транзакциям, 5 скриптов импорта
PgAdmin, загрузка по транзакциям, 5 скриптов импорта

Загрузка HDD, 5 скриптов импорта.

Загрузка HDD, 5 скриптов импорта.
Загрузка HDD, 5 скриптов импорта.

Метрики процессов, 5 скриптов импорта.

Метрики процессов, 5 скриптов импорта.
Метрики процессов, 5 скриптов импорта.

Загрузка HDD, 4 скрипта импорта.

Загрузка HDD, 4 скрипта импорта.
Загрузка HDD, 4 скрипта импорта.

Метрики процессов, 3 скрипта импорта.

Метрики процессов, 3 скрипта импорта.
Метрики процессов, 3 скрипта импорта.

Загрузка HDD, 3 скрипта импорта.

Загрузка HDD, 3 скрипта импорта.
Загрузка HDD, 3 скрипта импорта.

Загрузка CPU, 3 скрипта импорта.

Загрузка CPU, 3 скрипта импорта.
Загрузка CPU, 3 скрипта импорта.

Метрики процессов, 2 скрипта импорта.

Метрики процессов, 2 скрипта импорта.
Метрики процессов, 2 скрипта импорта.

Метрики процессов, 1 скрипт импорта.

Метрики процессов, 1 скрипт импорта
Метрики процессов, 1 скрипт импорта

Загрузка CPU, 1 скрипт импорта.

Загрузка CPU, 1 скрипт импорта.
Загрузка CPU, 1 скрипт импорта.

Загрузка HDD, работает скрипт создания индексов в один поток.

Загрузка HDD, работает скрипт создания индексов в один поток
Загрузка HDD, работает скрипт создания индексов в один поток

Учитывая белые провалы, можно запустить создание индексов в два потока, в два скрипта. C PU при этом не сильно нагружен. Что интересно, при создании индексов, сначала идёт чтение на скорости 100 MB/s, потом запись на той же скорости 100 MB/s.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите
, пожалуйста.

Чем вы импортируете и обновляете ФИАС?

сторонний софт хороших людей (напишу об этом в коментах)

собственная inhouse разработка

можно попробовать эти скрипты

мне не нужен ФИАС

Проголосовали 14 пользователей.

Воздержались 4 пользователя.

Что там внутри ФИАС такого страшного

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

Важнее понять, как в целом подходить к ФИАС, и что она из себя представляет. Главный столп нашей адресной системы: Адресная система — это дерево, ветви (адресные цепочки) которого имеют разную длину и состав. Что это означает, объясню на примерах ниже.

Цепочки можно вытянуть из двух файлов, один с административным делением (историческим), другой с муниципальным (актуальным). Судя по моим изысканиям, отличия минимальные и затрагивают верхние уровни подчинения (административные и муниципальные районы).

Поэтому работаем с муниципальным делением. А вообще, чтобы помочь Даше отыскать все 10 различий, можно зайти сюда
, выбрать деление и сравнить поля.

В лучших традициях ФИАС многие объекты «гуляют по уровням», например, всякого рода дачные и садовые территории могут считаться как улицей, так и элементом планировочной структуры, так и населенным пунктом. Все это делает приведение адресных цепочек к стандартному разговорному «населенный пункт, улица, дом» страшным геморроем. Вообще, то, как люди называют адреса в реальности может иметь мало общего с формальностью. Особенные названия микрорайонов, ЖК, топонимы из советского прошлого, — только малая часть того, что сразу приходит в голову.

У нас бывают улицы с одинаковыми названиями в одном городе в разных микрорайонах, бывают деревни и СНТ внутри городов, отсутствуют улицы в деревнях, бывают деревни-тезки в разных муниципальных районах, и прочие радости жизни. К счастью, цепочки от уровня здания теперь можно представить в едином виде с элементами от 10 до 1, где 10 — это дом, а 1 — регион, и подчиняются они именно в таком порядке.

А еще регион может быть населенным пунктом (города федерального значения, которые сами себе регион).

Иногда микрорайон или деревня, входящая в состав города, включается в название улицы в скобках, очень удобно (нет).

В некоторых регионах предусмотрительно выделена колонка PATH
с цепочкой адресов. Если же такой колонки там нет, то может помочь дополнительный ключ ОКТМО
, но ему тоже не на всех уровнях можно верить. В своих функциях я обращаюсь к сверке ОКТМО, когда вверху цепочки вдруг происходит взрыв. Почему так? А черт его знает, почему в Челябинской области весь славный город Челябинск в подчинении у собственных внутригородских районов. При попытке составить цепочки рекурсивно, в Челябинске одни и те же адреса оказываютя во всех районах одновременно. Но в Челябинске рекурсивно собирать цепочки не нужно, ведь есть колонка с цепочками, а вот в Дагестане ее нет. Почему бы не сделать и там отдельную колонку с цепочками? Я не знаю, работаем с тем, что имеем. Проверка ОКТМО не всегда спасает от транзитивных отношений, когда одно и то же здание попадает в несколько цепочек, так как верхние объекты подчиняются по транзитивной схеме. Для такого я просто проверяю, включаются ли более мелкие цепочки в более крупные, и если так, то мелкие цепочки оставляю за бортом.

Если обратиться к цифрам, то примерно вот так представлены адресные цепочки в России:

Чтобы использовать этот зоопарк на практике, мы пытаемся структурировать полученные цепочки таким образом: дом, улица, населенный пункт, остаток (leftover), муниципальный/административный район. Встает вопрос, вот допустим, есть город Мухосранск, в нем есть деревня Кочерыжкино, в деревне — улица Ватрушкина, на улице — дом 42. Что считать населенным пунктом при приведениии цепочки? Город или деревню? Зависит от того, где вы будете это применять и как. Мы обычно деревню определяем в остаток. Остаток может быть пустым. Муниципальный район редко используется, как правило, он нужен для различения одноименных городов и деревень-сателлитов.

Со зданиями (домами) все тоже зажигательно. Для номеров домов в ФИАС есть целых три колонки, видимо, чтобы уместить корпуса, строения, владения и прочие вынужденные отклонения от простоты и красоты. И три колонки для указания типа здания в довесок. И вот колонки есть, а как их заполнять, на местах все решили по-своему. Где-то все уместили в одну колонку вместе с литерами и корпусами. Где-то разнесли. Где-то одну и ту же информацию продублировали дважды или трижды во все колонки. Где-то вместо очевидного типа «Литера» подставлен другой неподходящий тип. Поэтому, собирая адресную строку, не стоит бездумно конкатенировать все три или шесть колонок и считать, что это настоящий номер дома. Придется разобрать все возможные виды извращенства и почистить вилкой.

Про документацию ФИАС надо заметить, что, во-первых, она есть, а во-вторых, она очень плохая. Как говорил мой препод в универе, без полбутылки не разберешься
. Например, встречаются там поля ISACTUAL
и ISACTIVE
. Из документации не то чтобы понятно, в чем их назначение и различие. На практике оказывается, что этим полям не всегда можно верить. Бывало, еще в старом формате ФИАС, что вся Башкирия резко становилась устаревшей и неактивной, поэтому, банальные проверки на количество записей и размеры файлов после апдейтов будут нелишними. Любые предположения (а в случае плохой доки без них никак) в таких случаях нужно сначала попытаться проверить, и только потом применить.

Из менее раздражающих свойств ФИАСа можно заметить, что единые названия одних и тех же сущностей в разных таблицах не всегда практикуются, и в туториале можно наблюдать, как я переименовываю сразу колонки, чтобы не таскать за собой мусор из дублирующихся колонок с разным названием и одинаковым содержанием.

Запускаем скрипты

Сначала создаём таблицы. Перед этим, конечно, включаем OPCache и отключаем дебагер (у меня xDebug), всё это делается в php.ini.

 [opcache]
; Determines if Zend OPCache is enabled
opcache.enable=1
[xDebug]
;zend_extension = xdebug 

Создаём таблицы

 php ./install-storage.php 

Через минуту схема БД будет готова

 Creation tables duration is 00:01:10 

Запускам импорт

Теперь в отдельных окнах терминала запускаем собственно импорт

 php ./data-import1.php 

Аналогичным образом запускаем остальные копии скрипта импорта базы.

Скрипт начнёт работу, в консоли будет написано:

 Script is starting
2023-02-03T05:06:58.107703+05:00 Script is starting
2023-02-03T05:06:58.198173+05:00 Run import FIAS GAR files from `D:\WORK\fias-gar\gar_xml`, with commit each `100 000` operations
2023-02-03T05:06:58.198461+05:00 Import starting
2023-02-03T05:06:58.218593+05:00 Starting common reference import
2023-02-03T05:06:58.218764+05:00 Start import files of pattern AS_ADDHOUSE_TYPES_20*.{x,X}{m,M}{l,L}
2023-02-03T05:06:58.431129+05:00 Starting import file D:\WORK\fias-gar\gar_xml\AS_ADDHOUSE_TYPES_20221222_86d52a97-cf6d-4329-8f55-0723b3feda90.XML
2023-02-03T05:06:58.460446+05:00 Rows read `4`, success Operations `4`, success Inserts `4`, insert Affected `4`, failure Inserts `0`, success Updates `0`, update Affected `0`, failure Updates `0`
2023-02-03T05:06:58.461465+05:00 Finish import files of pattern AS_ADDHOUSE_TYPES_20*.{x,X}{m,M}{l,L} 

Каждые 100 000 ( BATCH_SIZE
) записей будет выводиться статистика:

 2023-02-03T05:08:04.095876+05:00 Rows read `500 000`, success Operations `500000`, success Inserts `500000`, insert Affected `500000`, failure Inserts `0`, success Updates `0`, update Affected `0`, failure Updates `0`
2023-02-03T05:08:04.123922+05:00 Batch duration is 00:00:12 795 ms 521 mcs 500 ns, rows was read is `500 000`, mem allocated is `2`Mb, import processing with `500000` success Operations 

Ждём 8 часов :)) По завершению работы скрипта будет подведена статистика:

 2023-02-03T13:27:01.834772+05:00 Rows read `301 900 000`, success Operations `301900000`, success Inserts `301900000`, insert Affected `301900000`, failure Inserts `0`, success Updates `0`, update Affected `0`, failure Updates `0`
2023-02-03T13:27:01.835855+05:00 Batch duration is 00:00:08 723 ms 752 mcs 100 ns, rows was read is `301 900 000`, mem allocated is `2`Mb, import processing with `301900000` success Operations
2023-02-03T13:27:04.426001+05:00 Rows read `301 929 373`, success Operations `301929373`, success Inserts `301929373`, insert Affected `301929373`, failure Inserts `0`, success Updates `0`, update Affected `0`, failure Updates `0`
2023-02-03T13:27:04.426257+05:00 Finish import files of pattern AS_STEADS_PARAMS_20*.{x,X}{m,M}{l,L}
2023-02-03T13:27:04.426419+05:00 Finish region data import
2023-02-03T13:27:04.427513+05:00 Batch duration is 00:00:02 591 ms 510 mcs 900 ns, rows was read is `301 929 373`, mem allocated is `2`Mb, import processing with `301929373` success Operations
2023-02-03T13:27:04.427642+05:00 Rows was read is `301 929 373`, import was processed with `301929373` success operations, max mem allocated is `2`Mb
2023-02-03T13:27:04.427753+05:00 Import duration is 08:20:06 469 ms 147 mcs 400 ns
2023-02-03T13:27:04.427842+05:00 Script is finished 

Создаём индексы

Для ускорения можно создать две копии скрипта создания индексов.

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

Вы может попросить у меня логи, я с радостью поделюсь.

 php ./create-indexes.php 
 Creation index duration is 01:05:17 

Вот и сказочке конец, а кто слушал, молодец !

ГАР БД ФИАС или очень полная БД ФИАС

Время на прочтение

01.06.2020 ИФНС опубликовала новый формат выгрузки данных

17.12.2020 Мягко намекнула, что в 2021 будет использоваться только он

01.09.2021 Это свершилось: теперь просто «полная БД ФИАС» перестала обновляться и требуется использовать ГАР БД ФИАС

Частично импортируем ГАР БД ФИАС в MySQL на PHP.

Небольшой офф

Возможно имеет смысл написать небольшую библиотеку, функционал которой — скачивать только необходимую часть zip архива для распаковки конкретного файла с web сервера, поддерживающего докачку. Весьма актуально, т.к. из этих 28Гб требуется значительно меньшая часть. Если вы знаете, что это уже есть где-то «из коробки» или реализовано отдельной библиотекой — пожалуйста, напишите.

Теперь новая БД содержит иерархическую информацию об адресных объектах в двух вариантах:

— по административно-территориальному устройству (для упорядоченного осуществления функций государственного управления) — на основе этого код ОКАТО

— по муниципальному устройству (для организации местного самоуправления) — соответственно, ОКТМО

Не каждый объект «доступен» в обоих иерархиях. Например, если мы возьмём г. Карабулак, то по административно-территориальному устройству он находится в республике Ингушетия, а по муниципальному устройству расположен городском округе города Карабулак, который в свою очередь в республике Ингушетия и «не участвует» в административно-территориальном устройстве.

Таблица gar_addr, ключевое поле id. Иерархию определяют указывающие на него owner_adm и owner_mun. Субъекты РФ (и Байконур) имеют level=1, owner_adm=owner_mun=0. Содержит информацию о названиях адресных объектов (NAME, TYPENAME) и говорящие за себя OKATO, OKTMO, KLADR. O BJECTGUID, ранее в ФИАС именовался AOGUID, является идентификатором адресного объекта (уникальный для актуальных записей; не уникальный, если используются исторические устаревшие записи). O BJECTID аналогичен по значению OBJECTGUID, но уже целочисленный.

КАК СКАЧАТЬ СПИСОК АДРЕСОВ ИЗ ФИАС
КАК СКАЧАТЬ СПИСОК АДРЕСОВ ИЗ ФИАС

Импорт и частичное описание структуры.


Всё описанное ниже реализовано в исходниках.

Ранее в ФИАС был один файл со всеми регионами, теперь данные о каждом регионе в своей директории.

b) Импортируем файлы AS_ADDR_OBJ_(дата)_(идентификатор). XML, содержащие информацию об адресных объектах.

КАК СКАЧАТЬ СПИСОК АДРЕСОВ ИЗ ФИАС

Все элементы однотипные, выбираем только ISACTUAL=1 и ISACTIVE=1. И получить мы можем только название объекта и поля OBJECTID, OBJECTGUID. Теперь в этом файле нет указания на дочерний объект, нет данных об ОКАТО, ОКТМО — все они находятся в отдельном XML файле.

c) Проверим, что OBJECTID является уникальным. Если нет — надо разбираться, что это вызвало и писать в ИФНС. Ранее в ФИАС такая проблема часто возникала. Проиндексируем таблицу по этому полю — по нему будет определяться адресный объект при обработке последующих XML с данными.

e) Проиндексируем дома по OBJECTID и убедимся, что все записи уникальны.

h) Проиндексируем gar_addr по owner_adm и owner_mun

i) И начнём искать ошибки 🙂 Отметим достижимыми все субъекты РФ. И далее будем отмечать достижимыми все записи, родители которых тоже достижимы, до тех пора, пока количество достижимых не изменится. Так делаем по обоим полям — owner_adm, owner_mun. Если owner_adm = owner_mun = 0, то оказалось так, что мы не можем выбрать этот объект — это ошибка. Информация об этом будет сохранена в отчёте, а запись удалена. Отправляется bug-report

Самизнаетекому

ГАР, полная выгрузка

В файле AS_ADDR_OBJ_20210906_2a908987-3309-454e-9364-b75afd551e12. XML

есть объект с ISACTUAL=»1″ ISACTIVE=»1″

<OBJECT ID=»1823960″ OBJECTID=»95254004″ OBJECTGUID=»e3b0cdce-8a09-4955-8a43-a85ae759cfde» CHANGEID=»138210670″ NAME=»2-й Мартозанова» TYPENAME=»пер» LEVEL=»8″ OPERTYPEID=»10″ PREVID=»0″ NEXTID=»0″ UPDATEDATE=»2020-01-15″ STARTDATE=»2020-01-15″ ENDDATE=»2079-06-06″ ISACTUAL=»1″ ISACTIVE=»1″>

однако, его OBJECTID=»95254004″ вообще не встречается в AS_ADM_HIERARCHY_20210906_221e769c-cfac-4af6-9a20-04cc9c2e1fe5. XML AS_MUN_HIERARCHY_20210906_214fdb76-13c8-49cf-90ef-b5f05c4ee6df. XML

Таких проблем в выгрузке от 07.09.2021 — 107 (из 1405143+107 записей)

j) Теперь проверяем дома. Удаляем и логируем записи без owner_*, т.к. до них мы не сможем добраться. Логично считать ошибкой отсутствие owner в любом из типе устройств — дом же есть, значит до него надо как-то добраться. Разбираться с такими ситуациями придётся вручную (аналогично предыдущему пункту).

В выгрузке от 07.09.2021 — по owner_adm все дома достижимы; по owner_mun — 1639 домов (из 25842972 интересующих) не имеют владельца.

Надеяться, что дом обычно расположен на конкретной улице и owner_adm должен совпадать с owner_mun не получится. Крайне малое количество домов имеют разных владельцев, например один и тот же дом «Х»:

Башкортостан, Уфимский р-н, Зубовский с/с, д. » Х»

Башкортостан, Уфимский м.р-н, с.п. Зубовский сельсовет, тер. С НТ Авиатор, ул N1, д. » Х»

В выгрузке от 07.09.2021 — не совпадающих owner_adm и owner_mun всего 2231 объект и есть огромное желание пренебречь одним из столбцов owner_*.

КАК СКАЧАТЬ СПИСОК АДРЕСОВ ИЗ ФИАС

Всё просто. Из плюсов, что при обработки иерархии мы не знаем к какому объекту относится OBJECTID — здесь же всё однозначно, искать надо только по таблице addr.

m) Удаляем вспомогательные столбцы и индексы

n,o) Выполняем слияние всех таблиц по регионам в одну общую.

p) Создаём нужные индексы

q) Переименовываем временные таблицы в нормальные имена

Чтобы получить этот результат надо обработать:

У меня этот процесс занял 28 часов 13 минут.

Приложения:

Исходный код
, реализующий импорт. (для Windows; для Linux придётся переписать bat на *sh и команды извлечения файла из архива — поискать exec в php файлах).

P. S.

MySQL dump
— только адресные объекты (67Мб).

Вероятно, в дальнейшем дамп будет удалён — потеряет свою актуальность; формат, используемый мной, изменится. Обычно информацию по домам и их индексам я группирую по владельцам — что позволяет уменьшить размер БД до 400Мб и использовать её даже на слабых VDS.

КАК СКАЧАТЬ СПИСОК АДРЕСОВ ИЗ ФИАС

P. P. S. locdb.ru давно не обновлялся, но, надеюсь, скоро выложу актуальную базу. » Живых» населённых пунктов стало значительно больше: не только не менее 1000 жителей, также учитываются населённые пункты с большим количеством домов.

Итоги

Странно начинать с итогов, но это интересней чем вникать в подробности, поэтому сначала кратко, а потом всё более и более подробно.

Алгоритм

  1. Развернуть архив, файлы с данными займут 300 GB

  2. Установить пакет для создания таблиц БД

  3. Установить пакет для импорта файлов

  4. Скопировать скрипты из исходников пакетов в отдельную директорию

  5. Разбить директории с данными регионов на равные по весу группы (каждая группа регионов будет импортироваться отдельным процессом PHP)

  6. Для каждой группы сделать копию скрипта импорта, в скрипте отредактировать список регионов в соответствии с группой

  7. Выполнить скрипт создания таблиц

  8. Запустить копии скриптов импорта в отдельных окнах терминала (параллельное выполнение)

  9. Выполнить скрипт создания индексов

Затраченное время

  1. 01-22 регионы, 54 GB — Rows was read is 301 929 373
    duration is 08:20:06

  2. 23-36, 59 GB — Rows was read is 326 217 126
    duration is 08:51:45

  3. 37-52, 63 GB — Rows was read is 342 339 705
    duration is 09:15:50

  4. 53-67, 57 GB — Rows was read is 315 815 574
    duration is 08:36:36

  5. 68-99, 55 GB — Rows was read is 285 073 083
    duration is 07:53:59

Дополнительно к этому, создание индексов, ещё час — Creation index duration is 01:05:17
. Запускать создание индексов параллельно особого смысла не имеет, потому что загрузка диска была 100% пополам с промежутками загрузки на 5%, чтобы совсем не было простоя диска, можно запустить два обработчика на создание индексов. Это сэкономит минут 20.

Использованное аппаратное обеспечение

Жёсткий диск

 WDC WD40PURX-64AKYY0
Capacity:	3.6 TB
Type:	HDD 

Не самый медленный диск, во время импорта его скорости вполне хватало, загрузка не превышала 50%. Когда я развернул базу на «быстром» NVMe диске, импорт занял 40 часов, на этот «медленный» диск база развернулась за 42:58:16, + 1 час на индексы, вместе 44 часа, это на четыре часа дольше, да, разница есть, но не драматичная.

База без индексов занимает 190 GB, с индексами весит столько же сколько исходные файлы: 231 GB (248,504,378,147 bytes), надо сказать, что у меня сделан всего 1 индекс в каждой таблице, для работы с базой, мне кажется, надо минимум пять индексов.

Процессор

 Intel(R) Core(TM) i5-9400 CPU @ 2.90GHz
Base speed:	2.90 GHz
Sockets:	1
Cores:	6
Logical processors:	6
Virtualization:	Enabled
L1 cache:	384 KB
L2 cache:	1.5 MB
L3 cache:	9.0 MB 

Загрузка процессора для пяти обработчиков упёрлась в потолок 100%, для четырёх — так же 100%, 3 — 85%, 2 — 55%, 1 — 35%. Памяти потребовалось: 8 Mb на процесс PHP и 4 Mb на процесс PostreSql, чтение с диска PHP — 2 Mb/s, PostreSql — 1.5 Mb/s.

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

Про оперативную память нет смысла писать, потому что как видите, её объём не значителен.

Заключение

Импорт написан в декларативном стиле, каждая таблица объявлена как набор колонок. Для колонок определёно их поведение, при присвоении значения, при вставке новой записи, при обновлении ранее добавленной записи.

Пространство имён SbWereWolf\FiasGarDataImport\Import\Composition\Column
содержит примеры классов колонок, SbWereWolf\FiasGarDataImport\Import\Processor
— примеры таблиц.

Изучайте, копируйте, распространяйте 🙂

Оцените статью
ГИС ЖКХ