Как автоматизировать вход в систему с помощью Selenium в Python?

Автоматизация процесса входа на какой-либо веб-сайт, используя программу, написанную на Python, очень удобна и практична в эксплуатации. Реализовывается это благодаря библиотеке Selenium WebDriver. Selenium WebDriver — это библиотека для управления браузером, которая поддерживает все основные браузеры и доступна для разных языков программирования, включая Python. И в данной работе я бы хотел использовать ее для автоматизированного входа на GitHub.

Первым делом, что нам необходимо сделать – это установить Selenium для Python, использую команду: pip3 install selenium. После этого, понадобится установить специфичный драйвер для браузера, которым мы хотим управлять. Лично я, буду использовать ChromeDriver, но вы можете использовать любой другой. Далее, необходимо открыть новый скрипт Python, инициализировать WebDriver и ввести свои учетные данные от GitHub:

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait

# Github credentials
username = "username"
password = "password"

# initialize the Chrome driver
driver = webdriver.Chrome("chromedriver")

После того, как мы загрузили и разархивировали драйвер, необходимо будет его поместить в текущий каталог, то есть, скачанный мною chromedriver.exe я помещаю в папку с проектом. Именно поэтому, я просто передаю его имя конструктору.

Поскольку мы заинтересованы в автоматизации входа в систему GitHub, то перейдем на страницу входа в Github и посмотрим на неё через кнопку f12 для того, чтобы найти её HTML элементы, а именно: идентификатор полей ввода логина и пароля, а также имя кнопки входа, необходимые для получения этих элементов в коде и их запрограммирования. Обратите внимание, что поле ввода имени пользователя/адреса электронной почты имеет id поля login_field, где поле ввода пароля имеет id password. Также кнопка отправки имеет имя commit. И приведенный ниже код переходит на страницу входа в Github, извлекает эти элементы, заполняет учетные данные и нажимает кнопку:

# перейти на страницу входа в github
driver.get("https://github.com/login")

# найти поле имени пользователя / электронной почты и отправить само имя пользователя в поле ввода
driver.find_element_by_id("login_field").send_keys(username)

# найти поле ввода пароля и также вставить пароль
driver.find_element_by_id("password").send_keys(password)

# нажмите кнопку входа в систему
driver.find_element_by_name("commit").click()

В данном коде, функция find_element_by_id() извлекает HTML элемент по его идентификатору, а метод send_keys() имитирует нажатие клавиш. Приведенный выше код заставит Chrome ввести электронное письмо и пароль, а затем нажать кнопку «Sign in».

Следующее, что нужно сделать, это определить, был ли наш вход в систему успешным. Для этого есть масса способов, но здесь мы попытаемся обнаружить ошибку при входе в систему. С помощью все той же консоли, которая вызывается клавишей f12, мы увидим, что при непрвильном вводе учетных данных, появляется новый элемент div HTML с классом «flash-error», который имеет текст «Incorrect username or password» («Неправильное имя пользователя или пароль»). Приведенный ниже код отвечает за ожидание загрузки страницы после входа в систему с помощью WebDriverWait() и проверяет наличие ошибки:

# ждем завершения состояния готовности
WebDriverWait(driver=driver, timeout=10).until(
    lambda x: x.execute_script("return document.readyState === 'complete'")
)
error_message = "Incorrect username or password."

# получаем ошибки (если есть)
errors = driver.find_elements_by_class_name("flash-error")

# при необходимости распечатать ошибки
# для e в ошибках:
#     print(e.text)
# если мы находим это сообщение об ошибке в составе error, значит вход не выполнен
if any(error_message in e.text for e in errors):
    print("[!] Login failed")
else:
    print("[+] Login successful")

Тут мы используем WebDriverWait, чтобы дождаться завершения загрузки документа, метод execute_script() выполняет Javascript в браузере, код JS возвращает document.readyState === 'complete' возвращает True, если всё хорошо и False в противном случае.

В результате всех этих действий мы получаем очень удобную программу, которая упрощает нам жизнь. Например, мы написали какую-либо программу на Python и хотим ее выгрузить на наш GitHub. И раньше, чтобы это сделать, необходимо было заходить в браузер, переходить на сайт, вводить свои учетные данные и после этого уже загружать свой новый проект в репозиторий. Теперь же, достаточно просто запустить эту программу, и она все основные действия сделает за вас, вам останется лишь загрузить свой проект.

Работа с EMail

Как читать электронные письма в Python?

В ходе моего учебного процесса на паре было опробовано приложение, которое позволяло читать мои электронные письма, их удалять, писать и отправлять кому-либо от моего имени, как единичным письмом, так и рассылкой. Все это было реализовано с помощью встроенного модуля imaplib. Что вообще такое IMAP? IMAP — это стандартный протокол Интернета, который используется почтовыми клиентами для получения сообщений электронной почты с почтового сервера.
Начать я бы хотел с чтения отправленных мне писем. Для этого, нам не нужно ничего устанавливать, все модули, которые используются в этом руководстве, являются встроенными:

import imaplib
import email
from email.header import decode_header
import webbrowser
import os

# учетные данные
username = "youremailaddress @ provider.com"
password = "yourpassword"

def clean(text):
    # чистый текст для создания папки
    return "".join(c if c.isalnum() else "_" for c in text)

В данном блоке, мы импортировали необходимые модули, а затем указали учетные данные нашей электронной почты. Позже нам понадобится функция clean(), чтобы создавать папки без пробелов и специальных символов. Далее, мы подключаемся к серверу IMAP и так как я тестирую учетную запись gmail, то я использую сервер imap.gmail.com (для другой почты, необходим, соответственно, другой сервер):

# create an IMAP4 class with SSL 
imap = imaplib.IMAP4_SSL("imap.gmail.com")
# authenticate
imap.login(username, password)

Далее, необходимо уже начинать получать электронные письма:

status, messages = imap.select("INBOX")
# количество популярных писем для получения
N = 3

# общее количество писем
messages = int(messages[0])

В данном блоке мы использовали метод imap.select(), который выбирает почтовый ящик (входящие, спам и т. Д.) и выбрали папку INBOX. Переменная messages содержит общее количество сообщений в этой папке (папке «Входящие»), а статус — это просто сообщение, которое указывает, успешно ли мы получили сообщение. Затем мы преобразовали сообщения в целое число, чтобы создать цикл for. Переменная N — это количество основных сообщений электронной почты, которые вы хотите получить, сейчас я буду использовать 3, давайте переберем каждое сообщение электронной почты, извлечем все, что нам нужно, и закончим наш код:

for i in range(messages, messages-N, -1):
    # fetch the email message by ID
    res, msg = imap.fetch(str(i), "(RFC822)")
    for response in msg:
        if isinstance(response, tuple):
            # parse a bytes email into a message object
            msg = email.message_from_bytes(response[1])
            # decode the email subject
            subject, encoding = decode_header(msg["Subject"])[0]
            if isinstance(subject, bytes):
                # if it's a bytes, decode to str
                subject = subject.decode(encoding)
            # decode email sender
            From, encoding = decode_header(msg.get("From"))[0]
            if isinstance(From, bytes):
                From = From.decode(encoding)
            print("Subject:", subject)
            print("From:", From)
            # if the email message is multipart
            if msg.is_multipart():
                # iterate over email parts
                for part in msg.walk():
                    # extract content type of email
                    content_type = part.get_content_type()
                    content_disposition = str(part.get("Content-Disposition"))
                    try:
                        # get the email body
                        body = part.get_payload(decode=True).decode()
                    except:
                        pass
                    if content_type == "text/plain" and "attachment" not in content_disposition:
                        # print text/plain emails and skip attachments
                        print(body)
                    elif "attachment" in content_disposition:
                        # download attachment
                        filename = part.get_filename()
                        if filename:
                            folder_name = clean(subject)
                            if not os.path.isdir(folder_name):
                                # make a folder for this email (named after the subject)
                                os.mkdir(folder_name)
                            filepath = os.path.join(folder_name, filename)
                            # download attachment and save it
                            open(filepath, "wb").write(part.get_payload(decode=True))
            else:
                # extract content type of email
                content_type = msg.get_content_type()
                # get the email body
                body = msg.get_payload(decode=True).decode()
                if content_type == "text/plain":
                    # print only text email parts
                    print(body)
            if content_type == "text/html":
                # if it's HTML, create a new HTML file and open it in browser
                folder_name = clean(subject)
                if not os.path.isdir(folder_name):
                    # make a folder for this email (named after the subject)
                    os.mkdir(folder_name)
                filename = "index.html"
                filepath = os.path.join(folder_name, filename)
                # write the file
                open(filepath, "w").write(body)
                # open in the default browser
                webbrowser.open(filepath)
            print("="*100)
# close the connection and logout
imap.close()
imap.logout()

Во-первых, range(messages, messages-N, -1), означает переход сверху вниз, то есть самые новые сообщения электронной почты имеют наивысший идентификационный номер, а первое сообщение электронной почты имеет идентификатор 1. Во-вторых, мы использовали метод imap.fetch(), который извлекает сообщение электронной почты по идентификатору, используя стандартный формат, указанный в RFC 822. После этого, мы анализируем байты, возвращаемые методом fetch(), на правильный объект Message и используем функцию decode_header() из email.header для декодирования темы электронного адреса в удобочитаемый юникод. После того, как мы напечатали отправителя электронной почты и тему, мы хотим извлечь тело сообщения. Мы проверяем, является ли электронное сообщение составным, а это означает, что оно состоит из нескольких частей. Например, сообщение электронной почты может содержать содержимое text/html и части text/plain, что означает, что у него есть HTML-версия и текстовая версия сообщения. Он также может содержать вложения файлов, мы обнаруживаем это по заголовку Content-Disposition, поэтому мы загружаем его в новую папку, созданную для каждого сообщения электронной почты, названного в честь темы. Объект msg, который является объектом сообщения модуля электронной почты, имеет много других полей для извлечения, в этом примере мы использовали только From и Subject, напишите msg.keys() и посмотрите доступные поля для извлечения, вы можете, например, получить дату отправки сообщения с помощью msg ["Date"]. Если же вам ничего не отправили, то он будет выводить «Писем нет».

Как удалить электронные письма в Python?

Чтобы автоматический удалять электронные письма в Python, мы все также используем встроенный модуль imaplib и начинаем с импорта необходимых модулей и указания учетных данных нашей учетной записи:

import imaplib
import email
from email.header import decode_header

# учетные данные
username = "youremailaddress @provider.com"
password = "yourpassword"

После этого, мы подключаемся к IMAP-серверу почтового провайдера:

# create an IMAP4 class with SSL 
imap = imaplib.IMAP4_SSL("imap.gmail.com")
# authenticate
imap.login(username, password)

Далее, когда мы вошли в нашу учетную запись электронной почты, давайте выберем наш целевой почтовый ящик:

# select the mailbox I want to delete in
# if you want SPAM, use imap.select("SPAM") instead
imap.select("INBOX")

Итак, мы используем выбор почтового ящика с помощью метода imap.select(), после чего я выбрал папку INBOX. Теперь давайте выполним поиск писем, которые мы хотим удалить, по дате:

# для получения писем после определенной даты
status, messages = imap.search(None, 'SINCE "01-JAN-2020"')

status содержит строку, указывающую, был ли поиск успешно выполнен, messages возвращаются как список строки [[ASCII] почтовых идентификаторов, разделенных пробелом, преобразуем его в список целых чисел:

# преобразовать сообщения в список адресов электронной почты
messages = messages[0].split(b' ')
Теперь давайте переберем выбранные письма и отметим их как удаленные:
for mail in messages:
    _, msg = imap.fetch(mail, "(RFC822)")
    # вы можете удалить цикл for для повышения производительности, если у вас длинный список писем
    # потому что он предназначен только для печати SUBJECT целевого электронного письма, которое нужно удалить
    for response in msg:
        if isinstance(response, tuple):
            msg = email.message_from_bytes(response[1])
            # расшифровать тему письма
            subject = decode_header(msg["Subject"])[0][0]
            if isinstance(subject, bytes):
                # if it's a bytes type, decode to str
                subject = subject.decode()
            print("Deleting", subject)
    # отметить письмо как удаленное
    imap.store(mail, "+FLAGS", "\\Deleted")

Наконец, мы выполняем метод imap.expunge(), который навсегда удаляет сообщения, помеченные как удаленные, так же закрываем почтовый ящик и выходим из аккаунта:

# навсегда удалить письма, помеченные как удаленные
# из выбранного почтового ящика (в данном случае INBOX)
imap.expunge()

# закрыть почтовый ящик
imap.close()

# выйти из аккаунта
imap.logout()

Как отправлять электронные письма с Python?

В данном разделе мы будем использовать сразу несколько модулей, а именно: smtplib, email, предназначенный для отправки различных типов вложений, таких как HTML-контент и двоичные файлы, а также библиотека BeautifulSoup (которую нужно будет заранее установить), позволяющая автоматически извлекать простой текст из HTML, не беспокоясь о регулярных выражениях.
Ну и как обычно, начинаем мы с импорта всех библиотек:

import smtplib
from email import encoders
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from bs4 import BeautifulSoup as bs

Далее, мы определяем наши параметры:

# ваши учетные данные
email = "email @ example.com"
password = "password"

# электронная почта отправителя
FROM = "from @example.com"

# адрес электронной почты получателя
TO   = "to @example.com"

# тема письма (тема)
subject = "Just a subject"

Переменные email и password — это учетные данные адреса электронной почты, который вы хотите отправить, FROM и TO — это адрес электронной почты отправителя и адрес получателя соответственно (адрес электронной почты и FROM обычно совпадают), а subject — это заголовок (или тема) письма, которое мы отправим.
Убедившись в том, что значения заданы правильно, вы можете отправить сообщения на несколько адресов электронной почты, просто используя их список переменной TO, а не единственный адрес.
Кроме того, мы будем использовать объект MIMEMultipart для одновременной передачи «альтернативы» в качестве подтипа, чтобы объединить две версии почты в одно сообщение с двумя вариантами отображения:

# инициализируем сообщение, которое хотим отправить
msg = MIMEMultipart("alternative")

# установить адрес электронной почты отправителя
msg["From"] = FROM

# установить адрес электронной почты получателя
msg["To"] = TO

# задаем тему
msg["Subject"] = subject

Также, устанавливаем адреса электронной почты From, To и subject. Создадим тело сообщения:

# установить тело письма как HTML
html = """
This email is sent using Python!
"""
# делаем текстовую версию HTML
text = bs(html, "html.parser").text

В этом примере мы установили html как простое сообщение HTML, но вы можете прочитать его из почтового HTML шаблона, например:

# установить тело письма как HTML
html = open("mail.html").read()

# делаем текстовую версию HTML
text = bs(html, "html.parser").text

Далее, мы завершаем построение сообщения и смотрим, как оно выглядит:

text_part = MIMEText(text, "plain")
html_part = MIMEText(html, "html")

# прикрепить тело письма к почтовому сообщению
# сначала прикрепите текстовую версию
msg.attach(text_part)
msg.attach(html_part)
print(msg.as_string())

Теперь, когда у нас есть письмо, готовое к отправке, нужно создать функцию, которая принимает адреса электронной почты FROM и TO, а также фактическое сообщение, которое нужно отправить, и она отправляет нам электронное письмо:

def send_mail(email, password, FROM, TO, msg):
    # инициализировать SMTP-сервер
    server = smtplib.SMTP("smtp.gmail.com", 587)

    # подключиться к SMTP-серверу в режиме TLS (безопасный) и отправить EHLO
    server.starttls()

    # войти в учетную запись, используя учетные данные
    server.login(email, password)

    # отправить электронное письмо
    server.sendmail(FROM, TO, msg.as_string())

    # завершить сеанс SMTP
    server.quit()

Итак, сначала мы подключаемся к SMTP-серверу с помощью smtplib, в этом примере мы использовали SMTP-сервер Gmail с портом 587. Затем мы переводим соединение с SMTP-сервером в режим TLS для безопасности, и, наконец, мы входим в систему, используя данные учётной записи и завершаем сеанс после отправки нашего электронного письма. Server.sendmail() выполняет всю почтовую транзакцию. У метода есть несколько аргументов: адрес, отправляющий письмо (FROM), список адресов получателей письма (если это строка, то она будет рассматриваться как список с 1 адресом) и сообщение для отправки. Вызовем только что созданную функцию:

# отправить почту
send_mail(email, password, FROM, TO, msg)

Как итог, проделав все вышесказанные действия, вы сможете с легкостью взаимодействовать со своим Email благодаря и используя Python.