Pull to refresh

Comments 21

Когда то давно хотел на лету парсить конкретные csv файлы из zip-архива чтобы не тратить много памяти. Позже выяснилось, что нужно полностью скачивать весь архив, т.к. важные для распаковки метаданные находятся в конце этого файла (central directory). Но недавно на хабре видел статью, где автор утверждал, что его библиотека на js (https://github.com/greggman/unzipit) умеет это делать (статью похоже удалили). Я немного изучил его реализацию, похоже это действительно возможно, есть несколько путей:
1) Считывать архив дважды (возможно будет долго, но не придется хранить целиком)
2) Использовать метаданные файлов (local file header), которые тоже встречаются в архиве (но как понял, на них не стоит рассчитывать).
3) Если сервер поддерживает HTTP Range requests, то можно сразу обратиться к концу файла и сразу получить нужные метаданные для распаковки.
В любом случае, я думаю вам стоит попробовать эту библиотеку. Отпишитесь, если найдете решение получше.

Спасибо. Решение получше придётся написать самому и, похоже, теоретически сложного-то ничего нет. Возможно, некоторые проблемы могут возникнуть только в больших размерах получаемых данных (тут у меня возник другой вопрос, "а как оптимально скачивать cUrl-ом >20Гб в PHP"). JS (ес-но для NodeJS) меня не устраивает. Вспомнил:

Простой PHP код для создания ZIP файлов на лету (который я применяю для docx, xlsx...)
class ZIPMaker {
    private $archiveFiles = 0;
    private $archiveData = '';
    private $archiveHeader = '';
    private $relOffset = 0;
    private function unix2DosTime( $ts = 0 ){
        $timeRR = getdate( $ts ? $ts : time() );
        if ( $timeRR['year'] < 1980) {
            $timeRR['year'] = 1980;
            $timeRR['mon'] = $timeRR['mday'] = 1;
            $timeRR['hours'] = $timeRR['minutes'] = $timeRR['seconds'] = 0;
        }
        return
            ( ( $timeRR['year'] - 1980 ) << 25 ) |
            ( $timeRR['mon'] << 21 ) |
            ( $timeRR['mday'] << 16 ) |
            ( $timeRR['hours'] << 11 ) |
            ( $timeRR['minutes'] << 5 ) |
            ( $timeRR['seconds'] >> 1 );
    }
    function addFile( $name , $data , $ts = 0 ){
        $name = str_replace('\\', '/', $name);
        $HEXTime = pack('V', $this->unix2DosTime($ts));
        $crc = crc32($data);
        $dataGZ = gzcompress($data,9);
        $dataGZ = substr(substr($dataGZ, 0, strlen($dataGZ) - 4), 2); // fix crc bug
        $bin =
            "\x50\x4b\x01\x02".
            "\x00\x00".                  // version made by
            "\x14\x00".                  // version needed to extract
            "\x00\x00".                  // gen purpose bit flag
            "\x08\x00".                  // compression method
            $HEXTime.                    // last mod time & date
            pack('V', $crc).             // crc32
            pack('V', strlen($dataGZ)).  // compressed filesize
            pack('V', strlen($data)).    // uncompressed filesize
            pack('v', strlen($name)).    // length of filename
            pack('v', 0).                // extra field length
            pack('v', 0).                // file comment length
            pack('v', 0).                // disk number start
            pack('v', 0).                // internal file attributes
            pack('V', 32).               // external file attributes
            pack('V', $this->relOffset). // relative offset of local header
            $name;
        $this->archiveHeader .= $bin;
        $bin =
            "\x50\x4b\x03\x04".
            "\x14\x00".                 // ver needed to extract
            "\x00\x00".                 // gen purpose bit flag
            "\x08\x00".                 // compression method
            $HEXTime.                   // last mod time and date
            pack('V', $crc).            // crc32
            pack('V', strlen($dataGZ)). // compressed filesize
            pack('V', strlen($data)).   // uncompressed filesize
            pack('v', strlen($name)).   // length of filename
            pack('v', 0).               // extra field length
            $name.
            $dataGZ;
        $this->archiveData .= $bin;
        $this->relOffset += strlen($bin);
        $this->archiveFiles++;
    }
    function bin(){
        return
            $this->archiveData.
            $this->archiveHeader.
            "\x50\x4b\x05\x06\x00\x00\x00\x00".
            pack('v', $this->archiveFiles).          // total #of entries "on this disk"
            pack('v', $this->archiveFiles).          // total #of entries overall
            pack('V', strlen($this->archiveHeader)). // size of central dir
            pack('V', $this->relOffset).             // offset to start of central dir
            "\x00\x00";                              // .zip file comment length
    }
    function passthru( $fileName = 'attachment.zip' , $mimeType = 'application/zip' ){
        header('Content-type: '.$mimeType);
        header('Content-length: '.strlen($ctx = $this->bin()));
        header('Content-Disposition: attachment; filename='.$fileName);
        echo $ctx;
    }
}

И благодаря:

return
$this->archiveData.
$this->archiveHeader.
"\x50\x4b\x05\x06\x00\x00\x00\x00".
pack('v', $this->archiveFiles).          // total #of entries "on this disk"
pack('v', $this->archiveFiles).          // total #of entries overall
pack('V', strlen($this->archiveHeader)). // size of central dir
pack('V', $this->relOffset).             // offset to start of central dir
"\x00\x00";                              // .zip file comment length

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

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

a) Потому что всё равно. Да и статья-то не про это, я даже постарался ни одной строчки кода в ней не разместить. Тот же код и алгоритм у меня сделает (и делает) PostgreSQL. Я бы сам придрался к php :)

b) Потому что LAMP.

Кстати, массовый поиск по UUID при кейсе "выбираем только актуальное" нужен крайне редко (в реальности, только если зачем-то пришлось проверять старые адреса, ес-но хранящие этот UUID, обычно улицы, на возможно изменившийся новый). При менее 1.5млн записей в 280Мб данных не напрягает даже на MySQL.

Не вижу смысла текстового поиска с учетом опечаток по таким данным, когда выбор по первым двум буквам улицы внутри населенного пункта сокращает результат до десятков, а чаще меньше, вариантов. И выбор самого населенного пункта по 2-3 буквам с учетом количества населения\числа домов - тоже десятки вариантов.

В 2017 делал парсинг XML ФИАС в Oralce, когда она весила 24 Гб.

Многопоточное приложение на Delphi.

Основной поток разбирал по кусочкам XML с буфером около 2 Мб. И скармливал дочерним потокам в текстовый массив, а они в свою очередь делали инсерты.

Лучшая производительность была при установке потоков = кол-ву виртуальных ядер на клиенте.

Первые 5 Гб сервер съедал быстро, по 6 Мб/сек на поток, а потом начинал тормозить. Так и не выяснили почему.

Всего на 24 ГБ уходило около 4 часов. На клиенте прога занимала менее 50 Мб оперативки.

Вот только же не было такого размера в 2017. Не доросла она до 24Гб, остановилась на 12Гб. А 19,7 Гб версия ГАР БД ФИАС появилась уже в 2020 году только (о чем до сих пор написано на сайте ИФНС) и за год выросла до 28,9 Гб.

Аналогичные данные, что здесь (без owner_mun) 31.08.2021 получались на php за 3 часа. Не спешно, в один поток.

Мы же про разархивированные данные говорим?

А как я должен догадаться? Вы же не указали таблицы, а один размер - у архива. Мне что-то кажется, это относительно долго, т.к. AS_ADDROBJ + AS_HOUSE дошел до 44 Гб в 2021. В 2020 году он был 36 Гб и помню, что я в 2,5 часа укладывался. А теперь "ужас" более 128Гб :(

Коллеги, а как используете эту базу? Чисто для подсказок такие объемы выглядят дико... а других применений мне пока не приходит в голову...

Объемы-то для подсказок небольшие. Адреса от региона до улицы - 273Мб, и все дома с почтовыми индексами - 400Мб (если не нужны индексы и того меньше). Справляется даже слабенькая VDS.

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

Дико только парсить такие объемы, чтобы получить этот результат :(

И что у вас ни разу не было, что адрес прописки не существует в ФИАС?

Дома ещё иногда встречаются отсутствующие или почтовые индексы неопределенные\неверные у новых домов. Улицы уже давно без проблем. Произвольный "хвост" никто не отменял, но используется всё реже и реже. Сейчас посмотрел по двум базам - в совокупности последние 10К реальных адресов (все 2021 год) - не используется, т.е. улица была полностью определена выбором. А дом хранится без привязки к ФИАС, только при выборе в подсказках, статистики о его существовании у меня нет.

По прописке встречается - если отсутствует актуальный адрес в ФИАС, то обычно проблемы: например, человек прописан в деревне, дома (или даже целой деревни) уже и на карте-то нет. С пожилыми людьми обычно возникает.

Автор спасибо за статью. Можно код на git hub выложить ?

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

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

А подскажите по домам,как теперь нормально собрать номер дома например: дом 1 корпус 4 строение 3 ? Не понятно с ADDNUM1,ADDNUM2,ADDTYPE1, ADDTYPE2

Как и раньше, только теперь обозначение числовое. Я пока ещё не использую ГАР в системах подсказки (и ещё не писал "упаковщик" данных домов, чтобы это снова стало менее 500Мб с постовыми индексами), но заметил, что ADDNUM2\ADDTYPE2 отсутствуют в доступных (т.е. актуальных и до которых можно добраться по иерархии) домах.

Запись реальная, правда недостижимая (мне ИФНС ещё не ответила на письмо выше, глюк у меня или, как обычно, будет исправлено)
HOUSENUM="37" ADDNUM1="10" ADDNUM2="10" HOUSETYPE="1" ADDTYPE1="1" ADDTYPE2="2"
влд. 37 влд. 10 д. 10

HOUSENUM="4" ADDNUM1="94" HOUSETYPE="10" ADDTYPE1="2"
корп. 4 д. 94 (В ГАР именно так. Приоритеты, что дом раньше делать самостоятельно.)

Кстати, сможете назвать хоть один такой реальный такой "сложный" адрес, как в вашем примере?

UFO just landed and posted this here

Это как раз то, из-за чего нецензурно выражаться хочется. С улицами всё хорошо, с домами, похоже, стало хуже. Полная запись этого дома в ГАР сейчас выглядит так:

HOUSE ID="40479359" OBJECTID="67218762" OBJECTGUID="ed525b52-8467-467f-aa0a-ca802d9432b6" CHANGEID="100222233" HOUSENUM="1-3" ADDNUM1="4" HOUSETYPE="2" ADDTYPE1="2" OPERTYPEID="10" PREVID="0" NEXTID="0" UPDATEDATE="2017-04-05" STARTDATE="1900-01-01" ENDDATE="2079-06-06" ISACTUAL="1" ISACTIVE="1"

Существенное только: HOUSENUM="1-3" ADDNUM1="4" HOUSETYPE="2" ADDTYPE1="2"

И адрес получается "д. 1-3 д. 4". (Либо надо ещё раз очень внимательно перечитать документацию...)

А ещё таких HOUSETYPE="2" ADDTYPE1="2" в актуальных - 368670 домов (1.41%) и, похоже, это всё - проблемы.

В ФИАС этот же дом HOUSENUM="1-3" , STRUCNUM (строение) = "4" - все логично.

p.s. Кстати, видите, что на карте Яндекса нет "корпуса".
p.p.s. Меня пока только одно радует - такие сложности, обычно, в нежилых домах.

Отправил вопрос в ИФНС, на предыдущий (по поводу недостижимых улиц) мне пока не ответили. Но это нормально, всего-то чуть более 10 дней прошло.

С этими домами всё хорошо, для ADDTYPE1 и ADDTYPE2 существует отдельный справочник: AS_ADDHOUSE_TYPES где 2 это строение:

<HOUSETYPES>
<HOUSETYPE ID="1" NAME="Корпус" SHORTNAME="к." />
<HOUSETYPE ID="2" NAME="Строение" SHORTNAME="стр." />
<HOUSETYPE ID="3" NAME="Сооружение" SHORTNAME="соор." />
<HOUSETYPE ID="4" NAME="Литера" SHORTNAME="литера" />
</HOUSETYPES>

Да, спасибо. Уже нашел, оценил логику; написать об этом сюда, правда, забыл. Это HOUSETYPES из AS_ADDHOUSE_TYPES_. HOUSETYPES из AS_HOUSE_TYPES_... другие.

Sign up to leave a comment.

Articles