- PVSM.RU - https://www.pvsm.ru -

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

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

Пишем парсер

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

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

Результат

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

Ссылки

Автор: MonsterAndrew

Источник [6]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/249364

Ссылки в тексте:

[1] МРКО: https://mrko.mos.ru/dnevnik/

[2] Портал MOS.RU: http://mos.ru

[3] МРКО: http://mrko.mos.ru/dnevnik

[4] Requests: http://docs.python-requests.org/en/master/

[5] BeautifulSoup: https://www.crummy.com/software/BeautifulSoup/bs4/doc/

[6] Источник: https://habrahabr.ru/post/323856/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best