botが一応完成したのでまとめておく。
前回(TwitterBotの作成② - Hello World !)のは、http://localhost:8080/では正常に動くものの、GAE上ではうまく動かないことが判明。
原因はDeadlineExceededErrorというもの。
タイムアウトによるエラー。プログラムが30秒前後で止まらなければ出るみたい。
まあ当然やわなw 長時間プログラム回し続けてたら負荷大きいし。
とういことでsleepを使うのをあきらめ、Datastoreでツイート管理することに。
作成したモデルは以下の2つ。
- TweetModel
- id…tweetのID
- tweet…tweetの内容
- WordModel
- word…単語
- id_list…その単語を含むtweetのID
- count…その単語を含むtweetの数(len(id_list))
機能・アルゴリズム
TL学習・定期ポスト
1.@pika_shi(Twitter)のTLを1時間分取得し、replyを除去した後にTweetModelに格納。
この時、
・@を含むツイートは格納しない。
・長いツイート(現在は50文字以上に設定),空ツイートは格納しない。
2. 取得したツイートを形態素解析し、名詞かつ日本語かつ2文字以上かつストップワードでないものを抽出。
3. 抽出された単語をWordModelに格納。
4.WordModelに格納されている単語の中で、最も出現回数が多かったもの(最頻出単語)を検索。
5.最頻出単語を含むツイートの中で、ランダムに1つを選択し、ツイートする。
6.TweetModel,WordModelの全エンティティを削除。
リプライ
TL取得の際、正規表現パターンとマッチするツイートがあれば、あらかじめ登録されたものの中からランダムで1つ選び、ツイートする。
プログラム
get_tweet.py
#!/usr/bin/env python #-*- coding: utf-8 -*- import twitter from module import remove_stopwords import igo.Tagger from google.appengine.ext import db from random import choice import re tagger = igo.Tagger.Tagger('ipadic_gae', gae=True) # pika_shiの認証 CONSUMER_KEY="XXXXXXXXXXXX" CONSUMER_SECRET="XXXXXXXXXXXX" ACCESS_TOKEN="XXXXXXXXXXXX" ACCESS_TOKEN_SECRET="XXXXXXXXXXXX" api = twitter.Api(consumer_key=CONSUMER_KEY, consumer_secret=CONSUMER_SECRET, access_token_key=ACCESS_TOKEN, access_token_secret=ACCESS_TOKEN_SECRET, cache=None) # pika_shi_botの認証 bot_CONSUMER_KEY="XXXXXXXXXXXX" bot_CONSUMER_SECRET="XXXXXXXXXXXX" bot_ACCESS_TOKEN="XXXXXXXXXXXX" bot_ACCESS_TOKEN_SECRET="XXXXXXXXXXXX" bot_api = twitter.Api(consumer_key=bot_CONSUMER_KEY, consumer_secret=bot_CONSUMER_SECRET, access_token_key=bot_ACCESS_TOKEN, access_token_secret=bot_ACCESS_TOKEN_SECRET, cache=None) class TweetModel(db.Model): id = db.IntegerProperty() # tweetのid tweet = db.StringProperty(multiline=True) # tweetの内容 class WordModel(db.Model): word = db.StringProperty() # 単語 id_list = db.ListProperty(item_type=long,default=[]) # その単語が含まれるtweetのidのリスト count = db.IntegerProperty(default=0) # その単語が含まれるtweetの数 # 正規表現パターン p1 = 'ぴか(し|に(い|ぃ))(いけめん|イケメン|はんさむ|ハンサム)' p2 = 'ぴか(し|に(い|ぃ))かわいい' p3 = 'ぴか(し|に(い|ぃ))(ど|ド)(M|M|えむ|エム)' # compile re1 = re.compile(p1) re2 = re.compile(p2) re3 = re.compile(p3) # TL取得 TL = api.GetFriendsTimeline() for tweet in TL: # tweetがDBに登録されていない場合 if TweetModel.all().filter('id =', tweet.id).get() == None: # reply除去 while 1: if tweet.text[0] == '@': tweet.text = tweet.text[tweet.text.find(' ')+1:len(tweet.text)] else: break # @を含むtweetは除去 if tweet.text.find('@') != -1: continue # 長いtweet or 空tweet は除去 if len(tweet.text) > 50 or len(tweet.text) == 0: continue # reply if re1.scanner(tweet.text.encode('utf-8')).search() != None: postmsg = '@'.decode("utf-8") + tweet.user.screen_name.decode("utf-8") + choice([' キリッ',' さんくす(・∀・)',' そんなことないよ!',' うれしいでござる',' 言うまでもないがなw']).decode("utf-8") bot_api.PostUpdate(postmsg) if re2.scanner(tweet.text.encode('utf-8')).search() != None: postmsg = '@'.decode("utf-8") + tweet.user.screen_name.decode("utf-8") + choice([' てへっ',' (´ω`)',' 君には負けるぜ(キリッ',' まあね',' どこがやw']).decode("utf-8") bot_api.PostUpdate(postmsg) if re3.scanner(tweet.text.encode('utf-8')).search() != None: postmsg = '@'.decode("utf-8") + tweet.user.screen_name.decode("utf-8") + choice([' ばれてますやんw',' もっといじめて!',' ギクッ']).decode("utf-8") bot_api.PostUpdate(postmsg) # DBに登録 tweetmodel = TweetModel(id=tweet.id, tweet=tweet.text) tweetmodel.put() # 形態素解析 l = tagger.parse(tweet.text) for m in l: # 名詞かつ日本語かつ2文字以上かつストップワードでないものを抽出 if m.feature.split(',')[0] == u"名詞" and m.feature.split(',')[len(m.feature.split(','))-1] != u"*" and len(m.surface) >= 2 and not remove_stopwords(m.surface): # wordがDBに登録されていない場合 if WordModel.all().filter('word =', m.surface).get() == None: # DBに登録 wordmodel = WordModel(word=m.surface) else: # wordをfetchする wordmodel = WordModel.all().filter('word =', m.surface).get() # 同じtweetに複数出てくるwordは除去 if tweet.id in wordmodel.id_list: continue # DBを更新 wordmodel.id_list.append(tweet.id) wordmodel.count = int(wordmodel.count)+1 wordmodel.put()
post_tweet.py
#!/usr/bin/env python #-*- coding: utf-8 -*- import twitter import random from google.appengine.ext import db import datetime d = datetime.datetime.today() # pika_shi_botの認証 bot_CONSUMER_KEY="XXXXXXXXXXXX" bot_CONSUMER_SECRET="XXXXXXXXXXXX" bot_ACCESS_TOKEN="XXXXXXXXXXXX" bot_ACCESS_TOKEN_SECRET="XXXXXXXXXXXX" api = twitter.Api(consumer_key=bot_CONSUMER_KEY, consumer_secret=bot_CONSUMER_SECRET, access_token_key=bot_ACCESS_TOKEN, access_token_secret=bot_ACCESS_TOKEN_SECRET, cache=None) class TweetModel(db.Model): id = db.IntegerProperty() # tweetのid tweet = db.StringProperty(multiline=True) # tweetの内容 class WordModel(db.Model): word = db.StringProperty() # 単語 id_list = db.ListProperty(item_type=long,default=[]) # その単語が含まれるtweetのidのリスト count = db.IntegerProperty(default=0) # その単語が含まれるtweetの数 # 0~8時(日本時間)はtweetしない if d.hour <= 14 or d.hour == 23: # countの最大値を取得 max = WordModel.all().order('-count').get().count # 最頻出単語の検索 wordmodels = WordModel.all().filter('count =', max) # 最頻出単語を含むtweetのidのリストを生成 id_list = [] for wordmodel in wordmodels: id_list.extend(wordmodel.id_list) # その中からランダムで1つを選択し、post id = random.choice(id_list) tweet = TweetModel.all().filter('id =', id).get().tweet api.PostUpdate(tweet) # TweetModel,WordModelのエンティティを削除 db.delete(TweetModel.all()) db.delete(WordModel.all())
質問・指摘などあればコメント欄にお願いします。
現在のところこんな感じで安定してつぶやいてくれてます。
現在の課題としては、
・最頻出単語として、「今日」「明日」などの本質的でない単語が選ばれることがある。このあたりの単語もストップワードと同様の扱いにしたい。
・取得するツイートの長さの限界や、ポストする時間間隔を吟味する必要がある。