[Из песочницы] История о том, как я парсер для дневника мастерил


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



Пишем парсер

Сначала, понятное дело, нужно написать парсер для самого дневника. Для тех, кто не знает, поясню. Система входа примерно такая: ученик/родитель входит на портал 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})

Сразу хочу обратить внимание на заголовок 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(
                                "{0}. {1}. Домашнее задание:\n"
                                "{2}\n"
                                "Оценка за урок: {3}\n\n".format(lesson_number,
                                                                        lesson_name,
                                                                        lesson_dz,
                                                                        lesson_mark))
final_ans.append("\n-------------------\n\n")

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


Результат

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


Ссылки
  • Портал MOS.RU
  • МРКО
  • Requests
  • BeautifulSoup

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

  • 14 марта 2017 в 10:17

    0

    А зачем записи без оценок? =)

© Habrahabr.ru