Год назад я начал писать ботов для всеми любимого Телеграма. На Питоне, конечно. И вот недавно мой сын пошёл в школу, где, как оказалось, был электронный дневник под названием МРКО. Как вы могли догадаться, самая первая мысль — сделать бота (пока для личного пользования), который смог бы присылать в Телеграм оценки, домашнее задание и комментарии. Кому интересно — прошу под кат.
Пишем парсер
Сначала, понятное дело, нужно написать парсер для самого дневника. Для тех, кто не знает, поясню. Система входа примерно такая: ученик/родитель входит на портал 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")
В итоге у нас получился парсер, который может выдавать примерно это:
Ну, на этом все. Спасибо за прочтение. Надеюсь я сэкономил вам много времени. Следующую статью напишу про интеграцию этого парсера с Телеграм-ботом.