がむしゃらの画像を取りまくるRubyスクリプト

がむしゃら画像をひたすら集めまくるRubyスクリプトを書いたので、何となく公開。

使い方

  • 基本的にWindows用(なのでアウトプットはSJIS
  • 適当なフォルダにスクリプトをコピーして
> ruby gamuget.rb
  • つらつらとログが流れるのをボケッと眺めていると、カレントディレクト配下に板名のフォルダができて、画像がひたすら落ちてきている。

特徴

  • 画像ファイルはまずheaderをゲットして、ファイルサイズが既にあるものと同じかどうかチェックする。
  • 取得する板の一覧は最後の方のfor文のところで、板名を入れて指定する。
  • 取得する処理は板毎のスレッドに分けているので、早い(?)
  • 板一覧はカレントディレクトリ配下に、board_list.txt として作成する。
  • 板一覧ファイルがないときは、トップページに板一覧を取りに行く。
  • 超適当なリンク先取得処理。
=begin
画像掲示板がむしゃらから、画像を取りまくるスクリプト。

todo
・同じ名前のファイルが既にローカルにあったときに、ファイルサイズを見て既存ファイルを待避する。その際、すでに待避されているファイルと同じものが無ければ、新規に待避する。同一判定の基準は、とりあえずファイルサイズ。
    一応実装しているけど、検証していないのであやしい。。

=end

$KCODE = 'utf8'

require 'uri'
require 'net/http'
require 'kconv'
require 'fileutils'

Net::HTTP.version_1_2  # おまじない

def printsjis(str)
  print str.tosjis
end

def fetch( uri_str, limit = 10 )
  # 適切な例外クラスに変えるべき
  raise ArgumentError, 'http redirect too deep' if limit == 0

  response = Net::HTTP.get_response(URI.parse(uri_str))
  case response
  when Net::HTTPSuccess     then response
  when Net::HTTPRedirection then fetch(response['location'], limit - 1)
  else
    response.error!
  end
end

def normal_uri(now_uri, new_uri)
  if /^.+:\/\// =~ new_uri then
    new_uri
  else
    URI.join(now_uri, new_uri).encode
  end
end

top_url = 'http://gamushara.net/index.html'
board_list_file = 'board_list.txt'

# 板一覧ハッシュ  { 名前(UTF-8) => URL, ... }
board_list = Hash.new

# 板一覧ファイルがあるときは、そこから板一覧を得る。
# たまに板一覧ファイルを削除する必要あり。
if FileTest.writable?(board_list_file)
  open(board_list_file) {|f|
    while line = f.gets
      parts = line.chomp!.split(/\t/)
      board_list[parts[0]] = parts[1]
    end
  }
else
  # 板一覧を得る
  printsjis "トップページから左フレームのURLを取得中..."
  top_page = fetch(top_url).body
  left_frame_url = top_page.scan(/<FRAME SRC="([^"]*)"/)[0][0]
  left_frame_url = normal_uri(top_url, left_frame_url)
  printsjis "完了\n"

  printsjis "左フレームの内容を取得中..."
  left_page = fetch(left_frame_url).body.toutf8
  #puts left_page.tosjis
  printsjis "完了\n"

  printsjis "板一覧を取得中..."
  left_page.scan(/<A HREF="([^>]+)"><font size=.>([^<]+)<\/font><\/A>/).each {|s|
    board_list[s[1]] = s[0]
  }
  printsjis "完了\n"

  # 板一覧を名前でソートしてファイル書き込み & 画面表示
  open(board_list_file, "w") {|f|
    board_list.keys.sort.each{|a|
      f.puts a + "\t" + board_list[a.toutf8] + "\n"
      print a.tosjis + "\t\t=> " + board_list[a] + "\n"
    }
  }
  puts
end

# file_nameのファイルが存在していれば、ファイル名に_1とか_2とか付けて待避する。
def rename_file(file_name)
  file_size = FileTest.size?(file_name)
  return unless file_size

  dirname = File.dirname(file_name)
  extname = File.extname(file_name)
  basename = File.basename(file_name, extname) + '_1'
  i = 2
  while this_file_size = FileTest.size?(dirname + "/" + basename + extname)
    # 自分と同じファイルサイズのファイルがいれば、既に待避済みと見なして復帰する。
    return if file_size == this_file_size
    basename = File.basename(basename, '_' + (i - 1).to_s) + "_" + i.to_s
    i += 1
  end
  FileUtils.mv(file_name, dirname + "/" + basename + extname)
end

def get_all_pictures(board_name, board_url)
  # スレ一覧  { URL, ... }
  thread_list = Array.new

  now_board = board_name
  printsjis "「#{now_board}」(#{board_url})の画像を全取得します。\n\n"

  printsjis "「#{now_board}」のスレ一覧を取得中..."
  main_url = board_url
  now_page = 1
  begin
    printsjis now_page.to_s + "枚目..."
    # スレの内容をゲット
    main_page = fetch(main_url).body.toutf8
    #puts main_page.tosjis

    # 次ページURLを得る
    next_url = main_page.scan(/<a href="([^"]*)">次→<\/a>/).flatten[0]
    if next_url then
      next_url = URI.join(main_url, next_url).to_s
    end

    # スレ一覧を得る
    thread_list |= main_page.scan(/<a href="(.+?)">/).flatten.grep(/^\.\/html/).map{|url| URI.join(main_url, url).to_s }

    main_url = next_url
    now_page += 1
  end while next_url
  printsjis "完了\n"

  # スレッドURL一覧を表示
  #p thread_list

  # 画像一覧  { URL, ... }
  picture_list = Array.new
  res_url_list = Array.new

  printsjis "スレッドから画像URL一覧を取得中..."
  thread_list.each{ |thread_url|
    begin
      thread_main = fetch(thread_url).body.toutf8
      picture_list |= thread_main.scan(/<img src="([^"]+\.(?:jpe?g|gif))"/).flatten.reject{|item| item =~ /__s.(jpe?g|gif)/}.map{|url| URI.join(thread_url, url).to_s}
      res_url_list |= thread_main.scan(/<a href="([^"]+imgview\.php[^"]+)"/).flatten.map{|url| URI.join(thread_url, url).to_s}
    rescue
      printsjis "#{board_name}のスレ(#{thread_url})が読み込めません。\n"
    end
  }
  printsjis "完了\n"

  printsjis "レスから、画像URL一覧を取得中..."
  res_url_list.each{ |res_url|
    begin
      res_main = fetch(res_url).body.toutf8
      picture_list |= res_main.scan(/<img src="([^"]+\.(?:jpe?g|gif))"/).flatten.map{|url| URI.join(res_url, url).to_s}
    rescue
      printsjis "「#{board_name}」のレス(#{res_url})が読み込めません。\n"
    end
  }
  printsjis "完了\n"

  # 画像URL一覧を表示
  #p picture_list

  FileUtils.mkdir(now_board.tosjis) rescue "ディレクトリが既に存在します。"

  printsjis "画像取得を開始します。\n"
  picture_list.each {|picture_url|
    printsjis picture_url + "..."
    #ヘッダーを取得して、ファイルサイズを検査する。
    pict_uri = URI.parse(picture_url)
    file_name = now_board.tosjis + "/" + File.basename(picture_url)
    file_size = FileTest.size?(file_name) || 0

    begin
      Net::HTTP.start(pict_uri.host, pict_uri.port) do |http|
	res = http.head(pict_uri.path)
	if file_size == res['Content-Length'].to_i
	  print "Already gotton!"
	else
	  rename_file(file_name) if file_size != 0
	  res = http.get(pict_uri.path)
	  open(file_name, "w") {|f|
	    f.binmode
	    f.write res.body
	  }
	end
      end
    rescue
      ;
    end
    printsjis "done.\n"
  }
  printsjis "画像取得が完了しました。\n"
end

threads = Array.new
for board_name in %w( ゲーム 本 美術デザイン 風景 ここどこ? 芸能 テレビ スポーツ 爆笑画像 いろいろ おっぱい おしり まんすじ 脚 パンチラ 下着 水着 コス制服ユニ 二次元 キャンギャル お姉さん SM 外国人 [特集]透け透け デスクトップ 映画 1960年代 1970年代 1980年代 1990年代 gif エロ全般 )
#for board_name in %w( gif )
  t = Thread.new do
    get_all_pictures(board_name, board_list[board_name])
  end
  threads.push(t)
end

puts "Waiting for thread."
threads.each {|t| t.join}
puts "done."