読者です 読者をやめる 読者になる 読者になる

【Ruby】Tumblr Clientを使って記事一覧を取得する

RubyTumblrAPIを使用するのはTumblr Clientを使用するのが一番簡単みたいです。

記事投稿のサンプルはネットに色々転がってるんですが、全記事を取得するサンプルは見当たらなかったのでメモ代わりに載せておきます。

Tumblr Clientを使用するための準備

とりあえずGemをもらいます。GitHubにも書いてありますが一応。

gem install tumblr_client

コマンドプロンプトなりコンソールなりターミナルから「tumblr」と入力するとirbが起動してCUITumblrのデータをいじることが出来ますが、今回はライブラリとして使うのでそのための準備をします。

これもGitHubに書いてあるんですが、ConfigにOauth認証用のキーを突っ込みます。

require "tumblr_client"
Tumblr.configure {|config|
  config.consumer_key = ""
  config.consumer_secret = ""
  config.oauth_token = ""
  config.oauth_token_secret = ""
}

見ての通りきちんとOauth認証を経由してAccessTokenを取得する必要があるんですが、その辺はOauth Gemを使うといいよ、とのことです。丸投げかいな。

自分だけが使えればいいのであればAPI Consoleで認証すると「Show keysAPI」でどっちも貰えます。当たり前ですが、Rubyに限らずどんな言語からもこれでアクセス出来るので、貰っておいて損はありません。

実際にメソッドを投げてみる

このライブラリはTumblrのAPIをかなり忠実に…というか、ソースを見ると引数で渡したハッシュをそのままapi.tumblr.com/v2/blog/xxxx/yyyy?にクエリとしてくっつけています。

yyyyにあたる部分がメソッド名になっている、と考えれば大丈夫っぽいです。xxxxは「user」かbase-hostnameになっているんですが、base-hostnameが必要な場合は第一引数になってるみたいですね。

返り値もAPIを叩くと返ってくるjsonをとりあえずハッシュにしてみただけのようです。(一応ステータスコードを見たりしてる節はあるけど)

ま、要はjavascriptで書いてるようなもんだと思えばそれでよさそうです。Tumblrから返ってくるjsonは何らかのインターフェースやクラスにマッピングするのが非常に辛いところがあるので、これぐらいアバウトにやってくれた方が却ってありがたいのかもしれません。

とりあえずこんな感じに使えます。

require "tumblr_client"

Tumblr.configure {|config|
  config.consumer_key = ""
  config.consumer_secret = ""
  config.oauth_token = ""
  config.oauth_token_secret = ""
}

client = Tumblr::Client.new
client.post("outofmem.tumblr.com", :type=>"text", :limit=>20, :offset=>0)

上記の例だとTextの取得になります。postの引数は前述した通り、base-hostnameとpostメソッドで投げられるクエリのハッシュです。

返ってくるjsonは「Textの取得」にある例の「response」以下のjson(ハッシュ)です。なので、実際に投稿したデータを取得するにはこんな風にしなきゃいけません。

r = client.post("outofmem.tumblr.com", :type=>"text", :limit=>20, :offset=>0)
r["posts"].each{|x|
  puts "タイトル:#{x["title"]}"
}

取得ではなく投稿に関しても恐らく同じ法則でいけると思います。(試してない)

全件取得するコード

async_task.rb及びAsyncTaskクラスに関しては昨日書いた記事を読んでみてください。

HTTP通信もI/O処理だからなのか、思った以上に高速化できました。

$:.unshift File.dirname(__FILE__)  
require "async_task.rb"
require "tumblr_client"

class TumblrReader
  attr_accessor:limit
  attr_reader:is_end
  
  def initialize(type="text", limit=20)
    Tumblr.configure {|config|
      config.consumer_key = ""
      config.consumer_secret = ""
      config.oauth_token = ""
      config.oauth_token_secret = ""
    }
    
    @client = Tumblr::Client.new
    @domain = "outofmem.tumblr.com"
    @type = type
    @limit = limit
    @offset = 0
    @total_posts = 0
    @type_info = {@type => {:offset => @offset, :total_posts => @total_posts}}
  end
  
  def change_type(new_type)
    # 変更前のtypeのoffsetとtotal_postsを保存しておく
    @type_info[@type][:offset] = @offset
    @type_info[@type][:total_posts] = @total_posts
    
    @type = new_type
    
    if !@type_info.key?(@type) then
      @offset = 0
      @total_posts = 0
      @type_info[@type] = {:offset => @offset, :total_posts => @total_posts}
      @is_end = false
    else
      @offset = @type_info[@type][:offset]
      @total_posts = @type_info[@type][:total_posts]
      @is_end = @total_posts != 0 && @offset >= @total_posts
    end
    
  end
  
  def get_posts_async()
    
    if @is_end then return Array.new end
    
    # 初回時はtotal_postsを取得する必要がある
    if @total_posts == 0 then
      r = @client.posts(@domain, :type => @type, :limit => @limit, :offset => 0);
      @offset += @limit
      @total_posts = r["total_posts"]
      
      @is_end = @offset >= @total_posts
      
      return AsyncTask.new(lambda{|x|
        return x
      }, r["posts"]).start
    else
      return AsyncTask.new(lambda{
        
        if @is_end then return Array.new end
        
        offset = @offset
        @offset += @limit
        @is_end = @offset >= @total_posts
        
        return @client.posts(@domain, :type => @type, :limit => @limit, :offset => offset)["posts"]
        
      }).start
    end
    
  end
  
end

こんな感じに呼び出します。

tr = TumblrReader.new
tasks = Array.new

while(!tr.is_end) do
  tasks.push(tr.get_posts_async().continue{|t|
    t.result.each {|x|
      # TODO:Tumblrのデータでなにかする
    }
  })
end

tasks.each{|t|
  t.wait
}

まとめ

なーんかコードがあんまり洗練されてない感じがしますが、いっぱい書いていくうちに垢抜けていくでしょう。きっと。