Захотелось мне видеть в своей твитер-ленте анонсы погоды, фильмов в своем городе. Очень удобно в одном месте наблюдать за твитами людей, на которых подписан, и разные мелкие анонсы. Так и родилась идея написать твитер-бота информирующего о погоде в Виннице (Украина). Потом решил написать еще одного бота, а потом еще одного, на данный момент в армии твитер-ботов числится три курсанта :).
При написании использовал: 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
Дополнительное чтиво: