Twitter のアイコン画像 (プロフィール画像) を取得する Ruby スクリプト

Twitter のユーザー名 (screen_name) を指定して複数人のプロフィール画像をダウンロードする必要があったので、Rubyスクリプトを書きました。 特に面白くもないですが置いておきます。

プロフィール画像を取得するための Twitter API

プロフィール画像を取得するための API として、以下のものが公開されています。

ユーザー認証なし、アクセス回数制限なしで利用できるものだったので、これを利用しています。 この API は、レスポンスコードとして 302 を返し、実際のプロフィール画像URI へのリダイレクトを促すものです。 よって、今回書いたスクリプトでは、まず、この API を使って実際のプロフィール画像URI を取得し、次にその URI にアクセスして画像をダウンロードする、という 2 段階の処理を行っています。

詳しいことは API のドキュメントを参照してください。

禁止事項 (2011-06-05 追記)

詳しいことは API のドキュメントを参照、と書いていてもちゃんと読まない人も居ると思うので禁止事項について書いておきます。 API ドキュメントには以下のように書いています。

This method must not be used as the image source URL presented to users of your application.

すなわち、この API は画像リソースの URL へのリダイレクトをレスポンスとして返してきますが、その動きを利用して画像リソースの URL の代わりとしてこの API の URL をユーザーに提供するようなことはしてはいけません。 その点はご注意ください。

Twitterプロフィール画像を取得する Ruby スクリプト

以下、実際に書いたスクリプトです。

#! ruby_1.9 -E:utf-8
# encoding: utf-8
# (ruby 1.8 を使っている場合は上の 2 行を消してください)

##
# 設定項目

# アイコンを取得する対象となるユーザーの screen_name の配列
screen_names = [ 'nobuoka', 'qp*******' ]
# アイコン画像を格納するディレクトリ名 (存在するものを指定すること)
icon_dir = 'icons'
# 画像の content_type と拡張子の対応関係 (ここにない content_type の画像を受け取ると例外発生)
content_type_table = {
  'image/jpeg' => '.jpg',
  'image/png'  => '.png',
  'image/gif'  => '.gif'
}

## 
# 処理内容

require 'uri'
require 'net/http'
Net::HTTP.version_1_2 # おまじない

##
# 指定した URI の画像を取得してファイルに出力する
# 
# @param uri_str 取得する画像の URI (String)
# @param file_name 取得した画像を保存する際のファイル名 (String / 拡張子とドットを除く)
# @param output_dir 取得した画像を出力する先のディレクトリ. 'icons' など (String)
# @param content_type_table 取得した画像の content_type と拡張子の関係を表すハッシュ
def get_image_by_uri( uri_str, file_name, output_dir, content_type_table )
  uri = URI.parse( uri_str )
  req = Net::HTTP::Get.new( uri.path )
  res = Net::HTTP.new( uri.host, uri.port ).request( req )
  if res.code == '200'
    ext_str = content_type_table[ res.content_type ]
    if ext_str.nil? then raise "unexpected content type: #{res.content_type}" end
    # ファイルに出力する
    File.open( "#{output_dir}/#{file_name}#{ext_str}", 'wb' ) do |f|
      f.flock( File::LOCK_EX )
      f << res.body
      f.flock( File::LOCK_UN )
    end
  else
    # 画像取得に失敗
    if ext_str.nil? then raise "unexpected response code (image server): #{res.code}" end
  end
end

##
# screen_name の配列を渡すと自動的に画像を取得して出力する
# 
# 各 screen_name に関して, Twitter のアイコン画像取得 API [1] を使用し,
# アイコン画像の URI を取得する. 取得した URI を get_image_by_uri メソッドに投げて
# 画像を取得する. 画像取得に失敗した場合は, そのまま次のユーザーの画像取得に移行する.
# [1] http://dev.twitter.com/doc/get/users/profile_image/:screen_name
# 
# @param screen_names 取得する対象となる twitter アカウントの screen_name (String) の配列
# @param icon_dir 取得した画像を出力する先のディレクトリ. 'icons' など (get_image_by_uri の引数として使う)
# @param content_type_table 取得した画像の content_type と拡張子の関係を表すハッシュ (get_image_by_uri の引数として使う)
def get_icon_images_by_twitter_screen_name( screen_names, icon_dir, content_type_table )
  http = Net::HTTP.new( 'api.twitter.com', 80 )
  screen_names.each do |screen_name|
    begin
      req  = Net::HTTP::Get.new( "/1/users/profile_image/#{screen_name}.xml?size=normal" )
      res  = http.request( req )
      if res.code == '302'
        uri = URI.parse( res['location'] )
        get_image_by_uri( res['location'], screen_name, icon_dir, content_type_table )
      else
        # Twitter API からの戻り値失敗
        raise "unexpected response code (twitter API): #{res.code}"
      end
    rescue => err
      $stdout << "取得失敗 : #{screen_name} (#{err})" << "\n"
    else
      $stdout << "取得成功 : #{screen_name}" << "\n"
    end
  end
end

# 処理実行
get_icon_images_by_twitter_screen_name( screen_names, icon_dir, content_type_table )