TwitterBotの作成③【ラスト】

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())

質問・指摘などあればコメント欄にお願いします。

現在のところこんな感じで安定してつぶやいてくれてます。

現在の課題としては、

・最頻出単語として、「今日」「明日」などの本質的でない単語が選ばれることがある。このあたりの単語もストップワードと同様の扱いにしたい。

・取得するツイートの長さの限界や、ポストする時間間隔を吟味する必要がある。


@pika_shi_bot(Twitter)をよろしく!