酒と泪とRubyとRailsと

Ruby on Rails と Objective-C は酒の肴です!

Herokuの無料枠でTwitter Stream APIを常時監視 => DBに保存するRubyコード

TwitterのStreaming APIを使うと、流れてくるTweetを常時監視することができます。

監視する対象は特定のキーワードだったり、特定のユーザーだったり、特定のサイトを指定したりすることができます。ユーザーの場合はユーザーのツイートに対するリプライも取得できるので、使って見るとかなり夢が広がるAPIです。

今回はこのTwitter Stream APIをHerokuで無料で監視しつつ、DBに蓄積するPGを書いたのでその紹介をしていきます。


ソースコード

今回作成したソースコードはこちら。

詳細の説明は省きますが、基本的には環境変数に「TwitterのAPIのキー情報」と「DBへの接続情報」を書いて、後はAPIをEventMachineで監視 => Tweetが取得できたらDBに書き込むようになっています。

今回はこのソースをtweetscan.rbとします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
require 'rubygems'
require 'bundler'
require 'mysql2'
require 'json'

Bundler.require

require 'twitter/json_stream'

# TwitterのAPIキー情報を環境変数から取得
TWITTER_CONSUMER_KEY        ||= ENV['TWITTER_CONSUMER_KEY']
TWITTER_CONSUMER_SECRET     ||= ENV['TWITTER_CONSUMER_SECRET']
TWITTER_OAUTH_TOKEN         ||= ENV['TWITTER_OAUTH_TOKEN']
TWITTER_OAUTH_TOKEN_SECRET  ||= ENV['TWITTER_OAUTH_TOKEN_SECRET']
FOLLOWS                     ||= ENV['FOLLOWS']

# DBへの接続情報を環境変数から取得
DB_HOSTNAME   ||= ENV['DB_HOSTNAME']
DB_USER_NAME  ||= ENV['DB_USER_NAME']
DB_PASSWORD   ||= ENV['DB_PASSWORD']
DB_NAME       ||= ENV['DB_NAME']

EventMachine::run {
  stream = Twitter::JSONStream.connect(
    :path    => "/1.1/statuses/filter.json?follow=#{FOLLOWS}",
    :oauth => {
      :consumer_key    => TWITTER_CONSUMER_KEY,
      :consumer_secret => TWITTER_CONSUMER_SECRET,
      :access_key      => TWITTER_OAUTH_TOKEN,
      :access_secret   => TWITTER_OAUTH_TOKEN_SECRET
    },
    :ssl => true
  )

  stream.each_item do |item|
    $stdout.print "item: #{item}\n"
    $stdout.flush

    # MySQLへ接続(Postgresなどを使う場合は適宜変更)
    client = Mysql2::Client.new(:host => DB_HOSTNAME, :username => DB_USER_NAME, :password => DB_PASSWORD || '', :database => DB_NAME)

    # Tweetのjsonをパース
    tw_json = JSON.parse(item)

    # DBに格納するためにエンコーディング
    user_id                         = client.escape(tw_json['user']['id_str'])
    user_name                       = client.escape(tw_json['user']['name'])
    user_screen_name                = client.escape(tw_json['user']['screen_name'])
    user_image                      = client.escape(tw_json['user']['profile_image_url'])
    user_description                = client.escape(tw_json['user']['description']) rescue nil
    text                            = client.escape(tw_json['text'])
    post_media_url                  = client.escape(tw_json['entities']['media'].first['media_url']) rescue nil
    twitter_status_id               = client.escape(tw_json['id_str'])
    twitter_reply_status_id         = client.escape(tw_json['in_reply_to_status_id_str']) rescue nil
    twitter_reply_user_id           = client.escape(tw_json['in_reply_to_user_id_str'])   rescue nil
    twitter_reply_user_screen_name  = client.escape(tw_json['in_reply_to_screen_name'])   rescue nil

    # tweetsテーブルに書き込み
    client.query("INSERT INTO tweets (user_id, user_name, user_screen_name, text, post_media_url, user_image, user_description, twitter_status_id, twitter_reply_status_id, twitter_reply_user_id, twitter_reply_user_screen_name, updated_at, created_at) VALUES ('#{user_id}', '#{user_name}', '#{user_screen_name}', '#{text}', '#{post_media_url}', '#{user_image}', '#{user_description}', '#{twitter_status_id}', '#{twitter_reply_status_id}', '#{twitter_reply_user_id}', '#{twitter_reply_user_screen_name}', '#{Time.now}', '#{Time.now}')")

    # MySQLとの接続を解除
    client.close
  end

  stream.on_error do |message|
    $stdout.print "error: #{message}\n"
    $stdout.flush
  end

  # 再接続は書いていないです。書いて教えてくださいw
  stream.on_reconnect do |timeout, retries|
    $stdout.print "reconnecting in: #{timeout} seconds\n"
    $stdout.flush
  end

  stream.on_max_reconnects do |timeout, retries|
    $stdout.print "Failed after #{retries} failed reconnects\n"
    $stdout.flush
  end
}

foremanをつかったプロセス管理

今回のソースはforemanを使っています。Gemfileに以下のコードを追加してbundle installを実行。

1
2
# プロセス管理
gem 'foreman'

次にforeman用の設定ファイルProcfileを作成。

1
tweetscan: bundle exec ruby tweetscan.rb

これで設定は完了です。以下のコードを実行するとプロセスがスタートして、Twitter Streamの監視を始めます。
ただし現時点では、Twitterのキー情報やDBへの接続情報が登録されていないので失敗します。

1
foreman start

Twitterのキー情報の取得と環境変数への登録

まずは以下のサイトでTwitterアプリを登録してください。
(Sign in => アプリの登録)

Twitter Developers

登録したら環境変数にTwitterキーを登録します。まずはローカルへのキー情報の登録です。foremanで管理しているプロセスでは、.envファイルに環境変数にしたい情報を書き込むと勝手に読み込んでくれます。

1
2
3
4
TWITTER_CONSUMER_KEY=xxx
TWITTER_CONSUMER_SECRET=xxx
TWITTER_OAUTH_TOKEN=xxx
TWITTER_OAUTH_TOKEN_SECRET=xxx

こういった面倒な手順をふむ理由は、キー情報が第三者に使われると悪いことをされる可能性があるからです。(.envはgitignore☆)

同じ流れで、DBの情報やAPIの引数なども環境変数に登録してください。

Herokuへのデプロイ

では、Herokuにソースコードをデプロイ。
(このまえにソースはローカルでgitにコミットしておいてください)

1
2
heroku create heroku-twitterscan --stack cedar
git push heroku master

続いて、環境変数にTwitterのキー情報やDBへの接続情報、APIの引数などを登録。

1
2
3
4
heroku config:set TWITTER_CONSUMER_KEY=xxx
heroku config:set TWITTER_CONSUMER_SECRET=xxx
heroku config:set TWITTER_OAUTH_TOKEN=xxx
heroku config:set TWITTER_OAUTH_TOKEN_SECRET=xxx

ちなみに、HerokuのDBの作成手順などは拙著の以下の記事などがオススメです。

Heroku/Posgresqlでよく使うコマンド一覧

Rails4でheroku Pushまでの最短手順 [haml/bootstrap 3.0/postgresql or MySQL]

ということでtwitterscan.rbのプロセスを起動!

1
heroku scale twitterscan=1

以下のコマンドでプロセスが起動しているか、確認できます。

1
heroku ps

ツイート結果はログからも確認できます!

1
heroku logs --tail

ということでHerokuの無料枠でTweetをチェックして、DBに格納までする手順でした。 こちらはGitHubでもソースコードを公開しておきます。

morizyun/tweetscan GitHub

エンジニア経験浅いので、是非色々とツッコミを頂ければ幸いです。よろしくお願いします!

Special Thanks

voloko/twitter-stream

HerokuでStreaming APIを使うTwitter Botを作る | monoの開発ブログ

RubyでMySQLに繋ぐためのruby-mysqlとmysql2 - tagomorisのメモ置き場

Convert datetime to mysql format on Ruby on Rails - Stack Overflow

おすすめの書籍