語り得ぬことについては、沈黙するほかない

世界は事実の総体であり、ものの総体ではない

Rubyでbingから画像をスクレイピングしたよ

唐突ですが、乃木坂46の3期生の顔を機械学習で学習させて未知の画像データから名前を推測するWebアプリケーションを作っています。Webから画像データを取得するスクレイピングの作業の際に、色々とハマったので備忘録的に記させていただきます。

作ろうとしているもの

こちらの記事の乃木坂46版です。人数が多いので3期生限定にしました。

memo.sugyan.com

進捗

顔認識させるために大量の画像データが必要となるので集めることになりました。
ということでbing画像検索から自動でローカルに画像を保存するスクリプトを書きました。
Googleを使わなかったのは、同じことをやろうとしていた人がGoogleよりbingの方がいいよって言っていたためです。

はじめPythonのBeautifulSoupとかScrapyとかのスクレイピング用ライブラリorフレームワークを使おうと思ったのですが、結構難しかったのでRubyで書くことにしました。似たようなスクリプトがネットにあったので参考にさせていただきました。
taremimi.hatenablog.jp

できたコードと解説

ほぼパクリになりました。

require 'uri'
require 'open-uri'
require 'nokogiri'
require 'selenium-webdriver'

class Scraper
  def initialize(prefix, query)
    @prefix = prefix
    @query = URI.escape(query.encode("utf-8"))
    @search_url = "https://www.bing.com/images/search?q=" + @query    
  end

  def scrape_img
    driver = Selenium::WebDriver.for :chrome
    driver.navigate.to(@search_url)

    10.times do 
      driver.find_elements(:class, 'iusc').last.location_once_scrolled_into_view
       current_count = driver.find_elements(:class, 'iusc').length
       until current_count < driver.find_elements(:class, 'iusc').length
        sleep(3)
       end
       sleep(5)
     end

    elements = driver.find_elements(:class, "iusc")
    @array = []
    elements.each do |element|
      @array << element.attribute("m").scan(/","murl\":"(.+)","turl":/)
    end
    @array = @array.flatten!

    @url_array = []
    @array.each do |img|
      if /\.(jpg|png)$/ =~ img.to_s
        @url_array << URI.escape(img.to_s.force_encoding("utf-8"))
      end
    end

    @url_array.each_with_index do |url, i|
      begin
        if /\.(jpg)$/ =~ url
          filename = "#{@prefix}_#{i}.jpg"
        else
          filename = "#{@prefix}_#{i}.png"
        end
        p filename + " << " + url          
        dirname = "#{@prefix}_img"
        FileUtils.mkdir_p(dirname) unless FileTest.exist?(dirname)
        filepath = dirname + "/" + filename
        open(filepath, "wb") do |f|
          open(url.encode("utf-8", invalid: :replace, undef: :replace)) do |data|
            sleep(2)
            f.write(data.read)
          end
        end
        p "できたよ"
      rescue
      p "無理でした"
      end
    end
    driver.quit
  end
end

if __FILE__ == $0
  keywords = {"ririan" => "伊藤理々杏",
              "rentan" => "岩本蓮加",
              "minamin" => "梅澤美波", 
              "momochan" => "大園桃子", 
              "kubochan" => "久保史緒里", 
              "tamachan" => "阪口珠美", 
              "denchan" => "佐藤楓", 
              "renochan" => "中村麗乃", 
              "hazukichan" => "向井葉月", 
              "miichan" => "山下美月", 
              "ayaty" => "吉田綾乃クリスティー", 
              "yodachan" => "与田祐希"
  }

  keywords.each do |prefix, query|
    p prefix
    p query
    scraper = Scraper.new(prefix, query)
    scraper.scrape_img
  end
end

はじめ'Nokogiri'というスクレイピング用のgem(ライブラリ)を使用していたのですが、bing画像検索ではおそらくJavascriptによって動的にページを生成しているので'Nokogiri'では対処できなそうだ、ということで'Selenium'というgemを使うことにしました。この'Selenium'というgemは、プログラムからWebプラウザを操作し、Webサイトが正しく動作するか検証するためのツールだそうです。JavaRubyPythonなどいろんな言語でのサポートがあり、とても便利なのでオススメです。

Scraperクラスのインスタンスを生成するときに引数として'prefix'と'query'を指定します。
'query'では検索したいワードを指定します。'prefix'で指定した文字列がフォルダ名、ファイル名となって出力されます。
f:id:hoshimure-47:20170926225443p:plain

スクレイピングするとき、相手のサーバに負荷をかけないために所々でsleep関数を使っています。
sleepを使わないと短時間で無数のリクエストが相手サーバに送られてしまうので、マナーとしてsleepで1,2秒の間隔を置くらしいです。
そもそも公式のAPIを使っていない時点でマナーとしてどうなのっていう感じはある。

Selenium関連のエラーは、ドライバのバージョンによるエラーがほとんどだそうです。
僕が入れた最新バージョンのchromedriverは2.32でした。
Google Chrome使っている人は「selenium chromedriver」で検索すれば色々情報が出てきます。
qiita.com

zip形式のドライバを解凍して、$which rubyしたときに出てくるフォルダと同じ階層に入れればOKです。
僕の場合はrbenvを使用しているので、'~/.rbenv/shims'としました。


Githubにもあげてみました!pushできました!
github.com