Python imap search all

Читаем почту mail.ru из python при помощи imap

Подробно разбираем работу библиотек imaplib и email, открываем ящик и читаем письма (получаем из них всё что есть) на примере mail.ru (хотя в целом, должно работать везде).

Рабочие задачи заставили обратиться к классике — электронной почте, материала довольно много в сети, но подробного развернутого изложения не хватило, делюсь результатами изысканий, кто не сталкивался ещё с этой задачей, надеюсь, будет полезно.

Python imap search all

Приступаем:

Нам понадобятся библиотеки:

import imaplib import email from email.header import decode_header import base64 from bs4 import BeautifulSoup import re

Перед началом, из своего аккаунта на mail.ru нужно создать пароль для доступа к ящику. Для этого нужно зайти в настройки выбрать «Все настройки безопасности» и в «Способах входа» выбрать «Пароли для внешних приложений», создаёте пароль.

Предварительная подготовка окончена, начинаем писать.

Соединение и аутентификация:

Imap сервер мэйл ру расположен по адресу imap.mail.ru. Логин будет адрес вашего ящика, пароль, мы только что создали.

mail_pass = "пароль от ящика для внешних приложений" username = "адрес_ящика_на@mail.ru" imap_server = "imap.mail.ru" imap = imaplib.IMAP4_SSL(imap_server) imap.login(username, mail_pass)

Если всё прошло хорошо выйдет сообщение: [b’Authentication successful’]

Мы зашли в почтовый ящик. Чтобы добраться до писем нужно зайти в папку с ними, по умолчанию входящие это папка INBOX

Посмотреть список всех папок можно командой imap.list()

Получение писем

Чтобы добраться до письма нужно буквально открыть папку и в неё зайти, выполняется так:

Возвращает примерно такой кортеж (‘OK’, [b’19’]), первое, это статус операции, второе — количество писем в папке.

Теперь нужно узнать номер письма, а их как минимум два – порядковый номер в папке и UID, который тоже привязан к порядку номеров в папке, но уже не изменяется (ещё есть Message-ID).

Метод search библиотеки imaplib возвращает порядковый номер писем в ящике от первого до последнего.

Письма расположены в ящике по порядку номеров. Если выполнить поиск без каких-либо параметров, получим список номеров писем.

imap.search(None, 'ALL') >>('OK', [b'1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19'])

Можно убедиться, что писем действительно 19.

Поиск можно осуществлять и более предметно: аргумент «UNSEEN» вернёт, все номера непросмотренных писем:

Читайте также:  Java xsd create class

В ответ получим порядковые номера непрочитанных писем примерно такой: (‘OK’, [b’12 16 19′]) первый аргумент — это статус операции, далее идёт список битов с номерами писем.

Стоит иметь ввиду, что, если удалить какое-то письмо, то все номера сдвинутся. Т.е. для задачи чтения новых писем этот момент не принципиальный, а вот если нужно будет возвращаться к сообщениям, можно получить их UID, неизменяемый номер. Для этого нужно использовать метод IMAP4.uid(command, arg[, . ])

Для тех же самых писем мы получим уже другие номера: (‘OK’, [b’14 24 28′]), т.е. порядковый номер письма 12, а uid — 14, 16 — 24 и 19 — 28. С uid уже можно осуществлять более сложные операции — хранить, обращаться, они будет именно за теми письмами в которых вы их получите.

Получаем письмо и извлекаем часть информации о нём

Python imap search all

Зная номер письма теперь его можно наконец-то получить.

res, msg = imap.fetch(b'19', '(RFC822)') #Для метода search по порядковому номеру письма res, msg = imap.uid('fetch', b'28', '(RFC822)') #Для метода uid

Номер надо передавать строчкой str(num) или байтами, инты, не пройдут.

После этой операции в почтовом ящике письмо будет отмечено как прочитанное.

В ответ получим кортеж байтов, в первом будет содержаться порядковый номер, стандарт и ещё какое-то число.

Во втором слоте кортежа, будет наш будущий объект email. Извлекаем письмо при помощи метода message_from_bytes библиотеки email :

msg = email.message_from_bytes(msg[0][1])

Тип объекта msg будет email.message.Message. Непосредственно из него, не заглядывая внутрь можно извлечь почти всё кроме текста письма и вложений (текст иногда тоже можно извлечь).

letter_date = email.utils.parsedate_tz(msg[«Date»]) # дата получения, приходит в виде строки, дальше надо её парсить в формат datetime letter_id = msg[«Message-ID»] #айди письма letter_from = msg[«Return-path»] # e-mail отправителя print(type(letter_date), type(letter_id), letter_id, type(letter_from))

Получение From и Subject, первые сложности

From и Subject запрашиваются также msg[«From»], сложности с ответом. Если они полностью написаны латиницей, то извлекаются они также как и предыдущие. Но есть ещё вариант если письмо пришло без темы или поля «От» или «Тема» написаны кириллицей.

msg["Subject"] # тема письма написана кириллицей и закодирована в base64 '=?UTF-8?B?RndkOiDQn9GA0LjQs9C70LDRiNC10L3QuNC1INCyINC90L7QstGL0Lkg0KI=?=\r\n =?UTF-8?B?0LXRhdC90L7Qv9Cw0YDQuiDQsiDRgdGE0LXRgNC1INCy0YvRgdC+0LrQuNGF?=\r\n =?UTF-8?B?INGC0LXRhdC90L7Qu9C+0LPQuNC5IMKr0JjQoi3Qv9Cw0YDQusK7INC40Lw=?=\r\n =?UTF-8?B?0LXQvdC4INCRLtCg0LDQvNC10LXQstCwINC4INCc0LXQttC00YPQvdCw0YA=?=\r\n =?UTF-8?B?0L7QtNC90YvQuSBTdGFydHVwIEh1Yg==?='

Это кодировка MIME + Base64, можно расшифровать вручную, нужный текст находится между символов =? и ?= и дальше base64.b64decode().decode().Или можно воспользоваться методом decode_header, который импортируем из email.header:

decode_header(msg["Subject"]) [(b'Fwd: \xd0\x9f\xd1\x80\xd0\xb8\xd0\xb3\xd0\xbb\xd0\xb0\xd1\x88\xd0\xb5\xd0\xbd\xd0\xb8\xd0\xb5 \xd0\xb2 \xd0\xbd\xd0\xbe\xd0\xb2\xd1\x8b\xd0\xb9 \xd0\xa2\xd0\xb5\xd1\x85\xd0\xbd\xd0\xbe\xd0\xbf\xd0\xb0\xd1\x80\xd0\xba \xd0\xb2 \xd1\x81\xd1\x84\xd0\xb5\xd1\x80\xd0\xb5 \xd0\xb2\xd1\x8b\xd1\x81\xd0\xbe\xd0\xba\xd0\xb8\xd1\x85 \xd1\x82\xd0\xb5\xd1\x85\xd0\xbd\xd0\xbe\xd0\xbb\xd0\xbe\xd0\xb3\xd0\xb8\xd0\xb9 \xc2\xab\xd0\x98\xd0\xa2-\xd0\xbf\xd0\xb0\xd1\x80\xd0\xba\xc2\xbb \xd0\xb8\xd0\xbc\xd0\xb5\xd0\xbd\xd0\xb8 \xd0\x91.\xd0\xa0\xd0\xb0\xd0\xbc\xd0\xb5\xd0\xb5\xd0\xb2\xd0\xb0 \xd0\xb8 \xd0\x9c\xd0\xb5\xd0\xb6\xd0\xb4\xd1\x83\xd0\xbd\xd0\xb0\xd1\x80\xd0\xbe\xd0\xb4\xd0\xbd\xd1\x8b\xd0\xb9 Startup Hub', 'utf-8')]

Он также возвращает кортеж значений, нам нужно нулевое, собственно «Тема» письма, она уже декодирована в экранированные последовательности Unicode, осталось перевести символы в читаемый текст:

decode_header(msg["Subject"])[0][0].decode() 'Fwd: Приглашение в новый Технопарк в сфере высоких технологий «ИТ-парк» имени Б.Рамеева и Международный Startup Hub'

Если темы письма нет, msg[«Subject»] вернет NoneType.

Читайте также:  Char array from string python

Всё это можно проделать и несколькими другими способами, например, получить тему письма можно так:

imap.fetch(b'19', "(BODY[HEADER.FIELDS (Subject)])")

Но, как по мне, вариант, который приведен до этого, более понятен.

Всё что можно было получить не погружаясь дальше мы получили, переходим к тексту и вложениям.

Наконец-то текст письма! А, нет ещё придётся повозиться

Python imap search all

Чтобы продолжить нам нужно получить из объекта email.message.Message его полезную нагрузку, методом msg.get_payload().

И как нам любезно сообщает документация к библиотеке email результат может быть:

  1. простым текстовым сообщением,
  2. двоичным объектом,
  3. структурированной последовательностью подсообщений, каждое из которых имеет собственный набор заголовков и собственный payload.

Чтобы сразу разобраться с этим вопросом применяется метод .is_multipart(), который собственно и подсказывает, как дальше быть с письмом. Т.е. сразу определить третий вариант, который является самой настоящей матрёшкой.

Пойдём по-порядку. Если мы получили простое текстовое сообщение. Нет, оно конечно-же никакое не простое, а закодированное в base64, но тут вроде всё просто, берем и декодируем и… можем получить, например, HTML-код, который уже почти читается, но тоже его лучше почистить (поэтому и BeautifulSoup в библиотеках).

Двоичный объект переводим в текстовый, и тут делаем тоже что и в первом случае.

is_multipart() == True или структурированная последовательность подсообщений

Если полученный объект состоит из группы других объектов начинаем итерировать. Проход по частям можно сделать простым циклом:

payload=msg.get_payload() for part in payload: print(part.get_content_type()) multipart/alternative application/pdf

Но тут есть подвох, полученные части тоже могут составными, т.е. циклы нужно усложнять. Сильно упрощает этот вопрос метод walk

for part in msg.walk(): print(part.get_content_type()) multipart/alternative text/plain text/html application/pdf

Встречаются письма, в которых некоторые из этих составных частей тоже составные. Приведенный выше код из документации иллюстрирует именно такой случай.

Обычный проход дал результат по двум объектам, которые собственно и вернулись в результате выполнения .get_payload(), а .walk() дает четыре объекта, дело в том, что она распаковывает составные части вложений. Если тоже самое исполнить стандартным способом получиться примерной такой код:

payload=msg.get_payload() for part in payload: print(part.get_content_type()) if part.is_multipart(): level=part.get_payload() for l_part in level: print(l_part.get_content_type())

Каждый объект email снабжён заголовками, которые расскажут о богатом внутреннем мире объекта и соответственно подскажут средства его извлечения. Обычно в первой части payload хранится текст письма, а в остальных вложения. Но это не обязательно, может быть тип multipart и без вложений.

Читайте также:  Failed to open stream invalid argument in php

Типы делятся на одиночные (discrete-type) и составные (composite-type) к одиночным (это наш пункт назначения) могут относится: «text» / «image» / «audio» / «video» / «application» / extension-token, к составным: «message» / «multipart» / extension-token (RFC 2045), это надо разворачивать.

Нас интересует текст, поэтому проходимся по payload с условием part.get_content_maintype() == ‘text’

for part in msg.walk(): if part.get_content_maintype() == 'text' and part.get_content_subtype() == 'plain': print(base64.b64decode(part.get_payload()).decode())

Подтип бывает нескольких видов, plain и html (других пока не видел). В сообщении может встречаться как один из них, так и оба, поэтому отбираем нужный вариант при помощи условий.

В случае если подтип это html, возвращаемся к bs4, или регам (это после декодирования из base64).

Текст получен, можно переходить к вложениям.

payload.get_content_disposition() == ‘attachment’

Вложения ловятся в частях письма также как и текст, по условию get_content_disposition() == ‘attachment’.

get_content_type() сообщит нам тип вложения (/ «image» / «audio» / «video» / «application» /) и более конкретную разновидность его, например application/pdf. Также в заголовке содержится и название файла. Если название не латиницей, то добро пожаловать в MIME + Base64.

for part in msg.walk(): print(part.get_content_disposition() == 'attachment') False False False False True

Результат для четырёх частей приведенного выше письма. С именами файлов вложений тоже придётся повозиться

for part in msg.walk(): if part.get_content_disposition() == 'attachment': print(part.get_filename()) print(base64.b64decode('=0L/QtdGC0LDQvdC6LnBkZg==').decode()) print(decode_header(part.get_filename())[0][0].decode()) =?UTF-8?B?0L/QtdGC0LDQvdC6LnBkZg==?= петанк.pdf петанк.pdf

Сами файлы вложений тоже тут, лежат зашифрованные никого не трогают.

Python imap search all

for part in msg.walk(): if part.get_content_disposition() == ‘attachment’: print(part) Content-Type: application/pdf; name=»=?UTF-8?B?0L/QtdGC0LDQvdC6LnBkZg==?=» Content-Disposition: attachment; filename=»=?UTF-8?B?0L/QtdGC0LDQvdC6LnBkZg==? full-width «> «Робот читает электронные письма», сгенерировано Kandinsky Sber AI, Sber Devices

Вместо заключения

Зачем же это всё нужно, тем более не затронуты многие аспекты и вопросы. Очень просто, всё это можно завернуть в простенький бот и со спокойной душой удалить почтовое приложение с телефона и читать новые письма прямо в мессенджере.

Если про более практичное применение, например, автоматизация подтверждения прочтения и запуск счётчиков дедлайнов для ответов, и не только и многое что другое, конечно же начинается с получения доступа к ящику.

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

Библиография:

Источник

Оцените статью