Покупаем билеты на поезд в Новый год

в 22:16, , рубрики: php, Программирование, разработка, ржд, метки:

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

Покупаем билеты на поезд в Новый год

Но спрос явно превышает предложение. Особенно на плацкарт, который, прямо скажем, самый выгодный. Так что же делать? Если интересно, то можно пройти под кат. Но, конечно, все может быть не так драматично, а просто вам надо куда-то уехать, в любое время года, а цены на люкс от РЖД вас не устраивают. Все мы знаем про бронь которая снимается и билеты которые могут возвращать, их то мы и будем ловить :)

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

Запрос на получение мест на конкретную дату:

curl 'http://pass.rzd.ru/timetable/public/ru?STRUCTURE_ID=735&layer_id=5371&dir=0&tfl=3&checkSeats=1&st0={from}&code0=2004000&dt0={date}&st1={to}&code1=2060600&dt1={date}&rid=729493435&SESSION_ID=2' -H 'Cookie: JSESSIONID=00006mwFi5RKtF-z0R16OGSMJtS:17obqce3m;'

Жирным я выделил параметры, которые нас интересуют:

  • from — город отправления, выбираем сами
  • to — город куда мы хотим попасть, выбираем сами
  • date — дата отправления, выбираем сами
  • JSESSIONID — ид сессии, с ним нам ничего делать не нужно, просто будем использовать куки в curl
  • rid — загадочный ид номер 1
  • SESSION_ID — загадочный ид номер 2

После небольшого изучения становится понятно, что при каждом новом запросе rid изменяется внешне хаотично, а SESSION_ID просто увеличивается на один. Чтоже делать, как их узнать? Если присмотримся немного к логам, то увидим еще один запрос, который всегда идет перед этим.

Вот он:

curl 'http://pass.rzd.ru/timetable/public/ru?STRUCTURE_ID=735&layer_id=5371&dir=0&tfl=3&checkSeats=1&st0={from}&code0=2004000&dt0={date}&st1={to}&code1=2060600&dt1={date}' -H 'Cookie: JSESSIONID=00006mwFi5RKtF-z0R16OGSMJtS:17obqce3m;'

И вот, что он нам вернет:

{«result»:«RID»,"SESSION_ID":2,"rid":729493435,«discounts»:{}}

Нас в этом интересует понятно что :)

Все ясно скажете вы, делаем подряд два запроса, из первого получаем недостающие параметры, подставляем во второй и бинго! Но нет. Это еще не все. Я если честно, дальше впал в ступор, почему же оно отказывается работать? Сравнивал другие заголовки, искал скрытые переменные, искал немного магии. Но нет. Из консоли работает, с сайта работает, из скрипта — ошибка.

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

Вот и все. Данные есть, как бесплатно отослать смс? Можно конечно почтой, но если важна оперативность, то смс будет предпочтительнее. Тут все на ваш вкус, а я просто использовал sms.ru, там рассылка на один телефонный номер бесплатна, а больше мне и не надо. Для каждого вашего конкретного города лучше взять urlData с сайта, потому что там еще много загадочных параметров. Если кому он нужен, то сам скрипт:

Скрипт

<?php
 
class rzd {
    private $urlData = 'http://pass.rzd.ru/timetable/public/ru?STRUCTURE_ID=735&layer_id=5371&dir=0&tfl=3&checkSeats=1&st0={{from}}&code0=2004000&dt0={{date}}&st1={{to}}&code1=2060600&dt1={{date}}';
    private $data = [
        'Город откуда',
        'Город куда',
    ];
    private $replace = [
        '{{from}}',
        '{{to}}',
        '{{date}}',
    ];
    private $secure = '&rid={{rid}}&SESSION_ID={{session_id}}';
    private $replaceSecure = [
        '{{rid}}',
        '{{session_id}}',
    ];
    private $cookie = 'cookie';
 
    public function request($date) {
        $this->data[] = $date;
        $this->urlData = str_replace($this->replace, $this->data, $this->urlData);
        $ch = curl_init($this->urlData);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_COOKIEJAR, $this->cookie);
        curl_setopt($ch, CURLOPT_COOKIEFILE, $this->cookie);
        $result = json_decode(curl_exec($ch), true);
        $this->urlData .= str_replace($this->replaceSecure, [$result['rid'], $result['SESSION_ID']], $this->secure);
        sleep(2);
        $ch = curl_init($this->urlData);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_COOKIEJAR, $this->cookie);
        curl_setopt($ch, CURLOPT_COOKIEFILE, $this->cookie);
        $result = json_decode(curl_exec($ch), true);
        $result = reset($result['tp']);
        $result = $result['list'];
        foreach ($result as $train) {
            if (isset($train['cars']) && is_array($train['cars']))
                foreach ($train['cars'] as $ticket) {
                    $resultExec = 'На '.$date.' - '.$train['number']." - ".$ticket['type'].' за '.$ticket['tariff'].' - '.$ticket['freeSeats'].' м';
                    $ch = curl_init("sms.ru/sms/send");
                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
                    curl_setopt($ch, CURLOPT_POSTFIELDS, array(
                        "api_id" => 'ид sms.ru',
                        "to"      => 'номер телефона',
                        "text"   => $resultExec,
                    ));
                    sleep(2);
                    # здесь можно поставить условие на отсылку смс, например если цена меньше 4000 тр
                    $body = curl_exec($ch);
                    curl_close($ch);
                }
        }
    }
}
 
Выполняем метод с нужной датой
 
$rzd = new rzd();
$rzd->request('27.12.2013');
$rzd = new rzd();
$rzd->request('28.12.2013');
$rzd = new rzd();
$rzd->request('30.12.2013');

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

P.S. php 5.5, но что изменить для 5.4 и меньше, думаю всем понятно, и дада, тут нет никакого ООП, нет паттернов и нет продуманного дизайна кода, это просто скрипт, который пока что работает (пока ржд не изменят алгоритм)

Автор: tattoor

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js