Thursday, September 13, 2012

Простой Twitter-бот на Python

http://habrahabr.ru/post/127237/

Простой Twitter-бот на Python

В данной статье я бы хотел поделиться опытом написания небольшого твиттер-бота на Python.

Вступление

На написание бота меня натолкнул известный многим «пичалька-бот» в Twitter, который автоматически шлет реплаи всем, кто упомянит слово «пичалька» в своем твите. Поскольку в тот момент я занимался активным изучением Python, было решено писать на нем.


Подготовка

В качестве библиотеки для работы с API твиттера я взял tweepy. Это достаточно простая и удобная библиотека; к тому же в ее репозитории в Google Code есть несколько примеров кода и хорошаядокументация. Еще она есть в репозиториях Ubuntu и Debian, поэтому ее можно легко установить командой sudo apt-get install python-tweepy.

Разработка

1. Для работы бота необходимо зарегистрировать новое приложение в Twitter. Перейдите по ссылке и заполняем все поля.

image

После этого вы получите 2 ключа для OAuth авторизации.

image

Также необходимо изменить права приложения. Вкладка «Settings», Access -> Read and Write

image

2. Теперь необходимо получить еще 2 ключа, индивидуальные для каждого пользователя. Для этого можно нажать кнопку «Create access token» на странице приложения, либо воспользоваться небольшим примером кода из документации tweepy.

image

Вариант с примером кода:

import tweepy, webbrowser

CONSUMER_KEY = 'paste your Consumer Key here'
CONSUMER_SECRET = 'paste your Consumer Secret here'

auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth_url = auth.get_authorization_url()
webbrowser.open(auth_url)
verifier = raw_input('PIN: ').strip()
auth.get_access_token(verifier)
print "ACCESS_KEY = '%s'" % auth.access_token.key
print "ACCESS_SECRET = '%s'" % auth.access_token.secret


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



3. Теперь можно перейти к коду самого бота:

#coding: utf-8
import oauth, tweepy, sys, locale, threading 
from time import localtime, strftime, sleep

replyed=['']
search_reply_words={'печалька':' каждый раз, когда вы говорите "печалька", умирают хомячки.','пичалька':' каждый раз, когда вы говорите "пичалька", умирают хомячки.'}
update_time=60 #время обновления

def Tweet(twit,id_reply):
    if len(twit)<=140 and len(twit)>0:
        api.update_status(twit,id_reply) #обновляем статус (постим твит)
        return True
    else:
        return False

def init(): #инициализируемся
    global api
    #consumer_key = ""
    #consumer_secret = ""
    #access_key=""
    #access_secret=""
    auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
    auth.set_access_token(access_key, access_secret)
    api=tweepy.API(auth)

class TwiBot(threading.Thread): 
    def __init__ (self, keyword,answer):
        self.keyword = keyword
        self.answer=answer
        threading.Thread.__init__(self)

    def run (self):
        global replyed,api
        request=api.search(self.keyword) #ищем твиты с указанным словом
        for i in request:
            if i.from_user!='thevar1able' and i.id not in replyed: # если твит не наш и мы на него еще не отвечали...
          try:
           Tweet('@'+i.from_user+self.answer,i.id) #...отвечаем
           print strftime('[%d/%m %H:%M:%S]',localtime())+' Reply to @'+i.from_user+'('+str(i.from_user_id)+')'
          except:
           print strftime('DUP [%d/%m %H:%M:%S]',localtime())+' Reply to @'+i.from_user+'('+str(i.from_user_id)+')'
          replyed.append(i.id)
        return True



init() # инициализируемся
while not False: # вечно
    for word in search_reply_words: 
        TwiBot(word, search_reply_words[word]).start() #запускаем поток с нужным словом для поиска
        print strftime('[%d/%m %H:%M:%S]',localtime())+' Updating for word "'+str(word)+'"...'
        sleep(1) 
    sleep(update_time)


Для корректной работы нужно вставить полученные ключи в переменные в коде: ключи со страницы приложения — consumer_key и consumer_secret, пользовательские ключи — access_key и access_secret. Также нужно поправить ключевые слова для поиска твитов и ответы на них в переменной search_reply_words.

Вот и все на сегодня.

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

+39
29 августа 2011 в 11:13
143

комментарии (30)



0
mktums#
Так значит счетчик хомячков ваш? :)

ЗЫ: в twitter есть еще бот, который серчит по «over 9000» :)


+1
thevar1able#

Нет, к сожалению, не мой :)


+1
Setti#



0
So1#

WHAT?! NINE THOUSAND?!


+4
maxp#
Некоторые рекомендации по коду:
— избавление от global очень положительно сказывается на понимании сути мироздания
— не плохо было бы ловить KeyboardInterrupt и останавливать запущенные треды
— если хочется написать несколько одинаковых print bla-bla… некоторые из которых находятся в except, то самое время подумать про logging


+1
thevar1able#

Приму к сведению, спасибо.


+4
seriyPS#
global в сочетании с Threading приведут вас в ад…


+1
thevar1able#

Простите, больше так не буду :( Честно :(
А как передать список replyed треду? Так, чтобы он мог вносить в него изменения, и чтобы этот список был доступен другим тредам.


+1
seriyPS#

Ну, первое что бы я сделал — это выпилил бы Threading вообще выдал каждому треду свою уникальную копию api. Т.е. сделал бы так, чтобы init() возвращал tweepy.API(auth) типа

def init():
    ....
    return tweepy.API(auth)


И сделал бы так, чтобы init вызывался в начале run() функции
def run (self):
    api=init()

Так мы избавимся от race — conditions в tweepy.

А для записи айдишников твитов, на которые мы уже отвечали нужно использовать threading.Lock ну и так и быть — глобальную переменную или переменную класса:

class TwiBot(threading.Thread):
    replyed=set() # set гарантирует уникальность и проверка in работает быстрее
    replyed_lock=threading.Lock()

    def run():
        ....
        need_reply=[] # будущий список твитов, на которые нужно ответить.
        # захватываем блокировку replyed и быстро проходим по списку твитов
        # составляя список тех, на которые нужно ответить
        with TwiBot.replyed_lock:
            for i in request: #XXX: почему request? response ведь!!!
                if i.from_user!='thevar1able' and i.id not in TwiBot.replyed:
                    need_reply.append(i)
                    TwiBot.replyed.add(i.id)
        # тут блокировка уже снята и другой поток может писать в replyed
        # а наш поток может не спеша слать ответы.
        for i in need_reply:
            ...
            Tweet('@'+i.from_user+self.answer,i.id)
            ...
        


–1
thevar1able#

А действительно, зачем тут Threading…


+1
14zy#
Блин, так обрадовался, думал что приду домой — реализую давно задуманного твиттер бота, а тут злые дадьки в комментах говорят что я попаду в ад, если буду использовать «global в сочетании с Threading»

Просто я не программист(в нормальном понимании этого слова), может у кого есть ссылка на хорошую и простую статью про написание твиттер-ботов(можно и на php)

Спасибо!


0
bobry#

Разве написание твиттер-ботов имеет какую то особую специфику? особенно аналогичных боту в статье — import tweepy и все драконы уже позади.


0
maxp#

Можете сильно не заморачиваться, для непрограммиста и этот код вполне на уровне.

Но для улучшения мыслительной кармы посмотрите сюда — www.python.org/dev/peps/pep-0020/
artifex.org/~hblanks/talks/2011/pep20_by_example.html
docs.python.org/tutorial/

Причем, это вовсе не сугубо к Питону относится, но и ко многим другим языкам.


0
maxp#

Упс, а текст тут я не умею форматировать :(


0
14zy#

Спасибо)


+6
gigimon#
В Python принято использовать конструкции вида:
if __name__ == '__main__:
main()

А вот в функции main уже делать вашу инициализацию и запуск тредов


+1
Setti#

> принято использовать конструкции

И сразу бы писали почему. Чтобы модуль можно было использовать и самостоятельно и как подключаемую библиотеку.


+4
bobry#
Интересно откуда родом вот эта идиома:

while not False:
    ...


Обычно пишут `while True` или `while 1`, а тут налицо креатив :)


+5
thevar1able#

Просто было скучно :)


0
bobry#

Видимо игнорирование string formatting и склевание строчек плюсом по той же причине, да?


–1
thevar1able#

А почему плохо склеивать строки плюсом? Они же приведены к одному типу данных.


+1
bobry#

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


+1
thevar1able#

Спасибо, приму к сведению.


–1
Setti#

мило


+1
Savvy#
давайте и я занудно попинаю
< if len(twit)<=140 and len(twit)>0:
> if 0<len(twit)<=140:
Это ж питон, и вас должно было напрячь, что вычисляете одно и то же два раза подряд


0
spacenergy#
Не могу найти запостенные скриптом твиты. Где они должны появляться?


0
spacenergy#

Сорри, нашел


0
akme#
tweepy клёвая ^_^


0
qmax#
а access_token выдаётся на вечно или протухает?


0
maxidler#

Навечно вроде

No comments:

Post a Comment