Пользователь
0,0
рейтинг
27 января 2014 в 20:16

Разработка → Пишем мониторинг наличия билетов на РЖД из песочницы

Не раз слышал от своих знакомых, что было бы неплохо увидеть сайт который будет мониторить наличие свободных мест на ржд. Про себя я думал — «да неплохо бы» и благополучно забывал, но пост заставил меня вспомнить навыки копи паста, которыми я владею в совершенстве и обернуть это дело в питонячий код. Сразу оговорюсь что именно про мониторинг будет во второй части, а в этой будет про то: как ходить на РЖД из питона, что это за загадочный sleep про который писали в предыдущем посту и как живется на Google App Engine. Итак приступим:
image

Сначала я написал код, а потом задумался о хостинге, естественно сайт не предполагал никакой наживы, а был лишь 4 fun, поэтому и хостинг должен быть бесплатным и тут я вспомнил про App Engine. Для начала работы стоит скачать SDK. Запускаем, указываем путь к будущему приложению:
image

В указанной рабочей директории создаем файл настроек app.yaml содержащий примерно следующее:

application: rzdzstan1
version: 1
runtime: python27
threadsafe: false
api_version: 1

handlers:
- url: /favicon.ico
  static_files: favicon.ico
  upload: favicon.ico

- url: /.*
  script: web.py

libraries:
- name: webapp2
  version: "latest"


Дальше в вышеобозначенной рабочей директории создаем, web.py и тут уже можно начинать писать код копипастить. Приложение будем строить на легковесном WebApp2. Итак пишем основные обработчики:

import webapp2

application = webapp2.WSGIApplication([
    ('/', MainPage),
    ('/trains', TrainListPage),
    ('/suggester', SuggesterPage),
], debug=True)

def main():
    application.run()

if __name__ == "__main__":
    main()


Далее, как и говорилось в базовой статье, нам понадобятся коды городов для создания запроса:

def getCityId(city, s):
  req = 'http://pass.rzd.ru/suggester?lang=ru&stationNamePart=' + urllib.quote(city.encode('utf-8'))
  city = city.lower()
  respData = getResponse(req)
  rJson = json.loads(respData)
  for item in rJson:
    if item['name'].lower() == city:
      s.response.out.write(u'Найден: '+item['name']+' -> '+str(item['id'])+'<br>')
      return str(item['id'])
  s.response.out.write(u'Не найден: '+city+'<br>')
  s.response.out.write(u'Выбранный вами город не найден, попробуйте найти в списке и ввести еще раз:<a href="../">Вернуться</a><br>')
  for item in rJson:
    s.response.out.write(item['name']+'<br>')
  return None


Ну а дальше остается получить rid, SESSION_ID и сформировать окончательный запрос, не забывая что часто РЖД рвет соединения, отвечает 500 кодом и т.д. чтобы это замаскировать напишем пару костылей-обработчиков:

def getResponse(url):
  good = False
  while not good:
    try:
      resp = opener.open(url, timeout=5)
      if resp.getcode() in [httplib.OK, httplib.CREATED, httplib.ACCEPTED]:
        good = True
    except (urllib2.HTTPError, HTTPException):
      pass
  return resp.read()

def getResponseStub(url):
  r = json.loads(getResponse(url))
  cnt = 0
  while (r['result']!='OK' and cnt < 5):
    sleep(1)
    cnt+=1
    r = json.loads(getResponse(url))
  return r

def getFinalRequest():
  req1 = 'http://pass.rzd.ru/timetable/public/ru?STRUCTURE_ID=735&layer_id=5371&dir=0&tfl=3&checkSeats=1&\
st0='+st0+'&code0='+id0+'&dt0='+date+'&st1='+st1+'&code1='+id1+'&dt1='+date

  r = json.loads(getResponse(req1))
  if (r['result']=='OK'):
    s.response.out.write(r['tp'][0]['msgList'][0]['message']) #errType
    s.response.out.write('<br>')
    return
  sid = str(r['SESSION_ID'])
  rid = str(r['rid'])
  req2 = 'http://pass.rzd.ru/timetable/public/ru?STRUCTURE_ID=735&layer_id=5371&dir=0&tfl=3&checkSeats=1&\
st0='+st0+'&code0='+id0+'&dt0='+date+'&st1='+st1+'&code1='+id1+'&dt1='+date+'&rid='+rid+'&SESSION_ID='+sid

  r = getResponseStub(req2)


И в получившемся ответе — лежит все необходимое для финального парсинга. Теперь о загадочном sleep, он переехал в функцию: getResponseStub, дело в том что когда мы запрашиваем req1 му таким образом просим поставить нас в очередь исполнения, и если сразу спросить req2 — результат может быть еще не получен. Радиоактивные исходники доступны тут качать осторожно. Попробовать в действии можно тут и тут ибо квоты там небольшие и под известным эффектом быстро закончатся, а пока эта статья проходит премодерацию попробую закинуть немного денег чтобы страница продержалась продолжительное время. В следующей части будем приделывать собственно саму нотификацию по емайлу.
@zstan
карма
3,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (14)

  • 0
    internal server error =(
  • 0
    Пытался недавно уехать на новый год, столкнулся с трудностями. Купить билет можно только за 45 дней, с 8 утра. Купить подряд можно не более четырёх билетов Итог — 8:03 все места распроданы остались лишь пара мест в св. Наболело. Спасибо за статью. Жду скрипт покупки билетов, без карты хотя бы с стандартным 3 часовым таймаутом выкупа.
    • 0
      про 8 утра учту, не знал, т.е. «новый день » на РЖД начинается в 8.00 верно? про таймаут выкупа не понял, я думал сделать запрос — раз в час, на проверку наличия свободных билетов, что такое таймаут выкупа? Спасибо.
      • 0
        6. Оплата билета наличными средствами

        Обратите внимание на то, что оплата наличными должна быть осуществлена не позднее, чем через три часа после оформления заказа. В противном случае заказ аннулируется.

        (http://www.ufs-online.ru/portal/Help/Zheleznodorozhnye-bilety/Oplata-zakaza)

        По безналу ограничения нет.
      • 0
        Совсем забыл… рассказать наблюдение. «Новый день» в РЖД начинается в 7:20 — 7:40 билеты для покупки не доступны, но они покупаются. Посмотреть этот эффект можно вбив на озоне покупку билета и в адресной строке вручную поменять дату (дата для выбора не доступна до 8:00). И ещё наблюдение если забронировать билет уже после 45 дней (на 30 на 20 день до отправления) то бронь снимается в последний день месяца.
  • 0
    Обратите внимание на то, что оплата наличными должна быть осуществлена не позднее, чем через три часа после оформления заказа. В противном случае заказ аннулируется.
    Для того чтобы мне оформить заказ — нужно будет знать как минимум ваш логин на РЖД, а я никаких логинов знать, понятно, не хочу, ибо это неправильно. Счас цель написать как можно более прозрачный для пользователя функционал и исключить подписку «того парня» на данную рассылку :)
    • 0
      С того же сайта (ufs-online) есть возможность купить без логина РЖД, но API там другой
      • 0
        это партнерка, они как я понимаю имеют процентик с продаж, так же предлагают заключить с ними партнерское соглашение, я такой цели не преследовал… возможно когда этот сервис оформится во что то большее чем существующий костыль — я повешу Donate исключительно в целях оплаты хостинга, ибо ту денежку что я закинул в момент поста на хабре, уже наполовину израсходовали :)
  • 0
    Хороший чекер, осталось уведомление на почту прикрутить, ждём-с :)
  • 0
    Не знаю изменилось ли сейчас положение вещей, но раньше РЖД выкидывала билеты в продажу пачками, каждую неделю-полторы.
    Так что отсутствие билетов на 45-ый день не гарантия того что их вообще не будет
    • 0
      Насколько я понимаю, при высоком спросе они добавляют вагоны, и новые билеты, соответственно, появляются в продаже.
  • 0
    На сайте сервиса тутуру есть возможность подписаться на уведомления о билетах (смс или почта), которые сдали, если все раскуплено. Я сама пользуюсь этим сервисом.
  • 0
    Some error occured: {u'discounts': {}, u'rid': 818338178, u'tipFlags': {u'Ukr': 0}, u'result': u'RID', u'SESSION_ID': 1}
  • 0
    Понимаю, что с момента написания статьи 1,5 года прошло :)… но сервис, похоже, теперь не работает — не удается выбрать пункты отправления и назначения. Будет ли это исправлено?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.