Это сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

Сканирование наличия метрических книг

Написал небольшой скрипт на ПХП для сканирования наличия метрических книг в Национальном Архиве Республики Татарстан. У них на сайте приходится по одному году выуживать, что страшно долго.

Так как некоторые архивы горели, в них могут отсутствовать записи за какие-то периоды. Например, записи по селу Спасское Бугульминского района есть за 1852-1856, 1858, 1861-1864, 1866, 1871-1879, 1884-1889, 1891-1896 и 1898-1917 гг.

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

Как же мне это всё напоминает программирование двадцать лет назад! «Введите число и нажмите „Ввод“», правда тогда я всё больше на языках ассемблеров писал.

<?
    $URL = 'http://www.archive.gov.tatarstan.ru/_go/anonymous/metbooks/';
    $COOKIE = '';

    // парсим страницу, вынимаем оттуда все теги SELECT
    function U_getselects($url, $content = false)
    {
        if ($content === false) {
            global $COOKIE;

            $opts = array(
                'http' => array(
                    'method' => 'GET',
                    'header' => 'Cookie: ' . $COOKIE,
                ),
            );

            $content = file_get_contents($url, false, stream_context_create($opts));
            foreach ($http_response_header as $header) {
                if (preg_match('/^set-cookie:.*?(PHPSESSID=[^; ]+)/i', $header, $m)) {
                    $COOKIE = $m[1];
                    break;
                }
            }
        }

        $out = array();

        $content = iconv('cp1251', 'utf8', $content);

        if (preg_match('@<span class="bt4r">([^<>]+)</span>@si', $content, $m)) {
            echo 'Ошибка: ', $m[1], "\n";
            exit;
        }

        $mask = '@<span class="bt4b">([^<>]+)(?:<[^>]+>\s*)*?<select\s+.*?name="([^"]+)"[^>]*>(.*?)</select>@si';

        if (preg_match_all($mask, $content, $matches, PREG_SET_ORDER)) {

            foreach ($matches as $select) {
                $options = preg_match_all('/"([^"]+)"/s', $select[3], $m) ? $m[1] : array();

                $out[] = array(
                    'title' => $select[1],
                    'name' => $select[2],
                    'options' => $options,
                );
            }
        }

        return $out;
    }


    // Даём пользователю выбрать какой-то пункт из найденного SELECTа
    function U_reactselect($select)
    {
        for (;;) {
            echo $select['title'], "\n";

            foreach ($select['options'] as $key => $val) {
                if (strpos($val, '/') !== false) list(,$val) = explode('/', $val, 2);

                printf("% 2d. %s\n", $key + 1, $val);
            }

            echo "\nПожалуйста, введите номер пункта и нажмите Enter: ";

            $value = (int) fgets(STDIN);

            if (in_array($value - 1, array_keys($select['options']))) {
                echo "\n";
                return $select['options'][$value - 1];
            }

            echo "\n\nТакого пункта нет в списке. Попытайтесь ещё раз, пожалуйста.\n\n";
        }
    }

    // Отправляем запрос
    function U_postselect($url, $fields)
    {
        global $COOKIE;

        $fields = array_map(function($item) {
            return iconv('utf8', 'cp1251', $item);
        }, $fields);

        $opts = array(
            'http' => array(
                'method'        => 'POST',
                'header'        => 'Cookie: ' . $COOKIE . "\r\n".
                                    'Content-type: application/x-www-form-urlencoded',
                'content'       => http_build_query($fields),
            ),
        );

        return file_get_contents($url, false, stream_context_create($opts));
    }

    // Просим ввести диапазон
    function U_getyear($message)
    {
        echo $message;
        $val = (int) fgets(STDIN);

        return min(1917, max($val, 1724));
    }

    $postcontent = false;

    for ($step = 1; $step < 3; $step++) {

        $choices = array();

        foreach (U_getselects($URL, $postcontent) as $select) {
            $choices[$select['name']] = U_reactselect($select);
        }

        $choices['action'] = 'step' . $step;
        $choices['to_step' . ($step + 1)] = 'Далее >';

        $postcontent = U_postselect($URL, $choices);
    }

    $choices = array(
        'to_step4' => 'Найти',
        'action' => 'step3',
    );

    foreach (U_getselects($URL, $postcontent) as $select) {
        $choices[$select['name']] = U_reactselect($select);
    }

    $from = U_getyear('Введите начальный год: ');
    $to = U_getyear('И конечный: ');

    if ($from > $to) {
        list($from, $to) = array($to, $from);
    }

    $years = array();

    echo "\n";

    for ($year = $from; $year <= $to; $year++) {
        $choices['year'] = $year;

        $postcontent = U_postselect($URL, $choices);
        $found =
            strpos($postcontent, "\xed\xe5\x20\xed\xe0\xe9\xe4\xe5\xed\xfb") === false &&
            strpos($postcontent, "\xed\xe0\xe9\xe4\xe5\xed\xfb") !== false;

        if ($found) {
            $years[] = $year;
            echo '+';
        } else {
            echo '-';
        }

    }

    if ($years) {
        $years[] = INF;

        echo "\nАрхив найден за следующие годы: ";

        for ($i = 0, $l = sizeof($years); $i<$l-1; $i++) {
            if ($i) echo ', ';
            echo $years[$i];

            for($c = 0, $i++; $i<$l; $i++, $c++) {
                if ($years[$i] - $years[$i-1] > 1) {
                    $i--;

                    if ($c) {
                        echo '-', $years[$i];
                    }

                    break;
                }
            }
        }

        echo "\n";

    } else {
        echo "\nНичего не найдено.\n";
    }
15 комментариев
spiridonov@gmail.com 2012

А прикрути к своему сайту, пожалуйста :)

Евгений Степанищев (bolknote.ru) 2012

Комментарий для spiridonov@gmail.com:

Увы, он может выполняться куда дольше 30 секунд (у моего хостера есть ограничение на время выполнения скрипта). Может, мне просто найти машину под Виндоус и сделать из него запускаемый файл?

MiRacLe (miracle.rpz.name) 2012

Комментарий для Евгения Степанищева:

Откуда взялся $http_response_header в U_getselects?

dinoel 2012

Комментарий для miracle.rpz.name:

http://php.net/manual/ru/reserved.variables.httpresponseheader.php

MiRacLe (miracle.rpz.name) 2012

...век учись..., спасибо.

hshhhhh (hshhhhh.name) 2012

for (;;)

Забавное.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для hshhhhh.name:

Короче, чем while (true) и в некоторых языках быстрее выполняется (так как нет никакого условия, которое надо проверить).

Сергей Танкеев 2013

Доброе дело делаете Евгений. Я пятый год пытаюсь составить представление о своих пращурах. Вот настал момент когда остановился. Теперь без метрических записей все остановилось. Начал своих родственников искать, а получилось, что в Маклашеевке (с конца 50-х годов, на дне Куйбышевского водохранилища), ТрехОзерской волости, Спасского уезда, Казанской губернии, где родился мой отец, оказалось семь семей Танкеевых, или 7 генеалогических веток. Соотносительно моего поколения, у прадедов этих ВЕТОК я не знаю отчеств, про года рождения умолчу, а про состав их семей даже и не упоминаю. Несколько месяцев рыскал по просторам интернета и вот сегодня нашел электронный адрес НАЦИОНАЛЬНОГО АРХИВА РЕСПУБЛИКИ ТАТАРСТАН. Разумеется сайт не работает, как буд-то и не существует. И вот еще вышел на Вас.
Живу я в тридевятом государстве тридесятого царства. Приехать в Казань или Ульяновск, где хранятся ревизские Книги, мне как пенсионеру — не по карману. А то, что я собрал по инету не выходя из дому теперь в застое. Если сможете помочь, то ответьте. Мой адрес: pocherk1950@rambler.ru, тогда я вышлю первичные установочные данные.

Сергей Танкеев 2013

Еще раз прочел про выуживание раз в год из архива интересующей информации. Поделись опытом. Когда это делапть? Темной ночью или рано утром, в какой день недели, месяц? Или в праздники, когда возможно?

Сергей Танкеев 2013

*pocherk1950@rambler.ru*

Сергей Танкеев 2013

Вроде все, а то написано инкогнито.

Евгений Степанищев (bolknote.ru) 2013

Комментарий для Сергей Танкеев:

На сайте НА РТ написано, на самом деле. Одно неудобство — они работают только в рабочие дни в рабочие часы (т. е. вечером после работы я не могу там посидеть), поэтому я нанимаю людей, которые делают это за меня.

Евгений Степанищев (bolknote.ru) 2013

Комментарий для Сергей Танкеев:

Живу я в тридевятом государстве тридесятого царства. Приехать в Казань или Ульяновск, где хранятся ревизские Книги, мне как пенсионеру — не по карману. А то, что я собрал по инету не выходя из дому теперь в застое. Если сможете помочь, то ответьте. Мой адрес: pocherk1950@rambler.ru, тогда я вышлю первичные установочные данные.

Сайт Национального Архива сейчас и правда не работает ( http://www.archive.gov.tatarstan.ru ), но, думаю, это временное явление. Я не смогу вам помочь в Архиве, но они работают по электронном почте: nart.archive@tatar.ru, выполнение запроса стоит 200 рублей за документ.

Сергей Танкеев 2013

Спасибо, попробую. А тебе здоровья, хороших идей и их воплощения.