Захотелось мне видеть в своей твитер-ленте анонсы погоды, фильмов в своем городе. Очень удобно в одном месте наблюдать за твитами людей, на которых подписан, и разные мелкие анонсы. Так и родилась идея написать твитер-бота информирующего о погоде в Виннице (Украина). Потом решил написать еще одного бота, а потом еще одного, на данный момент в армии твитер-ботов числится три курсанта :).
При написании использовал: python 2.6.4, python-twitter, elementtree, BeautifulSoup, python-bitly.
И так приступим-с.
UPDATE
01.09.2010 поменялся способ авторизации для приложений, теперь используется oauth вместо basic auth. В связи с этим все приложения которые авторизовались через basic auth перестали работать.
Подробнее про спецификацию oauth можно почитать тут.
Список используемых компонентов немного видоизменился, вместо python-twitter будем использовать oauth-python-twitter2, причем берем последнюю версию из svn, версия что отдается архивом - не работает в связи с небольшими изменениями в еще одном нужном нам компоненте - python-oauth2.
Вытягиваем последнюю версию oauth-python-twitter2:
svn checkout http://oauth-python-twitter2.googlecode.com/svn/trunk/ oauth-python-twitter2
и делаем файл oauthtwitter.py видимым через PYTHONPATH.
Ставим oauth2
pip install oauth2
Для работы с Twitter API нам понадобится четыре параметра, которые будут отвечать за oauth авторизацию: CONSUMER_KEY, CONSUMER_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET.
Значения двух первых нам даст twitter после того, как мы зарегистрируем наше приложение, для этого:
После этого twitter сгенерит CONSUMER_KEY, CONSUMER_SECRET.
Следующие два параметра нам вернет такой скрипт
from oauthtwitter import OAuthApi import pprint CONSUMER_KEY = "*****" CONSUMER_SECRET = "*****" twitter = OAuthApi(consumer_key, consumer_secret) temp_credentials = twitter.getRequestToken() print(twitter.getAuthorizationURL(temp_credentials)) oauth_verifier = raw_input('What is the PIN? ') access_token = twitter.getAccessToken(temp_credentials, oauth_verifier) print("oauth_token: " + access_token['oauth_token']) print("oauth_token_secret: " + access_token['oauth_token_secret'])
В CONSUMER_KEY, CONSUMER_SECRET вводим значения полученные на предыдущем этапе, запускаем. Скрипт сгенерит url, переходим по нему и копируем PIN который нам вежливо предлагает twitter. Вставляем PIN в скрипт, он как раз его ждет.
На выходе мы получим значения для двух последних параметров OAUTH_TOKEN и OAUTH_TOKEN_SECRET.
Все, теперь у нас есть все что надо для oauth авторизации, осталось немного подправить скрипты.
Twitter-бот показывающий погоду в г. Винница
Реализация весьма прямолинейна:
Есть одна особенность при твите: иногда Twitter-сервер отклоняет твиты по причине своей перегрузки, потому блок с отправкой твита на сервер заключен в try ... catch, иначе скрипт валится. Ветку catch поленился реализовывать, если сервер перегружен и твит был отвергнут ... такова его судьба. По моим наблюдениям за все время существования бота, около 1,5 месяца, такое случалось только один раз. После каждого твита я задал задержку на 3 секунды, на случай если Twitter-сервер перегружен.
Больше особо описывать нечего, остальные подсказки есть в комментариях по коду.
# -*- coding: utf-8 -*- # подключаем нужные библиотеки import urllib, datetime, time from elementtree.ElementTree import parse from oauthtwitter import OAuthApi # инициализируем переменные CONSUMER_KEY = '*****' CONSUMER_SECRET = '*****' OAUTH_TOKEN = '*****' OAUTH_TOKEN_SECRET = '*****' url = 'http://informer.gismeteo.ru/xml/33562_1.xml' current_day = datetime.datetime.now() forecasts = [] twitter = OAuthApi(CONSUMER_KEY, CONSUMER_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) # загружаем xml с погодой rss = parse(urllib.urlopen(URL)).getroot() # парсим xml for element in rss.findall('REPORT/TOWN/FORECAST'): # усредняем скорость ветра wind = (int(element.find('WIND').get('max')) + int(element.find('WIND').get('min')))/2 # формируем массив, в котором лежат данные о погоде forecasts.append({ 'day': int(element.get('day')), 'tod': int(element.get('tod')), 'cloudiness': int(element.find('PHENOMENA').get('cloudiness')), 'precipitation': int(element.find('PHENOMENA').get('precipitation')), 'temp_min': element.find('TEMPERATURE').get('min'), 'temp_max': element.find('TEMPERATURE').get('max'), 'wind': wind, 'pressure': "%s - %s" % (element.find('PRESSURE').get('min'), element.find('PRESSURE').get('max')), }) # приводим числовые показатели в удобочитаемый вид и постим в твитер for forecast in forecasts[::-1]: day = u'сегодня' if forecast['day'] == current_day.day else u'завтра' if forecast['tod'] == 0: tod = u'ночью' elif forecast['tod'] == 1: tod = u'утром' elif forecast['tod'] == 2: tod = u'днем' elif forecast['tod'] == 3: tod = u'вечером' else: tod = '-' if forecast['cloudiness'] == 0: cloudiness = u'ясно' elif forecast['cloudiness'] == 1: cloudiness = u'малооблачно' elif forecast['cloudiness'] == 2: cloudiness = u'облачно' elif forecast['cloudiness'] == 3: cloudiness = u'пасмурно' else: cloudiness = forecast['cloudiness'] if forecast['precipitation'] == 4: precipitation = u'дождь' elif forecast['precipitation'] == 5: precipitation = u'ливень' elif forecast['precipitation'] == 6 or forecast['precipitation'] == 7: precipitation = u'снег' elif forecast['precipitation'] == 8: precipitation = u'гроза' elif forecast['precipitation'] == 9: precipitation = u'нет данных' elif forecast['precipitation'] == 10: precipitation = u'без осадков' else: precipitation = forecast['precipitation'] # формируем строку для твита в кодировке utf-8 twit = u"%s %s: темп. %s˚С (мин), %s˚C (макс); облачность - %s; осадки - %s; ветер %s м/с; атм. давление %s мм.рт.ст." % (day, tod, forecast['temp_min'], forecast['temp_max'], cloudiness, precipitation, forecast['wind'], forecast['pressure']) twit = twit.encode('utf-8') try: twitter.UpdateStatus(twit) except ValueError: pass # делаем трех-секундный перерыв между твитами time.sleep(3)
Twitter-бот показывающий анонсы фильмов в кинотеатрах г. Винница
Код получился меньше чем у предыдущего бота, все почти так же само: инициализируем объект работающий с Twitter API; создаем функцию которая принимает url с страницей анонсов фильмов и название кинотеатра, для серфинга по html-странице использую BeautifulSoup. После того как все распарсено - твитем.
# -*- coding: utf-8 -*- # подключаем нужные библиотеки from BeautifulSoup import BeautifulSoup from oauthtwitter import OAuthApi import urllib2 # инициализируем переменные CONSUMER_KEY = '*****' CONSUMER_SECRET = '*****' OAUTH_TOKEN = '*****' OAUTH_TOKEN_SECRET = '*****' twitter = OAuthApi(CONSUMER_KEY, CONSUMER_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) def movies(url, cinema_name): """ Функция принимает url с анонсами и имя кинотатра, парсит html-код, постит анонс в твитер """ response = urllib2.urlopen(url) html = response.read().decode('cp1251').encode('utf-8') soup = BeautifulSoup(html) for tag_td in soup.find("div", id="af").findAll('td')[2:]: tag_a = tag_td.find('a') if tag_a: film_name = tag_a.string else: txt = u'%s: %s, время сеансов: %s' % (cinema_name, film_name.strip(), tag_td.string) txt = txt.encode('utf-8') try: twitter.UpdateStatus(txt) except ValueError: pass url = 'http://kino.ukr.net/cinema/vinnica/kotsyubinskogo/' movies(url, u'Коцюбинского') url = 'http://kino.ukr.net/cinema/vinnica/mir-/' movies(url, u'Мир') url = 'http://kino.ukr.net/cinema/vinnica/rodina/' movies(url, u'Родина')
Twitter-бот показывающий праздники и события на сегодня/завтра
Операции с формированием твитов очень похожи на предыдущие: запрашиваем xml-файл, парсим, твитем. Но есть одно исключение: в xml есть ссылки на страницу с подробным описанием праздника/события, а так как у нас есть ограничение в 140 символов то укорачиваем ссылки с помощью bit.ly. API key берем тут
# -*- coding: utf-8 -*- # подключаем нужные библиотеки import urllib, datetime, time from BeautifulSoup import BeautifulSoup from oauthtwitter import OAuthApi import bitly # инициализируем переменные CONSUMER_KEY = '*****' CONSUMER_SECRET = '*****' OAUTH_TOKEN = '*****' OAUTH_TOKEN_SECRET = '*****' url = 'http://www.calend.ru/img/export/today-holidays.rss' twitter = OAuthApi(CONSUMER_KEY, CONSUMER_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) bitly_api = bitly.Api(login='proft', apikey='*********') current_day = datetime.datetime.now() events = [] # запрашиваем xml-файл rss = BeautifulSoup(urllib.urlopen(url)) # перебираем все item for item in rss.findAll('item')[::-1]: title_raw = item.title.string date_end = title_raw.find(' - ') if date_end != -1: item_day = int(title_raw[0:title_raw.find(' ')]) title = title_raw.string[date_end+3:] day_word = u'сегодня' if item_day == current_day.day else u'завтра' link_raw = item.guid.string # укорачиваем url с помощью bitly link = bitly_api.shorten(link_raw[0:link_raw.find('?')]) twit = "%s %s %s" % (day_word, title, link) twit = twit.encode('utf-8') # твитем try: twitter.UpdateStatus(twit) except ValueError: pass
Дополнительное чтиво: