121
122

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

TwitterAPI devise連携/グラフ可視化/データの効率的格納/API高速化

Last updated at Posted at 2013-01-09

追記:この技術を使って、コミュニティ解析をするサービスをリリースしました。

Qiita「Twitter API (Ruby)」の続きです。

年末から僕は、TwitterAPIでユーザー情報とフォロー関係を取得し、友人を自動でコミュニティ分類するサービスを作っています。その途中で得られた知見をまとめます。

##目次

  1. Devise連携 :ログインユーザーがTwitterAPIを使えるように。
  2. データ取得 :TwitterAPIから大量のデータを取るサンプル。重いので非同期処理。
  3. Visualize :得たソーシャルグラフを可視化してみる(綺麗)
  4. 高速化(DB) :redis、それは危険なほどのスピード
  5. 高速化(並列処理) :並列処理でAPIアクセスがこんなに速く!

#1.Devise連携
認証用Gem, Deviseとの連携です。Qiita「deviseでfacebook,twitter認証」の続き。
##1-1.tokenを取得
下記のようにコールバックからtokenを取得すれば、そのままTwitterAPIに利用することができます。今回はsessionに保存します。

OAuth認証の説明は省略しますが、これによりログインユーザーがTwitterAPIを使えるようになり、フォローしている鍵アカウントの情報なども取れるようになります。(token認証なしではAPI利用が制限されます)

controllers/users/omniauth_callbacks_controller.rb
def twitter
    @user = User.find_for_twitter_oauth(request.env["omniauth.auth"], current_user)
    session[:token] = request.env["omniauth.auth"]["credentials"]["token"]
    session[:secret] = request.env["omniauth.auth"]["credentials"]["secret"]
.
.
.

##1-2.clientを作成
この際、TwitterAPIを叩くためのclientを作成するメソッドを定義すると便利です。

models/client.rb
class Client
  def self.make(token, secret)
    Twitter::Client.new(
      :oauth_token => token,
      :oauth_token_secret => secret
    )
  end
end

Qiita「Twitter API (Ruby)」の最後にて、config/initializer/twitter.rbに自分のアカウントでログインするためのtoken,token_secretも記述していましたが、今回はユーザーにログインさせるので記述は不要です。

config/initializer/twitter.rb
Twitter.configure do |config|
  #この2つのみで良い
  config.consumer_key = ""
  config.consumer_secret = ""
end

##1-3.データ取得例
実際にcontrollerからclientを作成します。データを取るところまでの簡単な例を書きますが、APIエラーを見越して例外取得もしています。 (重要!)

controllers/hoge_controller.rb
  def get_data
    return unless user_signed_in?
    begin
      @client = Client.make(session[:token], session[:secret])
      data = @client.current_user #なんか取ってきます
      render :json => data
    rescue => exc
      render :json => exc
    end
  end

#2.データ取得
例として、ログインユーザーから1歩分のユーザー情報と、2歩分までのフォロー情報(友人の友人関係)を取ることを考えます。

##2-1.コード

  • データベースに格納する部分のコードは、「4.高速化」にて記述します
  • TwitterAPI ver1.0では350requests/hour しかできないので注意する必要があります。
  • 他にもAPIは様々なエラーを吐くので、その都度対処する必要があります。
  • コードは略しますが、もしwhitelistアカウント(API制限20000回)を持っているなら、基本はwhitelistユーザーで情報を取得し、エラーが吐かれたprotectedユーザーの情報はログインユーザーで情報を取得すると効率が良いです。
mode/twitter_get_data.rb
class TwitterGetData
  def initialize client
    @client = client
    @id = client.current_user.id
  end

  def run #1歩分のユーザー情報と、2歩分のフォロー情報をDBに格納するメソッド
    fetchMyData
    fetchFriendsData
  end

  def fetchMyData
    fetchAndSaveInfo @id
    fetchAndSaveEdges @id
  end

  def fetchFriendsData
    friends = getEdgesFromDB @id
    friends.each do |id|
      fetchAndSaveInfo id
      fetchAndSaveEdges id
    end
  end
  
  def fetchAndSaveInfo id
    info = fetchInfo(id)
    saveInfo(id, info) if info
    true
  end

  def fetchAndSaveEdges id
    edges = fetchEdges(id)
    saveEdges(id, edges) if edges and edges.size > 0
    true
  end

  def fetchInfo id
    id = id.to_i
    begin
      info = @client.user(id)
    rescue => exc
      case exc.class.to_s
      when Twitter::Error::NotFound.to_s #そのユーザーが居ない場合
        delNode id
        return false
      else
        raise exc.to_s + id.to_s
      end
    end
    info
  end

  def fetchEdges id
    id = id.to_i
    begin
      my_follows = @client.friend_ids(id).ids #5000人まで。それ以上取りたい時は:next_cursorを引数にとる必要あり。
    rescue => exc
      case exc.class.to_s
      when Twitter::Error::NotFound.to_s #そのユーザーが居ない場合
        delNode id
        return false
      else
        raise exc.to_s + id.to_s
      end
    end
    my_follows
  end

  def delNode id #後述、DBからデータを消去
  end
  def saveInfo id, info #後述、ユーザー情報を格納
  end
  def saveEdges id, edges #後述、ユーザーのフォロー情報を格納
  end
  def getEdgesFromDB id #後述、DBからフォロー情報を取得
  end


end

##2-2.実行

@client = Client.make(session[:token], session[:secret])
TwitterGetData.new(@client).run

でデータ収集を開始します。(めちゃくちゃ時間がかかります)

##2-3.非同期処理
データ収集中は30分サーバーが応答しないなどの状態に陥るので、delayed_jobや、resqueなどの非同期処理ができるgemを使うと良いでしょう。
今回はresqueを使いました。redis(key-valueなNoSQL)をインストールして起動し、resque workerを立ち上げる必要があります。詳細は公式サイトで。

gem 'resque', require: 'resque/server'
gem 'redis'
lib/tasks/resque.rake
require 'resque/tasks'

task "resque:setup" => :environment
config/initializer/resque.rb
Resque.redis = 'localhost:6379'

で設定し、

app/workers/run_processor.rb
class RunProcessor
  @queue = :fetch_twitter
  def self.perform(session)
    begin
      p "background running"
      client = Client.make(session["token"], session["secret"])
      t = TwitterGetData.new client
      t.run()
    rescue => exc
      p exc
    end
  end
end
hoge_contoroller.rb
def get_data
.
  Resque.enqueue(RunProcessor, session)
.
end

でキューにジョブを投げます。

#3.Visualize
取ってきた枝情報を以下の形式で整形すると、フリーソフトgephiにて可視化することができます。また、クラスター係数など、複雑ネットワークにおける様々な情報を得ることができます。

edges.txt
Tom Jerry
Chage Asuka
.
.

詳細は省きますが、以下のように可視化できます。

unko

#4.高速化(DB)

はじめはRDBMSにデータを格納していたのですが、800人フォローしている僕のアカウントを起点にとるだけで50万の枝情報となり、DBへの保存がネックになりました。
Resqueで導入したredisを使えばデータ構造を保存できるため、APIで取ってきた隣接リストやハッシュがそのまま入り、高速化が図れます。

config/initializer/redis.rb
$redis = Redis.new()
$redis.ping()

以下追記。

mode/twitter_get_data.rb
  def info_key str
    str.to_s + "_info"
  end

  def edges_key str
    str.to_s + "_edges"
  end

  def delNode id
    $redis.del info_key(id)
    $redis.del edges_key(id)
  end

  def saveInfo id, info
    key = info_key(id)
    info = info.to_hash
    $redis.del key
    $redis.mapped_hmset key, info
  end

  def saveEdges id, edges
    key = edges_key(id)
    edges = edges.to_a
    $redis.del key
    $redis.sadd key, edges
  end

  def getInfoFromDB id
    $redis.hgetall info_key(id)
  end

  def getEdgesFromDB id
    $redis.smembers edges_key(id)
  end

用途によってはNeo4jやFlockDBなど、GraphDBの利用も検討の余地があります。

#5.高速化(並列処理)
パフォーマンスを計測してみたところ、TwitterAPIに17分redis格納に3.7秒という結果になりました。APIの待ち時間が長そうなので、並列処理を試してみます。

gem 'parallel'
mode/twitter_get_data.rb
  def fetchFriendsData
    friends = getEdgesFromDB @id
    #friends.each do |id|
    Parallel.each(friends, :in_threads=> friends.size * 2) do |id|
      fetchAndSaveInfo id
      fetchAndSaveEdges id
    end
  end

**17分の処理が33秒になりました。**流石にびっくりです!!!

#終わりに
bot作成にクライアントやサービス作成、色々と遊べるTwitterAPI。
アクセスの遅さやAPI制限など中々手のかかる子ですが、その分かわいく思えてきます。あなたもお試しあれ。

121
122
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
121
122

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?