Seleniumで、Chromeをヘッドレスで使用する場合、9つの注意(ツボ)と、私が設定したオプション設定を含めたSeleniumのソースコードを紹介します。


Selenium 9つのツボ

  1. WebDriverは自動的にバージョンをあわせる
  2. UserAgentを指定する
  3. ダウンロードプレファレンスを設定する
  4. Waitをデフォルトでなく自分で設定する
  5. Nokogiriでパースする
  6. 認証があるサイトはリンクでたどる
  7. ログ表示を調整する
  8. untilで待っても、sleepを入れたほうが安心
  9. エラー時でも必ず「quit」する



WebDriverは自動的にバージョンをあわせる

ChromeのバージョンとWebDriverのバージョンが異なるとエラーになってしまいます。めんどうなので自動的にバージョンチェックして、異なっていたらWebDriverをダウンロードするようにします。


UserAgentを指定する

ヘッドレスの場合、UserAgentを設定しないとUserAgentheadlessであることが記載されてしまいます。するとスクレイピングを防ぐため、その文字を検知して拒否され get できない場合があるので、なにかしら設定しておく必要があります。


ダウンロードプレファレンスを設定する

通常使用のChromeのダウンロード設定とSeleniumのダウンロード設定は別のため、クリック等でファイルをダウンロードしたい場合は、オプションに追加設定する必要があります。


Waitをデフォルトでなく自分で設定する

Seleniumでは、自分でタイムアウト処理は作れないため、デフォルトの設定ではなく自分でタイムアウトとポーリング間隔を設定しておくと便利です。 ちなみに、暗黙的な待機(implicit_wait)は、明示的な待機と混在はしないほうが良いとのことです。


Nokogiriでパースする

Seleniumでは、途中エレメントのHTMLを少しずつ絞っていくなどはできないので page_source ですべて取得したら、Nokogiriでパースするほうが楽です。


認証があるサイトはリンクでたどる

ユーザーIDとパスワードを入力するサイトは、例えログインした後でも、所望のサイトURLを直接 get はできません。セッションが異なるためと思われます。ログインしたサイト上で、リンクをクリックして所望のページにたどっていく必要があります。


ログ表示を調整する

ChromeのログとSeleniumのログは別です。大量に表示されてしまう場合は、ログレベルを調整します。デバッグするときはフルで表示したりもできます。 ちなみにChromeオプションで、 --disable-logging は効きませんでした。


untilで待っても、sleepを入れたほうが安心

wait.until で処理が通っても、その後確実性を求めるのであれば、sleepを少し入れておいたほうが安心です。


エラー時でも必ず「quit」する

Selenumは、途中でエラーになる場合や、デバッグ中強制終了することがあるので、 ensure などで必ず quit しましょう。



Seleniumのソースコード

Seleniumモジュール(Mylibとする)

require 'webdrivers'
require 'selenium-webdriver'

module Mylib

  #***********************************************************
  # wdRunDuring
  #   in:
  #     vv : visible[true/false]    (default=false)
  #     dl_dir : Downloadフォルダー(default=Downloads)
  #   use:
  #     wd:webdrivers
  #     wt:wait
  #***********************************************************
  def wdRunDuring(vv: false, dl_dir: "#{ENV['USERPROFILE']}\\Downloads")

    # Webdrivers.logger.level = :DEBUG    # debugする場合
    # ChromeとWebDriverのバージョンをあわせる
    install_dir = (ENV["USERPROFILE"] + '/webdrivers').gsub!("\\","/")
    Webdrivers.install_dir = install_dir

    # WebDriver
    begin
      # Selenium::WebDriver.logger.level = :debug   # debugする場合
      # Chrome Options
      opt = Selenium::WebDriver::Chrome::Options.new
      opt.add_argument('--headless') if !vv
      opt.add_argument("--user-agent=DUMMY")    # UAにHeadlessと書かれるのを防ぐ
      opt.add_argument("--log-level=3")         # 余計なログを非表示(LOG_FATALにする)
      opt.add_argument('--disable-gpu')         # いらないと思うが。。
      opt.add_argument("--blink-settings=imagesEnabled=false")  # 画像を読み込まないで軽くする
      opt.add_argument("--disable-extensions")  # なくてもエクステンションは起動しない
      opt.add_argument('--start-maximized')     # 表などが切れるのを防ぐ

      # Chrome Options for download
      dl_pref = {
        'prompt_for_download'=> false,
        'default_directory'=>   dl_dir.gsub!("/","\\"),  # バックスラッシュで絶対Path
        'directory_upgrade'=>   true
      }
      opt.add_preference(:download, dl_pref)

      # WebDriver Wait
      wd = Selenium::WebDriver.for :chrome, options: opt
      wt = Selenium::WebDriver::Wait.new(:timeout => 10, :interval => 1)  # 秒

      yield wd,wt

    ensure
      wd.quit
      print "\nWebDriver Quit"
    end

  end
  module_function :wdRunDuring

end


Selenium使用例

require 'Mylib'
require 'nokogiri'

Mylib.wdRunDuring(){ |wd,wt|

  # ログイン例(下記サイトは架空)
  wd.get "https://aaa/bbb.php"
  wt.until{ wd.find_element(name: 'uname') }.send_keys  "namae"
  wt.until{ wd.find_element(name: 'pass') }.send_keys   "pasuwa-do"
  wt.until{ wd.find_element(css: 'th.login input') }.click
  pp "login  #{wd.title}"
  sleep 3

  # ダウンロード例
  wt.until{ wd.find_element(link_text: '週間データ') }.click
  pp "click  #{wd.title}"
  sleep 3
  ee = wt.until{ wd.find_element(css: 'td.weekly_data a') }
  ee.click
  pp "download  #{ee.attribute('title')}"

  # リンク取得例(下記サイトは架空)
  uu = "https://datalink.com"
  wd.get uu
  doc = Nokogiri::HTML(wd.page_source)

  uuu=[]
  doc.css("a").each{ |atag|
    hr = atag[:href]
    uuu << uu + hr  if hr.include?("/gazou")
  }
  uuu.uniq!   # 重複排除
  pp "get link  #{uuu.size}"

  ...後処理...
  ...後処理...
  ...後処理...
}


uuu << uu + hr if hr.include?("/gazou") は、すべてのリンクから必要な画像リンク(例えばURLに /gazou が含んでいるリンク)のみを選別し、ベースの親URLを追加して完全なリンクにして uuu 配列に格納する例です。(HTMLソース上のURLが相対パスだった場合



参考

2021/12/19