История о том, как я парсер для дневника мастерил

Год назад я начал писать ботов для всеми любимого Телеграма. На Питоне, конечно. И вот недавно мой сын пошёл в школу, где, как оказалось, был электронный дневник под названием МРКО. Как вы могли догадаться, самая первая мысль — сделать бота (пока для личного пользования), который смог бы присылать в Телеграм оценки, домашнее задание и комментарии. Кому интересно — прошу под кат.



Пишем парсер


Сначала, понятное дело, нужно написать парсер для самого дневника. Для тех, кто не знает, поясню. Система входа примерно такая: ученик/родитель входит на портал mos.ru, авторизуется на нем и уже с этого портала входит на основной mrko.mos.ru. Вы можете подумать — почему же просто сразу не войти на mrko.mos.ru? Проблема в том, что сервер отвечает нам таким сообщением:


Вход для родителей и обучающихся производится только с сайта Портала Госуслуг Москвы.

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


Исследованием сниффера исходящего трафика Я понял, что сначала происходит GET-запрос к https://mrko.mos.ru/dnevnik/services/index.php?login=ЛОГИН&password=ПАРОЛЬ_В_MD5, ставятся необходимые куки и потом можно заходить на https://mrko.mos.ru/dnevnik/services/dnevnik.php?r=1&first=1. Начал я с импортирования моей любимой библиотеки для работы с HTTP в Питоне — Requests. Далее — создание элементарной сессии:


import requests
def diary():
    session = requests.Session()
    headers = {'Referer': 'https://www.mos.ru/pgu/ru/application/dogm/journal/'}
    auth_url = "https://mrko.mos.ru/dnevnik/services/index.php"
    auth_req = session.get(auth_url, headers=headers, params={"login": ЛОГИН, "password": ПАРОЛЬ_В_MD5}, allow_redirects=False)

Сразу хочу обратить внимание на заголовок Referer. Как позже выяснилось, его необходимо указывать, иначе дневник не даст нам войти, думая что мы вошли напрямую. Я нам надо замаскироваться, будто бы мы вошли с mos.ru. Теперь основной запрос к дневнику:


main_req = session.get("https://mrko.mos.ru/dnevnik/services/dnevnik.php?r=1&first=1")

Разбираем данные


Отлично. В дневник зашли. Теперь самая сложная часть — разбор (парсинг) данных. Для этой простой задачи я решил использовать BeautifulSoup, т.к. раньше имел дело с ним работать.


from bs4 import BeautifulSoup
parsed_html = BeautifulSoup(main_req.content, "lxml")

НеДолгим копанием с помощью Chrome Developer Tools в DOM-дереве дневника, вычислил div с необходимой информацией.


columns = parsed_html.body.find_all('div', 'b-diary-week__column')
final_ans = []

Теперь у нас есть массив с данными на каждый день дневника, начиная от понедельника и заканчивая субботой и пустой массив с финальными данными. Очевидно, что для обхода массива я использую цикл for:


for column in columns:

Опять же, нашел элементы с нужной мне информацией. А именно: день недели, число, домашнее задание, оценки и комментарии к урокам. Получилось примерно так.


date_number = column.find("span", "b-diary-date").text
date_word = column.find("div", "b-diary-week-head__title").find_all("span")[0].text

Теперь записываем данные о дате и перебираем каждую "ячейку" в таблице


lessons_table = column.find("div", "b-diary-lessons_table")
all_lists = lessons_table.find_all("div", "b-dl-table__list")
for lesson in all_lists:
    lesson_columns = lesson.find_all("div", "b-dl-td_column")
    lesson_number = lesson_columns[0].span.text
    lesson_name = lesson_columns[1].span.text
    # Если название урока пусто, пропускаем
    if lesson_name == "":
        pass
    else:
        lesson_dz = lesson_columns[2].find("div", "b-dl-td-hw-section").span.text
        lesson_mark = lesson_columns[3].span.text[0:1]
        lesson_comment = lesson_columns[4].find("div", "b-dl-td-hw-comments").span.text
        final_ans.append(
                                "<b>{0}. {1}</b>. Домашнее задание:\n"
                                "<i>{2}</i>\n"
                                "Оценка за урок: <i>{3}</i>\n\n".format(lesson_number,
                                                                        lesson_name,
                                                                        lesson_dz,
                                                                        lesson_mark))
final_ans.append("\n-------------------\n\n")

В итоге у нас получился парсер, который может выдавать примерно это:


Результат

Ну, на этом все. Спасибо за прочтение. Надеюсь я сэкономил вам много времени. Следующую статью напишу про интеграцию этого парсера с Телеграм-ботом.


Ссылки


Поделиться публикацией
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 15
  • 0
    А зачем записи без оценок? =)
    • 0
      — Сын, ты уроки сделал?
      — а нам не задавали… (звуки стрельбы из наушников)
      — как не задавали, смотри сюда, у меня в телеграмме все записано
      • 0
        Не знаю, видели ли Вы дневник, но там есть список уроков и не по всем задают домашнее задание, а значит, и оценку не поставить — там прочерк. Дневник же весь парсится.
        • 0
          А, в самом посте об этом не было, не подумал про домашку =)

          Меня просто никто не проверял, делаю я её или нет, вот и в голову даже не пришло.
      • 0
        А ссылку на бота ?)
        • 0
          А бот пока не готов)
          • 0
            А вот уже готов. Прошу любить и жаловать: https://t.me/mrkorobot
        • 0
          Так как у дневника нет нормального решения по оповещению родителей, а раздавать пароль от ГосУслуг (по которому настроен вход) не правильно, придётся каждому желающему удобно работать с этим «чудом» писать свой «велосипед».

          Для себя добавил бы следующее:
          1. Косметически допилить (убрать информационный шум — информацию с прочерками).
          Можно попробовать разделить отметки и ДЗ, потому как отметки нужны на сегодня (и пару дней назад), а ДЗ на завтра (и на пару дней вперёд).
          2. Периодичность «прочёсывания» дневника.
          У нас часто, что принести на труд завтра любят написать только днём сегодня. Очень удобно.

          PS Спасибо за наводку.
          • 0
            Согласен. По оформлению много чего можно сделать, но я сконцентрировался на тех. части. Оформление — дело каждого:)
          • +1

            Не работает больше такой способ авторизации, ну по крайней мере у меня. Ибо теперь у него 3 url, с первого он получается свой определённый токен, потом получает ещё какие-то данные, и только потом обращается к МРКО, с этим идиотским токеном и какими-то данными.


            Да, в прошлом году такой способ аутентификации у них прокатывал, я тогда сделал экстеншон для хрома, который тупо со страницы мос.ру посылал форму на этот дурацкий МРКО. Из-за усложнения в этом году операции аутентификации я это дело забросил.

            • 0
              Круть.
              У меня почему-то реферер не сработал и пришлось писать обработку всей авторизации с mos.ru
              С получением всяких токенов и идентификаторов сессий.
              Причем надавно на mos.ru перешли на oauth 2.0 и пришлось переделывать.

              Телеграм 2 года назад был мне малоизвестен, так что я все это дела повесил на малину ( Raspberry PI ) к которой подцеплен GSM-модем. Настроил систему уведомлений для меня, жены и 2-их детей )
              Идут SMS и почта на все настроенные адреса.
              SMS-ки нужны были т.к у детей телефоны были типа бабушкофон.
              Есть сайтик на Flask который показывает домашку и статистику с рейтингом оценок детей.
              В планах написать систему монетной мотивации оценок.
              • 0
                Все должно уже работать, поправил авторизацию.
              • 0
                Вот оно, тоталитарное общество.)
                • –1
                  Задание на дом — мало вероятно, что реальное…
                  • 0
                    Бот для питерского дневника, с полным функционалом не заморачивался, только оценки высылает

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