GAE/Pでフォロワーの増減確認

前回の記事(フォロワーの増減を確認する - void*)は、shellで動かすことを目的として作ったもので、驚くことに、id:tyru 氏がcronで実行できる(Check Twitter's follower (via http://d.hatena.ne.jp/ykot/20110228/1298879383) · GitHub)ようにしてくれていますので、実際にお使いになる場合は、そちらを使用したほうが効率的かと思います。スクリーンネームでの表示になっていますしね。素敵です:)

今回、少し調子に乗って同じような仕組みでGAEで動かせるようにしてみました。いまさら、GAEの説明は不要だとは思いますので、わからない方は適当にググッてみてください。GAEは、自前マシンを必要としないですし、何よりマシンメンテナンスが必要ない、という点において優れています。この程度のスクリプトなら、無料の範囲なのもすばらしいです。とか、偉そうなことを書いていますが使い始めたばかりで、おもちゃ代わりに遊んでいるだけで、全然解ってないです。最低限動くよって状態ですので、ご指摘などありましたら、ぜひ。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from google.appengine.ext import webapp,db
from google.appengine.ext.webapp import util
from google.appengine.api import urlfetch
from google.appengine.api import mail

from django.utils import simplejson

class Follower(db.Model):
    uid=db.StringProperty()
    followers=db.TextProperty()

class FollowerHistory(db.Model):
    uid=db.StringProperty()
    followers=db.TextProperty()
    entry_dt=db.DateTimeProperty(auto_now_add=True)

class MainHandler(webapp.RequestHandler):
    def get(self):
        for follow in Follower.all():
            self.response.out.write( u"<a href=./%s>%s<br>" % (follow.uid, follow.uid ))

class FollowerHandler(webapp.RequestHandler):
    def get(self, uid):
        url = "http://api.twitter.com/1/followers/ids/" + uid + ".json"
        # retry 10回
        for r in range(10):
            result = urlfetch.fetch(url)
            if result.status_code != 200:
                self.response.out.write( u"%s twitter api error(%d)" % (uid, result.status_code) )
            else:
                break
        if result.status_code != 200:
            return 

        self.response.out.write( u"%s follower check<br>" % uid)
        # JSON結果から、list生成
        newlist=simplejson.loads(result.content)

        # datastoreから、JSONデータを取得
        follow = Follower.gql('WHERE uid = :1', uid).get()
        if follow is None:
            # データストアに存在しない=新規.比較なし
            Follower(uid=uid,followers=result.content).put()
            FollowerHistory(uid=uid,followers=result.content).put()
            return

        # 比較
        oldlist=simplejson.loads(follow.followers)
        newset=set(newlist)
        oldset=set(oldlist)
        follow_list=""
        for i in newset - oldset:
            follow_list += "follow:%s\n" % i
        for i in oldset - newset:
            follow_list += "remove:%s\n" % i

        # データ設定
        follow.followers = result.content
        follow.put()

        b = u"...%s の現在のフォロワーの数:%d" % (uid, len(newlist))
        self.response.out.write( u"%s<br>%s" % (follow_list, b) )

        if follow_list != "":
            # 差分あり
            FollowerHistory(uid=uid,followers=follow.followers).put
            # 差分があった時、メールを送信
            body_text = follow_list + b
            mail.send_mail(sender = "foo@example.com",
                           to = "bar@example.com",
                           subject = "%s followers check" % uid,
                           body = body_text.encode('utf-8'),
                          )

def main():
    application = webapp.WSGIApplication([('/', MainHandler),
                                          ('/([-\w]+)', FollowerHandler)],
                                         debug=True)
    util.run_wsgi_app(application)


if __name__ == '__main__':
    main()

response.out.writeとかは、cron使うと必要ないのですが、デバッグとして埋め込んでいます。
http://[appname].appspot.com/[twitter uid]で、差分があった場合、メールで通知する仕組みになっています。一応、http://[appname].appspot.com/ で、一覧が出るようになっています。履歴をとっているのは、後ほど利用するためです。
色々いじっていてわかったのですが、apiは、頻繁にエラーを返すので、retryをするようにしています。

また、下記のメール設定を変更してやれば、メール送信できるようになります。foo@example.com、bar@example.comを適当に変更してご使用ください。

            mail.send_mail(sender = "foo@example.com",
                           to = "bar@example.com",
                           subject = "%s followers check" % uid,
                           body = body_text.encode('utf-8'),
                          )

cron.yamlで、cron設定しておけば自動的に実行されます。5分おきとかなら、こんな感じです。差分があった場合に、メールで通知してくれます。

cron:
- description: follower chenge check job
  url: /ykots
  schedule: every 5 minutes

簡単ですね ;)

ちなみに、既知のバグに、フォロワーの数が多いとエラーになるというのがあります。これは、Datastoreの容量制限に引っかかってしまっているためです。それほど試していないのですが、感覚としては、10000から20000ぐらいのフォロワーがいるとエラーになるみたいです。しばらくしたら、そのあたりも対応していく予定です。