Railsでスクレイピングの環境構築してみた【Rails+Selenium+Heroku】

## はじめに
Rails環境かつ、Heorku環境でもスクレイピングをするための環境構築の備忘録。

ローカル環境でとりあえず動かすことはできても、連続してスクレイピングできなかったり、Heorku環境でスクレイピングができない問題がありました。

最終的にHeorku環境で動作させることができたので、参考になれば幸いです。


## やりたいこと

- Heroku環境でSeleniumのスクレイピングが機能するか確認するためのJobを作成すること

今回はwww.google.comにアクセスして、そのページのタイトルを返すものを作ります。
Googleというタイトルをスクレイピングするだけです。
（あくまで動作確認をするだけに徹しています）


## 環境構築
### Docker環境

元々DockerfileでChromeのインストールとChromeDriverをインストールしていたのですが、連続してスクレイピングできなかったり、Heroku環境で動作しなかったのでchromeのdocker imageを使用します。

この他、appやredis, sidekiqは適宜追加してください。
今回はJobにしてバックグラウンド実行を想定しております。

```compose.yml
services:
  chrome:
    image: selenium/standalone-chrome:latest
    ports:
      - 4444:4444
      - 7900:7900
    shm_size: "2gb"
    tty: true
```

### Service

Jobから呼ぶためのスクレイピングのサービスクラスを作成します。

```selenium_health_check_service.rb
class SeleniumHealthCheckService < ApplicationService
  require 'selenium-webdriver'

  attr_reader :driver

  def call
    @driver ||= setup_driver

    run
  end

  private

  def run
    driver.get('https://www.google.com')
    logger.info "Page title: #{driver.title}"
  ensure
    sleep 3
    driver.quit
  end

  def setup_driver
    options = build_chrome_options
    setup_webdriver(options)
  end

  def build_chrome_options
    options = Selenium::WebDriver::Chrome::Options.new
    options.add_argument('--headless')
    options.add_argument('--disable-gpu')
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-dev-shm-usage')
    options.add_argument('--window-size=1280x800')
    options.add_argument("--user-agent=#{user_agent}")
    options
  end

  def user_agent
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36'
  end

  def setup_webdriver(options)
    if Rails.env.production?
      setup_production_driver(options)
    else
      setup_development_driver(options)
    end
  end

  def setup_production_driver(options)
    chrome_bin_path = ENV.fetch('GOOGLE_CHROME_BIN', nil)
    chromedriver_path = ENV.fetch('CHROMEDRIVER_PATH', nil)
    options.binary = chrome_bin_path if chrome_bin_path
    Selenium::WebDriver::Chrome::Service.driver_path = chromedriver_path if chromedriver_path
    Selenium::WebDriver.for :chrome, options: options
  end

  def setup_development_driver(options)
    Selenium::WebDriver.for :remote, url: 'http://chrome:4444/wd/hub', options: options
  end
end
```

ポイントはlocal環境とHeroku環境でdriverの作成を分岐させています。
こうすることで、Heroku環境でもスムーズに環境構築することができます。
詳しくは後にあります。

また、動作確認でデバックするときに`logger.info`を仕込んでおくとわかりやすいです。

### Job

今回は検証用なのでスクレイピングがすぐにできますが、本来は色々なページを行き来したり、データを取得するので時間がかかります。
そのためバックグラウンドで動作するようにJobにしておきます。
想定する環境によってTaskにすることもあります。

```selenium_health_check_job.rb
class SeleniumHealthCheckJob < ApplicationJob
  queue_as :default
  sidekiq_options retry: false

  def perform
    SeleniumHealthCheckService.call
  end
end
```

このようにしておけばコンソールで実行できます。
```console
SeleniumHealthCheckJob.perform_later

=> Page title: Google
```

### Heroku設定

Herokuでは大きく2つの設定をすればOKです。
これだけでHeroku環境の設定ができるのですごくシンプルです。

#### ①Buildpacksの追加
以下の2つを追加します
- `https://github.com/heroku/heroku-buildpack-chromedriver.git`
- `https://github.com/heroku/heroku-buildpack-google-chrome.git`

#### ②環境変数の追加
以下の2つを追加します
- CHROMEDRIVER_PATH: `/app/.chromedriver/bin/chromedriver`
- GOOGLE_CHROME_BIN: `/app/.apt/usr/bin/google-chrome`


## おわりに
途中手こずりましたが想定の1/4の時間で進めることができました。
参考にさせていただいたドキュメントの著者の方々に感謝しております。


## 参考文献
- [www.selenium.dev](https://www.selenium.dev/documentation/webdriver/getting_started/)
- [docker-selenium](https://github.com/SeleniumHQ/docker-selenium)
- [selenium/standalone-chrome](https://hub.docker.com/r/selenium/standalone-chrome/tags)
- [RubyonRailsにスクレイピング機能を持たせてみる。](https://qiita.com/prg_mt/items/5383d87fccdd7fc2cecf)
- [Heroku でも Selenium を使いたい!](https://qiita.com/sho7650/items/8fe07126f00c8e94e13b)