
Центр непрерывного образования
факультет компьютерных наук НИУ ВШЭ
Уже много лет я преподаю машинное обучение, программирование и анализ данных. Подготовка материалов лекций и общение со студентами доставляют огромное удовольствие, а вот рассылки, оформление ведомостей занимают большое количество времени и вызывают лишь скуку. Поэтому я решила автоматизировать эту часть работы с помощью Python. Также наши сотрудники из учебного офиса тратят значительное количество времени, вручную создавая документы и рассылки. Приведенные ниже скрипты могут быть полезны не только в преподавательской, но и в разнообразной менеджерской работе.
По сути, передо мной и коллегами обычно стоят две типовыезадачи, которые я бы и хотела автоматизировать:
персонализированные рассылки
генерация документов (служебные записки, заявления)
И в рассылках, как и при генерации документов, хотелось бы автоматически подтягивать данные из большого количества excel‑таблиц, в которые собираются данные о студентах от преподавателей, ассистентов и учебного офиса.

Маргарита Бурова
Преподаватель и эксперт Центра непрерывного образования ФКН НИУ ВШЭ, руководитель Edtech-программ по DS и DA Wildberries&Russ
Автоматизированные рассылки
Здесь основной фокус стоит на том, чтобы большое количество людей получили персонализированные рассылки (в которых, к примеру, будет содержаться уникальный код доступа или еще какая‑то личная информация)
Для начала необходимо подключиться к почтовому серверу под логином и паролем отправителя. Для этого я использую следующий код:
HOST = "smtp.hse.ru"
PASSWORD = "ВашПароль" # тут записывается пароль к почте
PORT = 587
SENDER_EMAIL = # тут записывается адрес отправителя
smtpserver = smtplib.SMTP(HOST, PORT)
smtpserver.starttls()
smtpserver.login("логин отправителя", PASSWORD)
Поясню основные моменты:
HOST = "smtp.hse.ru"
Сначала мы задаем адрес SMTP‑сервера нашего вуза, Высшей школы экономики (hse.ru). Этот сервер будет использоваться для отправки электронной почты.
PORT = 587
Для порта устанавливается значение 587. Это стандартный порт для SMTP‑сервера с использованием TLS (Transport Layer Security). Он обеспечивает безопасную передачу данных.
Далее следует установка соединения с SMTP‑сервером:
smtpserver = smtplib.SMTP(HOST, PORT)
Мы создаем объект, который устанавливает соединение с указанным SMTP‑сервером по заданному хосту и порту.
И после этого инициализируется безопасное соединение:
smtpserver.starttls()
Осталось только авторизоваться:
smtpserver.login("логин", PASSWORD)
Таким образом, мы выполнили подготовительную часть, и если авторизация прошла успешно — можно переходить к рассылке. Рассылку сразу будем делать по списку почт.
Допустим, у меня есть три почтовых адреса:
mails = ['почта1@gmail.com’, 'почта2@yandex.ru’, 'почта3@gmail.com']
В реальности, конечно, их может быть сто или больше — и я подгружаю их из таблички.
Составляем непосредственно тело письма:
for mail in mails:
msg = MIMEMultipart() # Создаем сообщение
msg["From"] = SENDER_EMAIL # Добавляем адрес отправителя
msg['To'] = mail # Добавляем адрес получателя
msg["Subject"] = Header('Орг.инфо про курс', 'utf-8') # Пишем тему сообщения
text = ‘текст’
msg.attach(MIMEText(text, 'html', 'utf-8')) # Добавляем форматированный текст сообщения
smtpserver.send_message(msg) # отправляем сообщение
В начале создается новый объект MIMEMultipart, который представляет собой многочастное сообщение электронной почты. Это позволит нам добавить различные части к нашему сообщению, такие как текст, изображения и вложения. Или вообще отформатировать сообщение с помощью HTML‑разметки.
Далее добавляем основные характеристики письма: адрес отправителя (у меня он заранее определен в переменную), адрес получателя (будет каждый раз разный, мы в цикле как раз перебираем адреса) и тему сообщения.
В переменную text я записала здесь просто строку, но в реальности письма, как правило, носят персонализированный характер, поэтому сюда может добавиться информация для конкретного человека.
После добавления текста письма — оно отправляется! Таким образом, я за один раз делаю рассылку по всем студентам.
Для того, чтобы добавлять персонализированную информацию, я подтягиваю данные из таблицы.
К примеру, у меня есть табличка с данными о студентах:

В таком случае я могу запускать цикл при генерации писем, который пройдется по всем строкам и из каждой строки извлечет адрес для отправки и, к примеру, имя студента.
Можно использовать для этого pandas:
for i in range(df.shape[0]):
msg = MIMEMultipart()
msg['To'] = df['E-mail'].iloc[i] # добавляю адрес из i-ой строки
msg["Subject"] = Header('Орг.инфо про курс', 'utf-8')
text = f'Добрый день, {df["Имя"].iloc[i]}' # добавляю имя из i-ой строки
msg.attach(MIMEText(text, 'html', 'utf-8')) сообщения
smtpserver.send_message(msg)
В приведенном выше коде из персонализации я просто добавляю обращение по имени, но, разумеется, можно добавлять и какую‑то еще информацию, записанную в таблицу.
Рассылка сделана, время сэкономлено!
Заполнение документов по шаблонам
Кроме рассылки писем есть вторая задача — генерация документов по шаблонам.
Достаточно часто нужно сгенерировать какие‑либо документы или заявления по шаблону. Причемданные, как правило, подтягиваются из экселевских таблиц.
Этот код у нас используют и сотрудники учебных офисов, поэтому он максимально приближен к обычной работе с таблицами и документами.
По сути, задача такая:
Снова есть уже знакомая нам таблица с информацией о студентах:

И заявление, в которое необходимо подставить эти данные:

В этом заявлении в двойных фигурных скобках записаны переменные, которые необходимо заменять данными из таблицы.
Для решения задачи будем использовать два модуля: openpyxl и DocxTemplate.
Модуль openpyxl полезен для работы с файлами Microsoft Excel формата.xlsx и.xlsm непосредственно из Python. С его помощью вы можете программно создавать, читать, изменять и сохранять Excel‑файлы без необходимости использования самого приложения Excel. Его преимущество заключается в том, что им легко пользоваться людям, не очень глубоко знакомым с python, но при этом уверенно работающим в Excel. По сути, этот модуль позволяет работать с экселевскими файлами почти так же, как и в самом эксель. Именно поэтому я хотела бы показать его, а не pandas (в том числе, чтобы добавить разнообразия между двумя примерами)
Первоначально импортируем модуль openpyxl и открываем таблицу с данными, выбираем оттуда нужный лист:
import openpyxl
wb = openpyxl.load_workbook(filename='table_final.xlsx')
sheet = wb['Лист1']
Далее импортируем класс DocxTemplate и загружаем ранее созданный шаблон.
from docxtpl import DocxTemplate
doc = DocxTemplate("temp_final.docx")
DocxTemplate нужен, чтобы автоматически создавать документы Word на основе шаблонов с пустыми полями. Он позволяет вставлять в эти шаблоны переменные из кода Python, что упрощает создание персонализированных документов, например, отчетов или счетов. По сути, нужно сделать «рыбу» документа, обозначив места для подстановки данных. И далее уже такой шаблон можно преобразовывать в экземпляр класса DocxTemplate и работать с ним.
Далее необходимо лишь прописать цикл, который пройдет по всем строкам и в каждой заберет нужные ячейки, а потом подставит в них контекст:
for num in range(2,7):
name = sheet['A'+str(num)].value
course = sheet['B'+str(num)].value
group = sheet['C'+str(num)].value
op = sheet['D'+str(num)].value
mobile = sheet['E'+str(num)].value
mail = sheet['F'+str(num)].value
date = sheet['G'+str(num)].value.date()
context = {
'name': name,
'course': course,
'group': group,
'op': op,
'mobile': mobile,
'mail': mail,
'date' : date,
'now_date': today
}
doc.render(context)
doc.save(name+' заявление.docx')
Давайте разберем основные моменты:
name = sheet['A'+str(num)].value
В данном кусочке кода из объекта sheet (представляющего рабочий лист электронных таблиц) считывается значение из ячейки в столбце 'A' и строке num. Часть 'A'+str(num). Далее аналогичным образом из столбцов B, C, D, E и F текущей строки (num) считываются значения и присваиваются соответствующим переменным::
course — курс
group — группа
op — образовательная программа
mobile — номер мобильного телефона
mail — адрес электронной почты
date = sheet['G'+str(num)].value.date()
Здесь из столбца G текущей строки читается значение, которое предполагается датой. Метод.date() используется, чтобы получить только дату без времени.
Также здесь в текущую дату записано значение переменной today, которая задается следующим образом:
today = str(date.today())
Это нужно для подписания документа сегодняшней датой.
В конце мы обрабатываем шаблон, заменяя в нем плейсхолдеры соответствующими значениями из словаря context. т. е., если в шаблоне есть плейсхолдер {{ name }}, он будет заменен на значение переменной name. И так происходит со всеми плейсхолдерами.
doc.render(context)
После этого лишь сохраняем документ, и все! Таким образом можно сгенерировать огромное количество документов за раз. Что упрощает и автоматизирует работу.