Доставка свежей прессы с помощью Python прямо в почтовый ящик

в 15:13, , рубрики: Amazon Kindle, kindle, python, regex, smtp, метки: , , ,

В данной статье будут освещены следующие возможности python'a:

  • парсинг web-страницы с помощью простого регулярного выражения;
  • скачивание файла с web-страницы;
  • отправка скаченного файла через smtp-сервер;
  • написание небольшого обобщающего скрипта.

Все это будет сопровождено работающими примерами.

Предыстория

Многие помнят стандартную киносцену из типичного утра в американском пригородном посолке: парень на велоспипеде едет и раскидывает газету. А потом добрая большая и лохматая собака приносит газету главе семейства. Но прогресс идет и газеты теперь можно получить в цифровом формате, можно даже заказать их доставку на почту. На некоторых сайтах эта возможность предоставляется за деньги, на моем любимом, но страшно далеком от мира IT Спорт-Экспрессе, эта возможность отсутствует вообще. Но есть возможность скачать pdf.
В связи с чем каждое утро приходилось заходить на сайт, там проходить по ссылку, дальше скачивать себе на компьютер, после чего отправлять на свой kindle-ящик, потом на Kindle включить wi-fi — и вот газету можно читать и в метро, и в электричке.

Простейший анализ web-страницы

Для работы с вебом используется модуль urllib2. Данный модуль позволяет получать содержимое страниц, а так же еще много чего. Но в данном случае нам хватит одной его функции: просмотра страницы.

    def download_by_link(self, link):
        content = urllib2.urlopen(link).read()   # считываем html, расположенный по адресу link
        link_on_file, filename = self.get_filename(content)  # получаем из html ссылку для скачивания
        fullname = self.get_dir_name() + filename
        if self.is_new_version(fullname):
            with open(fullname, 'w') as fd:                                
                content = urllib2.urlopen(link_on_file).read()  # скачиваем файл
                fd.write(content)                                                    
        return self.get_prepared_files(fullname)

with — это аналог using в С# и try в Java 7. Если кратко, with гарантирует, что ресурсы, захваченные в данном блоке будут освобождены, как только мы выйдем за его пределы (в случае, обычного выхода или, например, исключения).

Для того, чтобы получить ссылку на архив со свежим выпуском газеты используем модуль re — реализующий всю мощь регулярных выражений. Правда, как и в случае с urllib2 нам опять не потребуется ничего сверхъестественного:

   def get_filename(self, text):
         pattern = r'(?P<link>http://archive.sport-express.ru/pdf/(?P<filename>[0-9]+.zip))'
         match = re.search(pattern, text)
         return match.group('link'), match.group('filename')

(?P ) — так на Python'e задается группа в регулярке.
Таким образом мы получает линк и имя файла.

class SportExpress():
    def get_from(self):
        return 'username@mail.ru'

    def get_title(self):
        return 'Sport Express subscribe'

    def get_text(self):
        return "Good morning! Don't be lazy - read newspaper!"

    def get_prepared_files(self, archive):                                      
        directory_to_extract = archive[:archive.rfind('.')]               
        zipfile.ZipFile(archive).extractall(directory_to_extract)         # опасно использовать на произвольном содержимом
        res = glob.glob(directory_to_extract+'/*.*')
        return res

    def get_dir_name(self):
        dir_name = 'archive/sport-express/'
        if not os.path.exists(dir_name):
            os.mkdir(dir_name)
        return dir_name

    def is_new_version(self, filename):
        return os.path.exists(self.get_dir_name() + filename) 

Создаем письмо

Теперь, когда у нас есть содержимое, которое мы планируем высылать, неплохо было бы создать для него «конверт».
Для работы с письмами в питоне используется модуль… используется модуль… используется модуль email.

import email, email. encoders, email.mime.text, email.mime.base

class MessageBase:
    def __init__(self, subscriber):
        self.__to_addresses = subscriber.get_subscribers()
        self.__from_address = subscriber.get_from()
        self.__message = self.__create_message(subscriber.get_title(), subscriber.get_text())

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

 def __create_message(self, subject, text):
        email_msg = email.MIMEMultipart.MIMEMultipart('mixed')
        email_msg['Subject'] = subject
        email_msg['From'] = self.get_from()
        email_msg['To'] = ', '.join(self.get_to())
        email_msg.attach(email.mime.text.MIMEText(text,'text'))
        return email_msg

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

    def attach_file(self, filename, file_link, file_type = 'unknown'):
        file_message = email.mime.base.MIMEBase('application', file_type)
        file_message.set_payload(file(file_link).read())
        email.encoders.encode_base64(file_message)
        file_message.add_header('Content-Disposition','attachment;filename='+filename)
        self.__message.attach(file_message)
        return True

Для того, чтобы получить из сообщения сообщение для отправки нужно вызвать метод as_string().

    def get_text(self):
        return self.__message.as_string()

Отправляем письмо

Теперь когда есть письмо, нужно уметь его отправлять. За отправку писем в Python'e отвечает smtplib.

import smtplib

from message import MessageBase

class SmtpBase:
    def __init__(self, serverhost):
        self.__smtp_server = serverhost

    def open(self):
        self.__server = smtplib.SMTP(self.__smtp_server)
        self.__server.login('<enter your smtp login>', '<enter your smtp password>')

    def close(self):
        self.__server.quit()

Представим что у нас есть класс Message с методами get_from(), get_to(), get_text().

    def send_mail(self, message):
        for message_to in message.get_to():
            self.__server.sendmail(message.get_from(), message_to, message.get_text())

Чтобы поддерживать 'with' необходимо добавить пару методов:

    def __enter__(self):
        self.open()
        return self

    def __exit__(self, type, value, traceback):
        if type:
            print '%s: %s %s' % (type, value, traceback)
        self.close()

Обобщаем

Теперь у нас есть всё, что нужно.
Мой итоговый скриптик выглядит так:

from subscriber_sex import SportExpress
from smtp import SmtpBase
from message import MessageBase

b = SportExpress()
filenames = b.download_by_link('http://www.sport-express.ru')

msg = MessageBase(b)
for file in filenames:
    msg.attach_file(file[file.rfind('/')+1:], file, 'pdf')

with SmtpBase('smtp.yandex.ru') as s:
    s.send_mail(msg)

После каждого запуска данного скрипта, свежий выпуск Спорт-Экспресса опускается прямо в ваш почтовый ящик! Предлагаю делиться в комментариях, какие еще издания раздают бесплатно pdf версии журналов. В принципе, можно и не pdf, главное, чтобы можно было на регулярной основе получать свежие выпуски.

А так, для того, чтобы скачивать новый журнал — вам всего лишь нужно изменить 3 функции.

Надеюсь, что материал был полезен и приятен для читателя.

Исходники.

Автор: Jimilian

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js