こんにちは、アルバイトのarakiです.

最近は感染症の流行で外に出る機会も減りました.人の少ない時間帯に散歩などしたいものですが、季節柄、暑さが厳しいかと思えば雨が強かったりでおっくうになってしまうものです.それでも、室内が湿気ないように外の空気を取り入れるくらいはしたいですね.

そういうわけで、今回はRubyとSeleniumを使ってWebサイトにあるファイルを"取り入れる"、つまりダウンロードする方法についてご紹介します.

通常の場合

先にソースコードを貼ります.


require 'selenium-webdriver'

download_path = File.absolute_path('./dl_files') # 現在位置にあるdl_filesディレクトリへのパス.

# windows用
driver_path = '/mnt/c/Users/driver/chromedriver.exe'
if File.exist?(driver_path)
  Selenium::WebDriver::Chrome::Service.driver_path = driver_path
  download_path = download_path.gsub(%r{/mnt/c}, 'C:').gsub(%r{/}, "\\")
end

download_pref = {
  'prompt_for_download'=> false,
  'default_directory'=>   download_path,
  'directory_upgrade'=>   true
}

options = Selenium::WebDriver::Chrome::Options.new
options.add_preference(:download, download_pref)

client = Selenium::WebDriver::Remote::Http::Default.new

driver = Selenium::WebDriver.for :chrome, http_client: client, options: options
driver.manage.timeouts.implicit_wait = 10

# seleniumの設定ここまで

sleep 1
driver.navigate.to 'https://chromedriver.storage.googleapis.com/index.html?path=84.0.4147.30/'

# テキストが'chromedriver_win32.zip'となっているa要素.
download_link = driver.find_elements(:css, 'a').find { |a| a.text.include? 'chromedriver_win32.zip' }

download_link.click
sleep 3

driver.quit

これは、seleniumでChromeDriverのダウンロードページへ行き、zipファイルをダウンロードするプログラムです.

最初のdownload_pathにはダウンロード先に指定したいディレクトリのパスを書きます.

また、#WSL用 とされている箇所では、指定されたパスにファイルがあればWSLで実行しているとみなしてWSL用の処理をしています.

ここでは、Selenium::WebDriver::Chrome::Service.driver_path = driver_pathによって指定したパス上のChromeDriverを使い、 download_path = download_path.gsub(%r{/mnt/c}, 'C:').gsub(%r{/}, "\\")によってパスをWindows向けのものにします.

例えば、/mnt/c/Users/dev/dl_filesC:\Users\dev\dl_filesにします.

このコードを書くことによって、MacユーザーもWSLユーザーも同じプログラムを実行できます.私は社内で少数派のWSLユーザーなので、この書き方を見つけたときはとても便利だと感じました.

以下、Seleniumの各種設定をした後に、ダウンロードページへでのダウンロード処理を行っています.なお、download_prefのところでハッシュロケットを使っているのが奇妙に見えるかもしれませんが、どうやらキーをStringにしないとうまくいかないようです(注1).

Seleniumでは、実際のページでダウンロードリンクを踏めばダウンロードできるのと同様に、リンクの要素をクリックすることでダウンロードができます.

ここで、ダウンロードしてすぐquitしないように気を付けましょう.ダウンロードする前にChromeを閉じてしまうと、ダウンロードができません.

サンプルでは3秒スリープを挟んでいますが、実際に使うときはディレクトリにファイルが落ちたことを確認してからquitする、のような処理を挟むと確実にダウンロードできるかもしれません.

ヘッドレスの場合

やはり先にソースコードを貼ります.


require 'selenium-webdriver'

download_path = File.absolute_path('./dl_files') # 現在位置にあるdl_filesディレクトリへのパス.

# WSL用
driver_path = '/mnt/c/Users/driver/chromedriver.exe'
if File.exist?(driver_path)
  Selenium::WebDriver::Chrome::Service.driver_path = driver_path
  download_path = download_path.gsub(%r{/mnt/c}, 'C:').gsub(%r{/}, "\\")
end

options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-popup-blocking')
options.add_argument('--disable-gpu')

caps = Selenium::WebDriver::Remote::Capabilities.chrome(
  'chromeOptions' => { w3c: false }
)
client = Selenium::WebDriver::Remote::Http::Default.new

driver = Selenium::WebDriver.for :chrome, desired_capabilities: caps, http_client: client, options: options
driver.manage.timeouts.implicit_wait = 10

bridge = driver.send(:bridge)
path = "/session/#{bridge.session_id}/chromium/send_command"
command_hash = {
  cmd:    'Page.setDownloadBehavior',
  params: {
    behavior:     'allow',
    downloadPath: download_path
  }
}
bridge.http.call(:post, path, command_hash)

# seleniumの設定ここまで

sleep 1
driver.navigate.to 'https://chromedriver.storage.googleapis.com/index.html?path=84.0.4147.30/'

# テキストが'chromedriver_win32.zip'となっているa要素.
download_link = driver.find_elements(:css, 'a').find { |a| a.text.include? 'chromedriver_win32.zip' }
download_link.click
sleep 3 # ダウンロードの待機

driver.quit

処理内容は通常の場合と同じです.ただし、ヘッドレスのseleniumでは設定の仕方が通常のものとは少し異なります(注2).

おわりに

以上になります.今年は体を動かす機会が減ってしまった方も多いかと思われます.プログラミングをしているのも良いですが、たまには椅子から立ち上がって体を動かしたほうが良いかもしれませんね.それでは.

脚注

(注1): Chrome download prefs stopped working when passed as symbols; directory_upgrade option

(注2): chromedriverのheadlessでファイルをダウンロードする

Pocket