Pull to refresh

Отправка важных сообщений из ВКонтакте по электронной почте

Reading time5 min
Views15K
Добрый день!
Наверняка многие сталкивались с ситуацией, что важная информация (новости, объявления и т.д.) публикуется ВКонтакте. Но во-первых, не всегда есть возможность туда попасть (вообще неприлично в рабочее время сидеть вконтакте!), во-вторых информацию приходится получать polling'ом, то есть постоянно обновляя страницу группы или что-нибудь аналогичное.
Отсюда родилась замечательная мысль — было бы удобно, чтобы важные уведомления приходили на почту. И на работе посмотреть можно и судорожно жать F5постоянно обновляться не нужно. Как оказалось, с помощью python'а можно легко справиться с такой задачей.

Попытка №1: VK API


Для начала я попробовал быть честным и использовать VK API. В сети я даже сумел отыскать парочку библиотек, которые умели логиниться и выполнять функции из API. К сожалению, ни одна из них меня не устроила, поэтому я умудрился за парочку часов соорудить свой велосипед. Ладно, дело сделано, но тут я наткнулся на неприятный момент, а именно, с помощью текущей версии API нет возможности получить сообщения со стены группы (или я не нашел как это сделать, что тоже вероятно). Остался один вариант — парсить страницы ВКонтакте самостоятельно. С одной стороны, это не очень легально, с другой стороны, такой подход дает возможность получить любую информацию, которую я могу увидеть непосредственно в браузере.

Попытка №2: парсим страницы напрямую!


Логинимся на сайт

Перво-наперво я попытался с помощью httplib и urllib получить страницу логина. Все замечательно и прекрасно. Вот только обнаружилось, что придется писать много некрасивого кода, да еще и с cookies работать… И как-то очень сильно меня это опечалило. Я начал искать замену. И нашел замечательную библиотеку mechanize, которая замечательно сделала за меня всю неинтересную работу по созданию соединений, обработке сессий и cookies и т.д…
Итак, с помощью mechanize получаем главную страницу ВКонтакте:
def initVK():
        # Browser
        br = mechanize.Browser()

        # Cookie Jar
        cj = cookielib.LWPCookieJar()
        br.set_cookiejar(cj)

        # Browser options
        br.set_handle_equiv(True)
        br.set_handle_gzip(True)
        br.set_handle_redirect(True)
        br.set_handle_referer(True)
        br.set_handle_robots(False)

        # Follows refresh 0 but not hangs on refresh > 0
        br.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(), max_time=1)

        # Little cheating...
        br.addheaders = [('User-agent', 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1')]

        br.open('http://vkontakte.ru')
        br.select_form(nr=0)
        br.form['email'] = EMAIL
        br.form['pass'] = PASSWORD
        br.submit()
        return br

В качестве пояснения скажу, что на vkontakte.ru первая форма как раз и есть форма логина. С помощью mechanize заполняем ее и вуа-ля, мы залогинились на сайт!
Получаем важные сообщения со стены

Следующий код позволит нам получить страницу группы:
def getGroupHTML(br):
        br.open('http://vkontakte.ru/OUR_GROUP')
        html = br.response().read()
        return html

Теперь непосредственно будем парсить полученный html-код, с целью отыскать в нем нужные сообщения.
Для этого нам потребуется библиотека HTMLParser. Создадим свой класс парсера, который отнаследуем от HTMLParser.
Для простоты будем искать сообщения, которые начинаются с какого-нибудь pattern'а (в своем скрипте я использовал '@year2007').
class MyHTMLParser(HTMLParser):

  def __init__(self):
    HTMLParser.__init__(self)
    self.recording = False
    self.export_tag = False
    self.message = unicode('')

  def handle_starttag(self, tag, attrs):
    if tag == 'div':
      for name, value in attrs:
        if name == 'class' and value == 'wall_text':
          self.export_tag = True
        if name == 'class' and value == 'wall_post_text':
          self.recording = True


  def handle_endtag(self, tag):
    if tag == 'div':
        if self.recording:
                self.recording = False
                year = re.compile(PATTERN)
                if year.match(self.message):
                        message_queue.append(year.sub('', self.message).strip())
                self.message = unicode('') 
        if self.export_tag:
                self.export_tag = False

  def handle_data(self, data):
    if self.recording:
        self.message += unicode(data, 'CP1251')

Весь текст приходит в кодировке CP1251, переводим его в unicode. Слои с классами wall_text отвечают за сообщения, wall_post_text — за сам текст сообщения.

Теперь код получения сообщений можно обернуть в бесконечный цикл. Чтобы не посылать сообщения на почту каждый проход, можно устроить очередь сообщений или пытаться распарсить время. Для простоты сделаем очередь.
Также стоит заметить, что в тексте могут быть и другие тэги, например, ссылки. Их тоже можно обработать, предварительно вырезав несчастный away.php. Но это уже детали.
import codecs
message_queue = []
try:
        f = codecs.open('/tmp/vk-last-message', 'r', encoding='utf-8')
        last_message = f.read()
        f.close()
        if len(last_message.strip()) == 0 :
                last_message = PATTERN
except:
        last_message = PATTERN

import time
browser = initVK()

import mymail

while True:
        #print "Getting vk.com pages"

        html = getGroupHTML(browser)
        p = MyHTMLParser()
        p.feed(html)
        #print message_queue
        msgSent = 0
        for msg in message_queue:
                if msg == last_message :
                        break
                #messageForSend = processMsg(msg)
                print msg
                mymail.sendMessage(msg)
                msgSent += 1

        if len(message_queue) > 0 and msgSent > 0 and len(last_message.strip()) > 0:
                last_message = message_queue[0]
                f = codecs.open('/tmp/vk-last-message', 'w', encoding='utf-8')
                f.write(last_message)
                f.close()
                #print "last message: " + last_message
        message_queue = []
        #print "Sleeping..."
        time.sleep(60)

Отправка сообщения на почту

Теперь раскроем тайну модуля mymail.

import smtplib
from email.mime.multipart import MIMEMultipart

from email.mime.text import MIMEText

def sendMessage(text):
        if len(text) == 0:
                print "Empty message"
                return

        fromaddr  = FROM_ADDR
        toaddrs = LIST_OF_RECEPIENTS

        #text = 'test message'  

        msg = MIMEMultipart('alternative')
        msg['Subject'] = "year2007@vkontakte"
        msg['From'] = fromaddr
        msg['To'] = toaddrs

        mime_text = MIMEText(text, 'plain', 'utf-8')
        msg.attach(mime_text)

        # Credentials (if needed)  
        username = USER 
        password = PASSWD

        # The actual mail send  
        server = smtplib.SMTP('SMTP_SERVER:SMTP_PORT')
        server.starttls()
        server.login(username,password)
        server.sendmail(fromaddr, toaddrs, msg.as_string())
        server.quit()

Простейший код отправки сообщений. Я использовал smtp-сервера Яндекса: smtp.yandex.ru:587. Список получателей можно читать из конфига или захардкодить один адрес рассылки, как было в моем случае.

Что получилось в итоге


На выходе мы имеем:
  1. важные сообщения приходят нам на почту, то есть не нужно ходить на ВКонтакте и обновлять страницу самостоятельно
  2. опыт парсинга страниц
  3. чувство удовлетворения и гордости собой

Стоит сказать, что данный код является основой, на него можно наворачивать очень много чего. Например, для любителей GUI можно сделать формочку для ввода логина и пароля. Также можно парсить сообщения по имени автора (например, отправлять все сообщения от имени группы), обрабатывать тэги внутри сообщений и прочее, прочее, прочее.
Опыт показывает, что не стоит завышать частоту обновления страницы, все равно все сообщения будут получены, а за излишнюю активность могут наказать временным баном.

Вот собственно и все. Спасибо за внимание!

UPD. Перенес из ВКонтакте в Python.
Tags:
Hubs:
+25
Comments27

Articles