Ruby seleniumを使ってのスクレイピング 基本から応用まで

Rubyでのスクレイピングを考えた時に、WEBサイトにJavascriptが使われていた場合Selenium一択になると思います。
そんなSeleniumの使い方をインストールから実行までまとめてみました。

目次

1、seleniumライブラリ
 ・よく使うやり方 例
2、ヘッドレスオプション(ブラウザ表示をなくす)
3、スクリーンショットの撮り方
4、画像を保存
5、ページ遷移
6、find_elementでNoSuchElementErrorが発生したとき
7、iflame
8、seleniumでスクロール
9、seleniumでページネーション
10、新規タブの作成

1.seleniumのインストール


まずはgemのインストール

$ gem install selenium-webdriver



以前はgemのselenium-webdriverを使うにはchromdriverをインストールしないといけませんでした。
"selenium-webdriver"のバージョン4.0.0以降は"selenium-manager"という新機能が追加されたので自動的にPATHを指定してくれるのでダウンロードもしなくてもよくりすごく便利になりました。



基本の使い方

require "selenium-webdriver"

driver = Selenium::WebDriver.for :chrome 

# googleのページを立ち上げる
driver.get("http://google.com")

#googleの検索フォームに"example.com"と文字を打ち込み検索をかける
element = driver.find_element(name: "q")
element.send_key("example.com")
element.submit
# 5秒間停止
sleep 5 

# ブラウザを閉じる
driver.quit
* googleで"example.com"の文字を検索して閉じるプログラム


よく使う要素を簡単に取得する方法

require "selenium-webdriver"
driver = Selenium::WebDriver.for :chrome 
driver.navigate.to "http://google.com"
#デベロッパーツールを開いて要素を右クリック→copy→xpathをcopy「//*[@id="rso"]/div[1]/div/div/div/div/div/div[1]/a/h3」
driver.find_element(:xpath,'//*[@id="rso"]/div[1]/div/div/div/div/div/div[1]/a/h3').click


find_elementを繋げてattribute("href")でURLを取得する場合

require "selenium-webdriver"

driver = Selenium::WebDriver.for :chrome 
nashi = driver.get("https://nashiblog.netlify.app/")
element = driver.find_elements(:tag_name,"a")[2]
puts element.attribute("href")

結果=> https://nashiblog.netlify.app/blog/javascript/rubymatch



eachでループをまわしてa属性のテキストを配列で取得

require "selenium-webdriver"

d = Selenium::WebDriver.for :chrome 
text = []
d.get("https://nashiblog.netlify.app/")
d.find_elements(:tag_name,"a").each do |u|
    text << u.text
end
p text
sleep 3
結果=> ["Nashi Blog", "プログラミング", "ruby 正規表現 【まとめ】", "ruby スクレイピング テクニック", "ruby 競技用プログラミング基本 【初心者用】", "ruby 要素数の取得", "ruby 配列の小技", "gitの基本 初心者用", "rails つまづきポイント", "heroku デプロイまで", "rails javascriptの読み込み方", "rails bootstrap導入", "プログラミングブログ一覧"]



2.スクレイピングの際にブラウザ表示をなくす場合(ヘッドレスオプションの設定)

options = Selenium::WebDriver::Chrome::Options.new
options.headless!  #ヘッドレスオプションを設定(ブラウザ表示をなくします)

d = Selenium::WebDriver.for :chrome,options: options
d.get("https://www.amazon.co.jp/")
d.find_element(:name,"field-keywords").send_keys("makbook") #検索に文字を入力
sleep 2
d.find_element(:id,"nav-search-submit-text").submit #inputタグに文字を入力後送信します
sleep 3



3.スクリーンショットの撮り方

require "selenium-webdriver"

options = Selenium::WebDriver::Chrome::Options.new
options.headless!

d = Selenium::WebDriver.for :chrome,options: options

d.get("https://www.amazon.co.jp/")
d.find_element(:name,"field-keywords").send_keys("makbook")
d.find_element(:id,"nav-search-submit-text").submit
sleep 5
d.save_screenshot("hoge.png")
d.quit


実行すると「hoge.png」の出来上がりです。


よくある要素の取得など

# ID要素取得
element = driver.find_element(:id, 'id')

# クラス要素取得
element = driver.find_element(:class, 'class')

# タグ要素取得
element = driver.find_element(:tag_name, 'a,div,img,h2')

# リンクテキスト要素取得
element = driver.find_element(:link_text, 'text名')

# a要素のテキストの部分一致する要素を取得する
element = driver.find_element(:partial_link_text, 'text名')

# XPath要素取得
element = driver.find_element(:xpath, "//a[@href='//']")

# css要素取得
elment = driver.find_element(:css, '.class,#id')

#要素が期待する値になるまで待つ
wait = Selenium::WebDriver::Wait.new(timeout: 20)
wait.until { driver.find_element(:id, 'some_id').text == 'Ajaxで生成されたテキスト' }

#windowの移動
driver.switch_to.window(some_id)

# 要素が1つ以上あるか確認
driver.find_elements(:id, 'any-id').size >= 1

#javascriptを使うとき
driver.execute_script(script)


キーボードのエンターキー入力など

driver.find_element(:id, 'any_id').native.send_keys(:return)
driver.find_element(:id, 'any_id').native.send_keys(:enter)


チェックボックス

unless driver.find_element(:id, 'radio-id').selected?
  driver.find_element(:id, 'radio-id').click
end
driver.find_element(:id, 'some_check_box').clear # 選択を解除


セレクトボックス

select = Selenium::WebDriver::Support::Select.new(driver.find_element(:id, 'some_select_id'))
select.select_by(:value, 'value')    
select.select_by(:text, 'テキスト') 
select.select_by(:index, 1)           

all_options = select.find_elements(:tag_name, 'option') # すべてのオプション


4.画像を保存


require "selenium-webdriver"
require "open-uri"

def get_items 
    d = Selenium::WebDriver.for :chrome
    d.get("https://www.amazon.co.jp/")
    d.find_element(:xpath,"//img")
    elements = d.find_elements(:class,"s-image")

    url = []

    elements.each do |e|
        url << e.attribute("src")
    end
    create_images(url)
end

def create_images(urls)
    urls.each_with_index do |url,i|
        filename = "test#{i}.jpg"
        open(filename,"wb") do |file|
            open(url) do |data|
                p file.write(data.read)
            end
        end
    end
end
get_items


配列に画像URLを入れて、それをファイルに書き写します。


5.ページ遷移

xpathで要素を取得し、そのhref属性の値にページ遷移するやり方

require "selenium-webdriver"

d = Selenium::WebDriver.for :chrome
texturl = d.find_element(:xpath,'//*[@id="search"]/div[1]/span/a').attribute("href")
sleep 1
d.navigate.to("#{texturl}")
puts d.find_element(:id,"some_id").find_element(:tag_name,"span").text  #textがspanタグの場合
sleep 1
d.quit



ページネーションによる要素全取得の方法

driver.get("URL")
page = 1
loop do 
   page += 1
   next = driver.find_element(#pageでボタンの番号があるとこ)
   break unless next  #要素がなくなったら終わり
   next.click
end


6.find_elementでNoSuchElementErrorが発生したとき


webページの表示は条件によって変わります。そんな要素をfind_elementでマッチさせた時期待した要素が見つからない場合があります。
そんなとき、結果が「nil」で返ってくるとおもいきやエラーを処理しないとプログラムがそのまま終了してしまいます。
そこで例外処理を使ってエラーを対処します。

class SomeClass 
    include Selenium::WebDriver::Error

    def some_method(items)
        items.each do |i|
            begin
                title = col.find_element(:class,"title")
            rescue NoSuchElementError #エラーを捕捉する
                p "エラー発生!"
                next
            end
            puts title.text
        end
    end
end


seleniumのエラー用のモジュールをincludeしてbegin-endでエラーを捕捉し対処します。

スクレイピングするとき要素が取得できない場合注意する点
iflameのタグがあるとflame位置が間違ってることがあります

7.iflame

frame = driver.find_element(:xpath,"~~")
driver.switch_to.frame(frame)

もしくわ

driver.switch_to.frame



セレクタからフレームを見つけて切り替える

iframe = driver.find_element(:css,'#modal > iframe')
driver.switch_to.frame iframe



nameまたはIDを使う

driver.switch_to.frame 'name or ID'



iflameやflameを終了させ、flameをデフォルトの位置に戻す

driver.switch_to.default_content


data属性の要素取得


d.find_element(:xpath,"~~~~").attribute("data-xxx")


javascriptを実行させる

driver.execute_script('return window.location.pathname')


8.seleniumでスクロール


seleniumでスクレイピングをする際、スクロールをしないと画面がでてこず要素を取得できないこともあるかと思います。
その場合に私がよくやるやり方です、

require "selenium-webdriver"
require "csv"


class TokyoSearch
    
    URL = "https://〜〜"
    def initialize
       options = Selenium::WebDriver::Chrome::Options.new
       options.headless!
       @d = Selenium::WebDriver.for :chrome,options: options
     end
    def run
        search
        scroll
    end
    def search
        @d.get(URL)
        list_names = @d.find_element(:class,"pageContent").find_elements(:tag_name,"li")
        list_names.each do |o|
            name = o.find_element(:tag_name,"a").find_element(:class,"thumbList__name").text
            url = o.find_element(:tag_name,"a").attribute("href")
            CSV.open("tokyo.csv","a") do |csv|
                csv << ["スタジオ名:#{name}","URL:#{url}"]
            end
            sleep 1

            #ここからスクロールするためのコーディング
            last_height = @d.execute_script("return document.body.scrollHeight") #カレントの高さ
            while true
                1.step(last_height, last_height/10).each do |height| 
                    @d.execute_script("window.scrollTo(0,#{height})")
                end
                sleep 1
                new_height = @d.execute_script("return document.body.scrollHeight") #高さを動かしながらスクロール
                if new_height == last_height #ウェブページの更新がなくなったら停止
                    break
                end
                last_height = new_height
            end
        end
        
    end


9.seleniumでページネーション


loop do #ループを回す
    begin
        pagenation = d.find_element(:class,"a-pagination")
        next_button = pagination.find_element(:class,"a-last")
        next_link = next_button.find_element(:css,"a")
    rescue NoSuchElementError
        break #要素が無くなったら停止
    end
    next_link.click
end



10.新規タブの作成


新規タブを作るにはjavascriptでタブを作ってからそこにURLを開きます

require "selenium-webdriver"
@d = Selenium::WebDriver.for :chrome

@d.execute_script("window.open()")
new_window = @d.window_handles.last
@d.switch_to.window(new_window)
@d.get(url)



クリックして新規タブを作る場合

element.send_keys(:command, :enter)