GoogleAppEngine SDK for PythonでTwitter BOTを作る


Pythonの勉強のために無料でも使えるGoogleAppEngine（以下GAE)をプラットフォームにTwitter BOTを作成して運用しています。
BOTはGoogleアラートを利用してラブライブのニュースを定期的に呟いてます。
アカウント: [@ll_news_bot](https://twitter.com/ll_news_bot "ll_news_bot")
ソース : [GitHub](https://github.com/droibit/ll_tweet_bot "GitHub")

GAE SDK for PythonでBOTを運用するまでの手順を備忘録として残しておきます。

## BOT仕様
今回作成したBOTの最終的な仕様は次のようになります。

* 30mごと(※1)にGoogleアラートの新エントリを呟く(9:00~23:59)
* フォローミーを呟く(18:30)
* 30mごと(※1)にフォロー返し(6:00~23:59)
* 1h毎にフォローのみの場合リムーブ（0:00~5:59）

※1 GAEにはインスタンス時間というものがあり、リクエストの処理期間中のみ時間を消費するというわけではない。
リクエストが終わった後もすぐ対応できるようアイドル状態になるが、その間も時間を消費する。
そのため、呟きとリフォローを同じ時間に行うことで無駄なアイドル時間を消費しないようにしています。

## 事前準備

### Googleアラート
[Googleアラート](http://www.google.co.jp/alerts/manage?hl=ja "Googleアラート")から新しいニュースを作成します。この時配信先を **フィード** にします。

### 環境
ローカルで開発するために次の環境を使用しています。

* Mac OS X 10.9.2
* Eclipse 4.3 + PyDev
* GoogleAppEngineLauncher 1.9.2
* Python2.7.6

### Twitter 

* BOTアカウントの取得
* [Twitter Developer](https://dev.twitter.com/ "Twitter Developer")への登録
* 新しいアプリの作成とAPIキーの取得
	* Permissions - アクセス権限をR/W に変更
	* Test OAuth より各種認証情報を取得

### GoogleAppEngine
[appengine.google.com](https://appengine.google.com/ "https://appengine.google.com/") よりBOT用の新しいアプリケーションを作成します。
この時入力した *Application Identifier* は GAELauncherで新しいアプリを作成する際に *Application ID* で使用します。

### 外部モジュール
BOT開発にあたり次のサードパーティ製モジュールを利用しました。
Twitter APIのPythonラッパーとRSSフィードのパーサーの2種類のモジュールです。

* [python-twitter](https://github.com/bear/python-twitter "python-twitter")
	* [Requests](http://docs.python-requests.org/en/latest/ "Requests")
	* [SimpleJson](http://cheeseshop.python.org/pypi/simplejson "SimpleJson")
	* [Requests OAuthlib](https://requests-oauthlib.readthedocs.org/en/latest/ "Requests OAuthlib")
* [feedparser](https://pypi.python.org/pypi/feedparser#downloads "feedparser")

上記のモジュールはWebアプリケーションと一緒にGAE上にデプロイしなければいけないので、
モジュールをインストール or ビルドしたあと、プロジェクト内にコピーする必要がありました。

## BOT実装
作成したBOTの機能は大きく「RSSフィードから呟きの作成」と「Twitter APIの操作」になりますので、これらの処理ごとのモジュールを作成していきます。

### feedモジュール
このモジュールでは *feedparser* モジュールを利用してRSSフィードを解析し、呟く内容を作成しています。
新着エントリがないか定期的に確認しているため、重複して呟かないように *DataStore* を利用しています。

``` python
class FeedFetcher(object):
    '''GoogleアラートのRSSフィードをパースするためのクラス'''

  	def fetch_new_entries(self):
    '''GoogleアラートのRSSフィードから新しいエントリを取得する。'''
	# 省略
	return new_entory

class Entry(object):
    '''解析したエントリー情報を格納するためのクラス'''
    
 	def make_tweets(self):
	'''呟き内容を作成する'''
	return # 呟き

# 使い方
fetcher = FeedFetcher()
new_entries = fetcher. fetch_new_entries()
for entry in new_entries:
	print entry.make_tweet()
```


### botモジュール
このモジュールでは、python-twitterモジュールをラップし、必要なAPIを簡単に叩けるようにしています。
また、準備の時に取得したTwitterの認証情報をpython-twitter APIに渡しています。
こちらを[参考](http://d.hatena.ne.jp/lolloo-htn/20100821/1282396855 "参考")にさせていただき、GAEでpython-twitterを使用する場合はキャッシュしないようにしています。

``` python
class TwitterBot(object):
    '''Twitterへの呟き、ユーザのリフォローとフォロー解除を行うためのクラス'''

	def tweet(self, message):
	'''Twitterへ対象の内容を呟く'''
	# 省略

	def get_not_follow_users(self):
	'''未フォローユーザのIDリストを取得する'''
	return # 未フォローユーザIDリスト

	def refollow(self, not_follow_id):
	'''対象のユーザをフォローする'''
	# 省略

	# ...省略

#使い方
bot = TwitterBot()
fetcher = FeedFetcher()
bot.tweet(fetcher. fetch_new_entries[0].make_tweet)
bot.refollow(bot. get_not_follow_users()[0])

```

### リクエストハンドラ
まずURLマッピングを行うために *app.yaml* ファイルを作成します。
login が admin になっていると、ブラウザ上でURLを叩いてもアカウント認証しないとリクエストが送信されないようになります。
script には モジュール名とURLマッピングを行う *WSGIApplication* オブジェクト名を書きます。

``` yaml
application: ll-news-bot
version: 8
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /
  script: tweet.app
  login: admin

- url: /tweet
  script: tweet.app
  
```

python側では *RequestHandler* を継承したクラスを作成し、get もしくは post メソッドのハンドル処理を書いていきます。
一度に大量にTwitterAPI をコールすることはありませんが、勉強のためタスクキューを使用しています。
タスクが実行されると指定したURLにPOSTメソッドのリクエストが送信されます。

``` python:tweet.py
class MainHandler(webapp2.RequestHandler):    
    def get(self):
		 taskqueue.add(queue_name='tweet', url='/tweet', params={'message': u'テスト'})

class TweetHandler(webapp2.RequestHandler):    
    def post(self):
		# つぶやき処理
		bot = TwitterBot()
		bot.tweet(self.request.get('message')

app = webapp2.WSGIApplication([('/', MainHandler), ('/tweet', TweetHandler)], true) 
```

  
タスクキューを使用するには *queue.yaml* を作成します。タスクキューに関する説明は次のサイトがとても参考になりました。

* [時間のかかる処理をタスクキューで分割する](http://techbooster.org/gae/12725/ "時間のかかる処理をタスクキューで分割する")

``` yaml:app.yaml
queue:
- name: tweet
  rate: 1/s
  bucket_size: 1 
  retry_parameters:
    task_retry_limit: 0
```

最後にこの処理を定期的に実行させるように *cron.yaml* ファイルを作成します。
どのくらいの間隔で実行するか *schedule* で設定します。

``` yaml:cron.yaml
cron:
- description: tweet task
  url: /
  schedule: every 30 minutes from 9:00 to 23:59
  timezone: Asia/Tokyo

```

### ローカル実行
GAELaucherアプリを使用し、ローカル環境でアプリの動作確認してみます。
作成はLauncherアプリから行えます。その際、Application IDに事前準備で予め作成しておいたアプリの *Application Identifier* を使用します。

実行の前にメニューから *Make Symlink* を実行しておく必要があります。
また、GAEが使用するポート番号を他のアプリが使用している場合は、どちらかのポート番号を変更する必要があります。

## デプロイ
最後にGAELauncherを使用し、Googleのサーバーにアプリケーションをデプロイします。
その際にアカウント入力がありますので、事前準備で作成したアプリケーションに対応するGoogleアカウントを入力します。

デプロイ中に *Checking if deployment succeeded.* が延々と表示される場合がありましたが、これはサーバー側の問題のようです。
最後にバージョンを上げて再デプロイするかロールバックしてくださいとでますので、今回はバージョンを上げて再デプロイしたところ上手くいきました。
　　
以上がTwitterのBOTを開発してから運用するまでの流れになります。