Pull to refresh

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

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



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



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

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

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, там рассылка на один телефонный номер бесплатна, а больше мне и не надо. Для каждого вашего конкретного города нужны буду уникальные числовые коды, которые как видим мы передаем на вход нашего метода:

$rzd->request([
    'Санкт-Петербург',
    '2004000',
    'Киров',
    '2060600',
    '28.12.2013',
]);


Чтобы их узнать, спасибо mafet, можно например сделать запрос к http://pass.rzd.ru/suggester?lang=ru&stationNamePart=Са, подставив две первые буквы города и найти там нужный город и вокзал, и оттуда достать нужный нам ид.

Если кому он нужен, то сам скрипт:

Скрипт
<?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={{code_from}}&dt0={{date}}&st1={{to}}&code1={{code_to}}&dt1={{date}}';
    private $data;
    private $replace = [
        '{{from}}',
        '{{code_from}}',
        '{{to}}',
        '{{code_to}}',
        '{{date}}',
    ];
    private $secure = '&rid={{rid}}&SESSION_ID={{session_id}}';
    private $replaceSecure = [
        '{{rid}}',
        '{{session_id}}',
    ];
    private $cookie = 'cookie';
 
    public function request($data) {
        $this->data = $data;
        $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) {
                    # здесь можно написать условие, например если цена меньше 4000р то делаем все что ниже и высылаем смс
                    $resultExec = 'На '.$data[4].' - '.$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" => 'id sms.ru',
                        "to"      => 'ваш телефон',
                        "text"   => $resultExec,
                    ));
                    sleep(2);
                    $body = curl_exec($ch);
                    curl_close($ch);
                }
        }
    }
}
 
 
 
$rzd = new rzd();
$rzd->request([
    'Санкт-Петербург',
    '2004000',
    'Киров',
    '2060600',
    '27.12.2013',
]);
 
 
$rzd = new rzd();
$rzd->request([
    'Санкт-Петербург',
    '2004000',
    'Киров',
    '2060600',
    '28.12.2013',
]);



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

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

Articles