Главная » Хабрахабр » [Из песочницы] Парсинг 0.5Tb xml за несколько часов. Поиск организаций по критериям в реестре субъектов МСП ФНС

[Из песочницы] Парсинг 0.5Tb xml за несколько часов. Поиск организаций по критериям в реестре субъектов МСП ФНС

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

В какой-то момент появилась необходимость проанализировать на основе открытых данных “Единого реестра субъектов малого и среднего предпринимательства” Федеральной налоговой службы (далее Реестр МСП) динамику по месяцам количества организаций определенного вида деятельности, а именно, сельхозпредприятий. Решению одной из таких задач будет посвящен обзор. Либо ограничен функционал, либо возникают проблемы при работе с кириллической кодировкой, либо не обеспечивается необходимая производительность, либо ограничены ресурсы «железа». Подходы, которые использовались при ее решении, надеюсь будут полезны тем, кто ищет варианты обработки больших структурированных массивов данных XML, но распространенные средства обработки такие как SelectFromXML, он-лайн XML обработчики по каким-то причинам не подходят. Программисты и профессионалы надеюсь не буду слишком строги к стилю кодирования и выбору способов реализации, а критика и советы в комментариях приветствуются.

Каждый архив содержит около 5-6 тыс. Итак задача:
На февраль 2018 года реестр МСП содержит 18 zip-архивов размером 3-4Gb. Из этого массива требуется отобрать только те, которые относятся к сельхозпредприятиям и проанализировать динамику количества этих предприятия по месяцам. файлов, содержащих сведения о примерно 6 миллионах организаций, общим объемом около 40Gb.

Исходные файлы ФНС размещены по ссылке

Файлы описания организаций содержат следующую структуру:

<Файл ИдФайл="VO_RRMSPSV_0000_9965_20170110_01b07970-41d2-4d1e-bb80-0abee395d333" ВерсФорм="4.01" ТипИнф="РЕЕСТРМСП" КолДок="900">
<ИдОтпр>
<ФИООтв Фамилия="-" Имя="-"/>
</ИдОтпр>
<Документ ИдДок="4e28d9a9-c004-0f72-a27d-7d677620df81" ДатаСост="10.01.2017" ДатаВклМСП="01.08.2016" ВидСубМСП="2" КатСубМСП="1" ПризНовМСП="2">
<ИПВклМСП ИННФЛ="636204531704">
<ФИОИП Фамилия="МАРЫШЕВ" Имя="ВЯЧЕСЛАВ" Отчество="ВЛАДИМИРОВИЧ"/>
</ИПВклМСП>
<СведМН КодРегион="63">
<Регион Тип="ОБЛАСТЬ" Наим="САМАРСКАЯ"/>
<Район Тип="РАЙОН" Наим="БЕЗЕНЧУКСКИЙ"/>
<НаселПункт Тип="УЛИЦА" Наим="СОВЕТСКАЯ"/>
</СведМН>
<СвОКВЭД>
<СвОКВЭДОсн КодОКВЭД="42.21" НаимОКВЭД="Строительство инженерных коммуникаций для водоснабжения и водоотведения, газоснабжения" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="52.21.2" НаимОКВЭД="Деятельность вспомогательная, связанная с автомобильным транспортом" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="74.30" НаимОКВЭД="Деятельность по письменному и устному переводу" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="63.91" НаимОКВЭД="Деятельность информационных агентств" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="95.23" НаимОКВЭД="Ремонт обуви и прочих изделий из кожи" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="42.21" НаимОКВЭД="Строительство инженерных коммуникаций для водоснабжения и водоотведения, газоснабжения" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="62.09" НаимОКВЭД="Деятельность, связанная с использованием вычислительной техники и информационных технологий, прочая" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="25.72" НаимОКВЭД="Производство замков и петель" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="47.54" НаимОКВЭД="Торговля розничная бытовыми электротоварами в специализированных магазинах" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="42.22.1" НаимОКВЭД="Строительство междугородних линий электропередачи и связи" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="47.99" НаимОКВЭД="Торговля розничная прочая вне магазинов, палаток, рынков" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="82.19" НаимОКВЭД="Деятельность по фотокопированию и подготовке документов и прочая специализированная вспомогательная деятельность по обеспечению деятельности офиса" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="49.32" НаимОКВЭД="Деятельность такси" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="42.22.2" НаимОКВЭД="Строительство местных линий электропередачи и связи" ВерсОКВЭД="2014"/>
</СвОКВЭД>
</Документ>
<Документ ИдДок="7a14e521-68a3-9514-7540-04cb03799ac4" ДатаСост="10.01.2017" ДатаВклМСП="10.09.2016" ВидСубМСП="2" КатСубМСП="1" ПризНовМСП="1">
<ИПВклМСП ИННФЛ="636204538611">
<ФИОИП Фамилия="РУЧКАНОВА" Имя="ЛЮДМИЛА" Отчество="АЛЕКСЕЕВНА"/>
</ИПВклМСП>
<СведМН КодРегион="63">
<Регион Тип="ОБЛАСТЬ" Наим="САМАРСКАЯ"/>
<Район Тип="РАЙОН" Наим="БЕЗЕНЧУКСКИЙ"/>
<НаселПункт Тип="УЛИЦА" Наим="МОЛОДЕЖНАЯ"/>
</СведМН>
<СвОКВЭД>
<СвОКВЭДОсн КодОКВЭД="47.11" НаимОКВЭД="Торговля розничная преимущественно пищевыми продуктами, включая напитки, и табачными изделиями в неспециализированных магазинах" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="47.25.12" НаимОКВЭД="Торговля розничная пивом в специализированных магазинах" ВерсОКВЭД="2014"/>
</СвОКВЭД>
</Документ>
<Документ ИдДок="ad8636bb-78c3-763c-52d2-4fe5a93e9a8f" ДатаСост="10.01.2017" ДатаВклМСП="10.09.2016" ВидСубМСП="2" КатСубМСП="1" ПризНовМСП="1">
<ИПВклМСП ИННФЛ="636204540794">
<ФИОИП Фамилия="МИЧУРОВА" Имя="ТАТЬЯНА" Отчество="АЛЕКСАНДРОВНА"/>
</ИПВклМСП>
<СведМН КодРегион="63">
<Регион Тип="ОБЛАСТЬ" Наим="САМАРСКАЯ"/>
<Город Тип="ГОРОД" Наим="САМАРА"/>
<НаселПункт Тип="УЛИЦА" Наим="ВЛАДИМИРСКАЯ"/>
</СведМН>
<СвОКВЭД>
<СвОКВЭДОсн КодОКВЭД="47.41" НаимОКВЭД="Торговля розничная компьютерами, периферийными устройствами к ним и программным обеспечением в специализированных магазинах" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="49.20.9" НаимОКВЭД="Перевозка прочих грузов" ВерсОКВЭД="2014"/>
<СвОКВЭДДоп КодОКВЭД="47.78" НаимОКВЭД="Торговля розничная прочая в специализированных магазинах" ВерсОКВЭД="2014"/>
</СвОКВЭД>
</Документ>

Обработка будет выполняться в оболочке bash на виртуальной Linux машине с 2-я ядрами, 8 Gb оперативной памяти и 100Gb дискового пространства:

%Cpu0 : 6.1 us, 2.0 sy, 0.0 ni, 91.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 54.1 us, 11.2 sy, 0.0 ni, 6.1 id, 28.6 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 8258760 total, 64684 free, 5645284 used, 2548792 buff/cache
KiB Swap: 2129916 total, 1157076 free, 972840 used. 2271428 avail Mem

Скрипт должен обеспечить скачивание zip-архивов с сайта ФНС, переименование файлов для удобства последующей обработки, распаковку, обработку парсером (используется xmlstarlet) для поиска организаций, соответствующих заданных в скрипте критериям, очистку диска от временных файлов (в процессе обработки исходные файлы занимают десятки Gb), сохранение в формате, удобном для последующего использования в системах анализа данных и импорта в программы для работы с электронными таблицами (в нашем случае будет использоваться формат csv).

Чтобы скрипт понимал, какие архивы с РМСП ему обрабатывать, создадим файл, под условным названием «полетное задание», где укажем, какие файлы обрабатывать и как именовать полученный результат. Скачивание и переименование выполним с использованием wget.

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

rmspfiles.txt

http://data.nalog.ru/opendata/7707329152-rsmp/data-08262016-structure-08012016.zip;20160826;*
http://data.nalog.ru/opendata/7707329152-rsmp/data-09102016-structure-08012016.zip;20160910;*
http://data.nalog.ru/opendata/7707329152-rsmp/data-10102016-structure-08012016.zip;20161010;*
http://data.nalog.ru/opendata/7707329152-rsmp/data-11252016-structure-08012016.zip;20161125;*
http://data.nalog.ru/opendata/7707329152-rsmp/data-12122016-structure-08012016.zip;20161212;*
http://data.nalog.ru/opendata/7707329152-rsmp/data-01112017-structure-08012016.zip;20170111;*
http://data.nalog.ru/opendata/7707329152-rsmp/data-02102017-structure-08012016.zip;20170212;*
http://data.nalog.ru/opendata/7707329152-rsmp/data-03102017-structure-08012016.zip;20170310;*
http://data.nalog.ru/opendata/7707329152-rsmp/data-04102017-structure-08012016.zip;20170410;*
http://data.nalog.ru/opendata/7707329152-rsmp/data-05102017-structure-08012016.zip;20170510
http://data.nalog.ru/opendata/7707329152-rsmp/data-11062017-structure-08012016.zip;20170611
http://data.nalog.ru/opendata/7707329152-rsmp/data-07102017-structure-08012016.zip;20170710
http://data.nalog.ru/opendata/7707329152-rsmp/data-08102017-structure-08012016.zip;20170810
http://data.nalog.ru/opendata/7707329152-rsmp/data-09112017-structure-08012016.zip;20170911
http://data.nalog.ru/opendata/7707329152-rsmp/data-10102017-structure-08012016.zip;20171010
http://data.nalog.ru/opendata/7707329152-rsmp/data-11102017-structure-08012016.zip;20171110
http://data.nalog.ru/opendata/7707329152-rsmp/data-12112017-structure-08012016.zip;20171211
http://data.nalog.ru/opendata/7707329152-rsmp/data-01112018-structure-08012016.zip;20180111

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

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

Итак, что получилось в итоге:

Загрузчик файлов:

#!/bin/bash # **************** batch downloader from rmsp v 1.0. 2018-02-15 ***********************
start=`date +%s`
dt=`date`
logFn='output_wget.log' printf "********************************************************************************************\n" | tee tmp_output.log
echo "* $ wget *" | tee -a tmp_output.log
printf "*********************************************************************************************\n\n" | tee -a tmp_output.log # download loop считываем файлы по ссылкам из “полетного задания”, переименовываем и сохраняем в папке zip2
IFS=';' while read line; do read -r -a array <<< "$line" echo "${array[0]} | ${array[1]} "
# wget ${array[0]} -O ./zip2/${array[1]}.zip | tee -a tmp_output.log 2>&1 # get filesize of external - этот параметр пишется в лог для оценки производительности обработчика FILESIZE=$(wget --spider ${array[0]} 2>&1 | awk '/Length/ {print $2}') # - c - continue, 3>&1 - размер файла wget -c ${array[0]} -O ./zip2/${array[1]}.zip 3>&1 | tee -a tmp_output.log end=`date +%s`; runtime=$((end-start)); dt=`date '+%Y-%m-%d %H:%M:%S'` printf "%s %4d sec %10d %s [ %s" ] ${dt} $runtime $FILESIZE ${array[0]} ${array[1]} | tee -a tmp_output.log
done < rmspfiles.txt echo "" | tee -a tmp_output.log //записываем в файл для последующей отладки результаты работы cat tmp_output.log $logFn > tmp_output2.log; mv tmp_output2.log $logFn

2. Парсер

#!/bin/bash
# 2018-02-16 Версия 1.1 Добавлены столбцы в итоговый файл
# 2018-02-19 Добавлены кавычки для предотвращение переноса строки в номерах лицензий в excell
# 2018-02-19 Добавлен sed для замены /n -> ; @@; -> \n
# удалены для лицензий кавычки # задаем разделитель колонок для итоговых файлов (в нашем случае табуляция)
sp=' ' # Задаем параметры обработчика, пути для исходных и результирующих файлов, названия файлов для журналов обработки. path_src="./src"
path_zip="./zip2"
path_res="./res"
t1="p1.log"
t2="p2.log"
t3="parsz.log" fnExt=""$1 start=`date +%s`
dt=`date '+%Y-%m-%d %H:%M:%S'` # Результат выводим в лог echo "**** | parsz | ${dt} unzip from: $path_zip/$fnExt.zip to $path_src/$fnExt" # | tee $t1 # -q quiet mode (-qq => quieter)
# -o overwrite files WITHOUT prompting # -j junk paths. The archive's directory structure is not recreated; all files are deposited in the extraction directory (by default, the current one). unzip -j -q -o $path_zip/$fnExt.zip -d $path_src/$fnExt/ end=`date +%s`
runtime=$((end-start)) MOREF1=`ls "$path_src/$fnExt/" | wc -l` echo " ${dt}, $runtime sec [${MOREF1}] | files from: $path_src/$fnExt/ to $path_res/$fnExt.csv" | tee -a $t1 echo "ИНН$spНаименование МСП\
$spКатегория МСП\
$spВид МСП\
$spВид Деятельности (Основной ОКВЭД)\
$spРегионНаим\
$spРайонТип\
$spРайонНаим\
$spгородТип\
$spгородНаим\
$spНаселПунктТип\
$spНаселПунктНаим\
$spДатаСост\
$spДатаВключения\
$spНомерЛицензии\
$spФайлИмя@@\ " > $path_res/res-$fnExt.csv /usr/bin/find $path_src/$fnExt/ -name "*.xml" | xargs -n1 xmlstarlet sel -T -f -t -m "//Документ/ОргВклМСП[contains(@НаимОрг,'СЕЛЬСКОХОЗЯЙСТВЕНН')]" \
-v "@ИННЮЛ" -o "$sp" \
-v "@НаимОрг" -o "$sp" \
--if "../@КатСубМСП=1" -o "Микро" --else --if "../@КатСубМСП=2" -o "Малые" --else -o "Средние" --break --break -o "$sp" \
--if "../@ВидСубМСП = 1" -o "Организация" --else -o "ИП" --break -o "$sp[" \
-v "../СвОКВЭД/СвОКВЭДОсн/@КодОКВЭД" -o "]" \
-v "../СвОКВЭД/СвОКВЭДОсн/@НаимОКВЭД" -o "$sp" \
-v "../СведМН/Регион/@Наим" -o "$sp" \
-v "../СведМН/Район/@Тип" -o "$sp" \
-v "../СведМН/Район/@Наим" -o "$sp" \
-v "../СведМН/Город/@Тип" -o "$sp" \
-v "../СведМН/Город/@Наим" -o "$sp" \
-v "../СведМН/НаселПункт/@Тип" -o "$sp" \
-v "../СведМН/НаселПункт/@Наим" -o "$sp" \
-v "../@ДатаСост" -o "$sp" \
-v "../@ДатаВклМСП" -o "$sp" \
-v "../СвЛиценз/@НомЛиценз" -o "$sp" \ -o "$fnExt@@" \
-n >> $path_res/res-$fnExt.csv end=`date +%s`
runtime=$((end-start)) dt=`date '+%Y-%m-%d %H:%M:%S'` echo " ${dt}, $runtime sec :parsing" | tee -a $t1 # Удаляем переносы строк в значениях за исключением последних в строках sed -e ':a;N;$!ba;s/\n/;/g' $path_res/res-$fnExt.csv > $path_res/sed_tmp.csv
sed -e 's/@@;/\n/g' $path_res/sed_tmp.csv > $path_res/res-$fnExt.csv end=`date +%s`
runtime=$((end-start)) dt=`date '+%Y-%m-%d %H:%M:%S'`
echo " ${dt}, $runtime sec :sed " | tee -a $t1
cat $t1 $t3 > $t2; mv $t2 $t3
# удаляем исходные XML файлы rm -rf $path_src/$fnExt/*
echo "Удаляем исходные XML файлы rm -rf $path_src/$fnExt/*" rm $t1

Весь массив данных из 18 файлов общим объемом в сотни Gb обрабатывается около 6 часов.
Процесс обработки записывается в файлы для последующей отладки и оптимизации скрипта.

После импорта в MS Excel получаем следующий результат:


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Newman и Continuous Integration на примере Atlassian Bamboo. Изобретение велосипеда

Введение Научившись писать функциональные тесты, и написав их порядка полутора сотен, мы решили, что настало то самое время — время прикрутить эти тесты к нашим CI-сборочкам. В недавней статье наш боевой товарищ actopolus рассказал о том, как мы научились применять ...

«Невозможная» ретро игра

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