ただそれだけ
Followingのお気に入りをランダムに返信するbot
botに対してつぶやくと、フォローしてる人がお気に入りにいれているTweetをランダムに選んで返信するというtwitterのbotです。
rubyで書いてます。
Twitter gemのバージョン1.0.0を使ってます。
# -*- encoding: utf-8 -*- require 'twitter' require 'sqlite3' class Frienvorite def initialize(hash) Twitter.configure do |config| config.consumer_key = hash.fetch('c-key') config.consumer_secret = hash.fetch('c-secret') config.oauth_token = hash.fetch('o-token') config.oauth_token_secret = hash.fetch('o-token-secret') end reply_to_mentions end def choice_favorite(friend) favorites = Twitter.favorites(friend) if favorites != [] fav = favorites[rand(favorites.size)] return "RT #{fav.user.screen_name} #{fav.text}" if fav != [] end return end def choice_friend(mention_user) friends = Twitter.friends(mention_user).users if friends != [] for i in 1..10 friend = friends[rand(friends.size)].screen_name favorite_retweet = choice_favorite(friend) if favorite_retweet != nil return "@#{mention_user} IN #{friend} #{favorite_retweet}" end end end return end def reply_to_mentions Twitter.mentions.each do |mention| tweet = choice_friend(mention.user.screen_name) Twitter.update(tweet) if tweet != nil end end end Frienvorite.new({'c-key' => 'コンシューマー・キー', 'c-secret' => 'コンシューマー・シークレット', 'o-token' => 'オーオース・トークン', 'o-token-secret' => 'オーオース・トークン・シークレット'});
このままでは、mentions全てに返信するので、返信済みのmentionは無視するなどしたい場合は、時間を保持する必要があります。
作ったものはこれです。
Plaggerを使ってTwitterの複数人のFavoritesをまとめてRSS表示
Plaggerというプラガブルな RSS/Atom フィードアグリゲータを使って、Twitterの複数アカウントのFavoritesをまとめてRSSで表示する、というプログラムです。
ServersMan@VPSが2ヶ月無料(2010年11月)ということで登録しまして、何かサーバを使って面白いことは出来ないかと考えていたところ、以前から気になっていたPlaggerを試してみようとなったわけです。
ちなみに、VPSの初期設定でやるべきことはここできれいにまとめてくれています。
さて、まずはPlaggerをインストールするわけですが、これがなかなかタフな作業です。
まず、時間がかかります。
そして依存モジュールが沢山あります。
CentOSだと、CPANでゴリゴリインストールすることになると思うんですが、Plaggerがインストール出来るかテストしては、エラー見て足らないモジュール追加という作業で、僕の場合は数時間かかりました。
Plaggerのインストールは時間に余裕のあるときにすることをおすすめします。
実際に、FavoritesをまとめてRSS出力するPlaggerプログラムは以下のようになります。
favorites.yaml
global: log: level: debug plugins: - module: Subscription::Config config: feed: - url: http://twitter.com/favorites/アカウント名1.atom - url: http://twitter.com/favorites/アカウント名2.atom - module: SmartFeed::All config: title: sbmix - module: Publish::Feed rule: expression: $args->{feed}->id eq 'smartfeed:all' config: dir: /var/www/html/ filename: output.rss format: RSS
/var/www/html/ のディレクトリに出力してますが、権限ないとここには書けません。
以下のようにコマンドを打ち、
$ plagger -c favorites.yaml
アドレス先にきちんと表示されていたらOKです。
定期的に動いてもらうために、cronで動かします。
crontabは以下のように記述しました。
PERL5LIB=/usr/lib/perl5:/usr/lib/perl5/site_perl 0 * * * * /usr/bin/plagger -c /home/ユーザ名/plagger/favorites.yaml > /dev/null
あとは、RSSリーダーなどにアドレスを登録して、みんなのFavoritesを見て楽しむだけです。
まぁこんなめんどくさいことしなくても、 http://twitter.com/favorites/アカウント名.atom をRSSリーダーに登録すればRSSで表示することは出来るんですけどね。
GAEでTwitterのツイートを圧縮新聞っぽくまとめてみる(2)
GAEでTwitterのツイートを圧縮新聞っぽくまとめてみる(1)という記事を書いたのですが、その続きです。
「圧縮新聞」を作ったを見ると、どうも品詞情報などは全く使っていないようなので、Yahoo! 形態素解析サービスで分割だけして、あとのロジックはそのまま使わせていただきました。
ソースコードはこんな感じ。
# -*- coding: utf-8 -*- import os import random from urllib import urlencode from google.appengine.ext.webapp import template from google.appengine.ext import webapp from google.appengine.api import urlfetch from django.utils import simplejson from BeautifulSoup import BeautifulStoneSoup # ユーザのツイートを圧縮する class Asshukutter(webapp.RequestHandler): def get(self): # リクエストを取得 user = self.request.params['user'] # twitterアカウントが入る # 形態素解析したユーザのツイートを取得 dic = self.segmentMorpheme(user) # 圧縮結果を取得 result = self.asshuku(dic) # ちゃんとした結果が得られなかったら得られるまで while result == "。".decode('utf_8'): result = self.asshuku(dic) data = {'result' : result.rstrip("。".decode('utf_8'))} path = os.path.join(os.path.dirname(__file__), '../html/index.html') self.response.out.write(template.render(path, data)) # ツイートを圧縮する def asshuku(self, dic): result = "" i = 0 punctuation = "。".decode('utf_8') # 木構造を繋げていく preSeg = dic[punctuation][random.randint(0,len(dic[punctuation])-1)] result = result + preSeg # 「。」が出てくるまで繋げる while preSeg != punctuation: i = i + 1 nextSeg = dic[preSeg][random.randint(0,len(dic[preSeg])-1)] result = result + nextSeg preSeg = nextSeg if i == 100: preSeg = punctuation return result # Yahoo! 形態素解析サービスを使って、ユーザのツイートを形態素で分割する def segmentMorpheme(self, user): dic = {} # ユーザのツイートを取得 tweets = self.searchUserTweet(user) # ツイートごとに形態素解析を行う for t in tweets: urlResult = urlfetch.fetch('http://jlp.yahooapis.jp/MAService/V1/parse', method=urlfetch.POST, headers={'Content-Type':'application/x-www-form-urlencoded'}, payload=urlencode({ 'appid': 'アプリケーションID', 'sentence' : t.encode('utf-8')})) # BeautifulStoneSoupを使って結果のxmlをパースする soup = BeautifulStoneSoup(urlResult.content.decode('utf_8')) preMorpheme = "。".decode('utf_8') flag = False for morpheme in soup('surface'): # 英数字のみからなる文字列を除外 if not morpheme.renderContents().isalnum() and not flag: nowMorpheme = morpheme.renderContents().decode('utf_8') # 木構造を作成 if dic.has_key(preMorpheme): dic[preMorpheme].append(nowMorpheme) else: dic[preMorpheme] = [nowMorpheme] preMorpheme = nowMorpheme # 英数字直後の形態素は除外 elif not morpheme.renderContents().isalnum() and flag: flag = False else: flag = True return dic # Twitter APIのuser_timelineを使ってユーザのツイートを取得する def searchUserTweet(self, user): text = [] preText = "" num = 0 # 入力したユーザ名をクエリとするURL url = "http://api.twitter.com/1/statuses/user_timeline.json?screen_name=" + user urlresult = urlfetch.fetch(url) if urlresult.status_code == 200: # 結果をsimplejsonを使ってパース jsonload = simplejson.loads(urlresult.content) for result in jsonload: num = num + 1 if num % 7 == 0: preText = preText + result['text'] + "。".decode('utf_8') text.append(preText) preText = "" else: preText = preText + result['text'] + "。".decode('utf_8') text.append(preText) else: text.append("存在しないか非公開なアカウントです。".decode('utf_8')) return text
Twitter APIのuser_timelineを使ってユーザのツイートを取得し、それをYahoo! APIの形態素解析にかけ、それを圧縮新聞と同様にマルコフ連鎖で繋げていきます。
Yahoo!の形態素解析は、1リクエストの最大サイズが100KBなので、ツイートを3分割くらいにしてます。
マルコフ連鎖を実現するために、ある形態素をキー、その後ろにくる形態素のリストをバリューをするディクショナリを作成します。
なので、形態素解析にかけ、連続した形態素の、前の形態素をディクショナリのキー、後ろの形態素をディクショナリのバリューのリストに挿入します。
あとはランダムに繋げていきます。
圧縮新聞は4単語を繋げてますが、これは2単語を繋げていきます。
リンクの文字列が結構やっかいで英数字を無視してしまっているんですが、そのためかエラーが出ます。
まぁ出来れば整形して載せます。
一応、作ったものは → 圧縮ったー
while文動きっぱが怖いので上のソースちょっといじって運用してます。
マルコフ連鎖に品詞情報付与したら精度良くなる気がするけど、そうでもないのかな…。
GAEでTwitterのツイートを圧縮新聞っぽくまとめてみる(1)
圧縮新聞というものがあるんですが、Twitterのツイートを圧縮新聞っぽくまとめたら面白いんじゃないかと思い、いろいろやってみました。まだ途中です。
とりあえずTwitterユーザのツイートを取得します。
どうも直近から20件までしかとれないらしく、まぁとれるだけとります。
下のsearchUserTweet.pyがそれです。
# -*- coding: utf-8 -*- from google.appengine.ext import webapp from google.appengine.api import urlfetch from django.utils import simplejson # Twitter APIのuser_timelineを使ってユーザのタイムラインを取得する class SearchUserTweet(webapp.RequestHandler): def sut(self, user): text = "" # 入力したユーザ名をクエリとするURL url = "http://api.twitter.com/1/statuses/user_timeline.json?screen_name=" + user urlresult = urlfetch.fetch(url) if urlresult.status_code == 200: # 結果をsimplejsonを使ってパース jsonload = simplejson.loads(urlresult.content) for result in jsonload: text = text + result['text'] else: text = "error" return text
userのところにTwitterのアカウント名が入ります。
simplejson使ってパースしてます。
そしてツイートを形態素解析するんですが、どうもGAEは形態素解析が出来ないらしく、Yahoo! APIの形態素解析サービスを使います。
でも、困ったことに「リクエストの最大サイズを100KBに制限している」らしく、20ツイートは場合によっては(というか高確率で)はじかれます。
なのでTinySegmenterを使おうかと思ったのですが、品詞情報はないので、TinySegmenterで分割して出現頻度の高い形態素をYahoo!の形態素解析サービスに突っ込もうと算段しました。
VPSを持っていればMeCabでやっちゃえばおしまいなんですが…。
そもそもGAE/Jなら形態素解析できるっぽいんですが…。
それをやるのが下のsegmentMorpheme.pyです。
# -*- coding: utf-8 -*- from google.appengine.ext import webapp from google.appengine.api import urlfetch from libs.tiny_segmenter import TinySegmenter from application.searchUserTweet import SearchUserTweet # TinySegmenterでテキストを分かち書きする class SegmentMorpheme(webapp.RequestHandler): def sm(self, user): text = "" morpheme = {} # ユーザのツイートを取得 tweet = SearchUserTweet().sut(user) ts = TinySegmenter() result = ts.segment(tweet) # 形態素を出現頻度を値にHashに入れる for seg in result: if morpheme.has_key(seg): morpheme[seg] = morpheme[seg] + 1 else: morpheme[seg] = 1 # 2回以上出現した形態素を保持 for (k, v) in sorted(morpheme.items(), key=lambda x:int(x[1]), reverse=True): if v >= 2 and k != "": text = text + " " + k return text
ここからsearchUserTweet.pyの結果を見にいってます。
ディレクトリ構造を色々やってるのでfrom libs.tiny_segmenterとかfrom application.searchUserTweetになってます。
__init__.pyファイルつくってやってます。
これでユーザのツイートをまとめて分かち書きされたものをゲットできます。
この結果をYahoo!の形態素解析サービスに突っ込むんですが、結果のxmlパースにハマっていてハマッてます。
BeautifulSoupにハマッてます。
できたらまた書きます。
Google App Engineを使ってtwitterのbotを動かす
作ったものはこちら → 読了bot [twitter:@dokuryo_bot]
Twitter SearchのAPIを使って、"読了"をキーワードに検索し、ツイートから書籍名と覚しき文字列を取り出して、出現回数によってテンション変えてつぶやいています。
といっても鉤括弧で囲まれた文字列を書籍名としているだけなんですが。
Twitterのbotを作るためには、定期的にツイートをポストさせるためのマシンが必要になります。
自分のパソコンをサーバとして利用してもいいんですが、経済的ではないのでGoogle App Engine(以下GAE)を利用しました。
GAEの実装環境は、PythonとJavaが提供されているのですが、今回はPythonを使用しています。
ディレクトリ構造はこのようになっています。
/ |-main.py |-tweetPost.py |-searchByKeyWord.py |-app.yaml |-cron.yaml |-oauth.py |-simplejson/
ファイルは全てルートに突っ込んでいます。
cron.yamlが定期的にアクションを起こし、searchByKeyWord.pyがSearch APIを使ってツイートを検索し、その結果をtweetPost.pyでツイートしています。
oauth.pyは、AppEngine-OAuth-Libraryのライブラリを使わせて頂き、simplejsonはライブラリのフォルダごと突っ込んでます。
まず、searchByKeyWord.pyはこのような感じ。
# -*- coding: utf-8 -*- from google.appengine.ext import webapp from google.appengine.api import urlfetch from django.utils import simplejson # Twitter Search API を使って"読了"の検索結果を取得する class SearchByKeyWord(webapp.RequestHandler): def searchKW(self): # "読了"をクエリとするURLを投げる twitterSerchUrl = "http://search.twitter.com/search.json?q=%E8%AA%AD%E4%BA%86&lang=ja&rpp=100&result_type=recent" urlresult = urlfetch.fetch( twitterSerchUrl ) if urlresult.status_code == 200: # 結果をsimplejsonを使ってパース jsonload = simplejson.loads(urlresult.content) results = jsonload['results'] book = {} for r in results: # RTで始まるツイートは無視 if r['text'].split(" ")[0] != "RT": # 『』で囲われた文字列を抽出 doubleBrackets = r['text'].split("『".decode('utf_8')) if len(doubleBrackets) >= 2: bookName = doubleBrackets[1].split("』".decode('utf_8'))[0] if book.has_key(bookName): book[bookName] = book[bookName] + 1 else: book[bookName] = 1 # 「」で囲われた文字列を抽出 brackets = r['text'].split("「".decode('utf_8')) if len(brackets) >= 2: bookName = brackets[1].split("」".decode('utf_8'))[0] if book.has_key(bookName): book[bookName] = book[bookName] + 1 else: book[bookName] = 1 array = [] bookcount = True # 抽出した書籍名を出現回数の降順に並べて配列に挿入 for (k, v) in sorted(book.items(), key=lambda x:int(x[1]), reverse=True): if v >= 2 and k != "": array.append(k + ":" + str(v)) # 出現回数1回の書籍は1つだけ挿入 elif v == 1 and k != "" and bookcount == True: array.append(k + ":" + str(v)) bookcount = False return array else: self.response.out.write("error")
Twitter SearchのAPIは、フォーマットがjsonとatomで選べるのですが、jsonを選択し、simplejsonを使って内容をパースしています。
urlのresult_typeのパラメータはrecentにしています。デフォルトのmixedはリツイートされたツイートが出たりしてあまりよろしくないので。
あとはまぁ書籍名と出現回数を保持していきます。
次に、tweetPost.py。
# -*- coding: utf-8 -*- import os import oauth from google.appengine.ext import webapp from google.appengine.ext.webapp import template, util from searchByKeyWord import SearchByKeyWord TWITTER_CONSUMER_KEY = 'アプリのconsumer key' TWITTER_CONSUMER_SECRET = 'アプリのconsumer secret' TWITTER_ACCESS_TOKEN = 'ボットのアカウントのアクセス・トークン' TWITTER_ACCESS_TOKEN_SECRET = 'ボットのアカウントのアクセス・トークン・シークレット' # 生成した文字列をtwitterにpostする class TweetPost(webapp.RequestHandler): def get(self): postText = "" currentCount = 2 result = SearchByKeyWord().searchKW() for r in result: bookName = r.split(":")[0] bookCount = int(r.split(":")[1]) if bookCount >= currentCount: # 書籍名を追加 postText = postText + "『".decode('utf_8') + bookName + "』".decode('utf_8') # 追加した書籍の出現回数を保持 currentCount = bookCount elif bookCount < currentCount: # 書籍の出現回数によって紹介のテンションを変える if currentCount == 2 and postText != "": postText = postText + "がまぁまぁ読まれてます。あと".decode('utf_8') elif currentCount == 3: postText = postText + "がめちゃ読まれてます。あと".decode('utf_8') elif currentCount >= 4: postText = postText + "がヤバい読まれてます。あと".decode('utf_8') # 書籍名を追加 postText = postText + "『".decode('utf_8') + bookName + "』".decode('utf_8') if bookCount == 1: postText = postText + "とか読まれてるっぽいです。".decode('utf_8') # 追加した書籍の出現回数を保持 currentCount = bookCount client = oauth.TwitterClient(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, None) param = {'status': postText} client.make_request('http://twitter.com/statuses/update.json', token=TWITTER_ACCESS_TOKEN, secret=TWITTER_ACCESS_TOKEN_SECRET, additional_params=param, protected=True, method='POST') self.response.out.write('ok')
searchByKeyWord.pyの結果をハッシュで返せばいいんですが、配列で返してるので色々ここでいじってます。
ちなみに文字数140字への制約はかけてません。今後の課題。
2010年8月でTwitter APIのBASIC認証は廃止されたので、認証にはOAuthを使う必要があります。
こちらのサイトを参考にさせて頂きました。
TwitterボットをOAuthに対応させてみた - Google App Engine(Python)
app.yamlから最初に呼び出すmain.pyはこんな感じ。
from google.appengine.ext import webapp from google.appengine.ext.webapp.util import run_wsgi_app from tweetPost import TweetPost application = webapp.WSGIApplication([('/*', TweetPost), ], debug=True) def main(): run_wsgi_app(application) if __name__ == '__main__': main()
あとは、app.yamlとcron.yamlを設定すれば完成。
課題としては、