RubyonRailsにスクレイピング機能を持たせてみる。

## はじめに
今回は連載企画第２回目ということで、スクレイピングについて書いていこうと思います。
### これまでに書いた記事はこちら
- [【個人開発】日用品を自動で購入してくれるアプリを作りました。](https://qiita.com/prg_mt/items/decb7912e82e6384fafb)
- [Ruby on RailsのアプリにLINEを組み込む。](https://qiita.com/prg_mt/items/b3238ebfae1a3df67cab)

## gemの導入
まずはじめに、gemを導入します。
以下の記述をgemfileに追加してください。
また、seleniumはRSpecで通常使用するものになりますので、`group :test do ~ end`のブロックの中にすでに記載がある場合はブロック外に記載を移すようにしてください。

```ruby:Gemfile
# selenium
gem 'selenium-webdriver'
```

上記を記載しましたら`bundle install`を実行してください。

## ChromeDriverとChormeのインストール
次にスクレイピングをする際に、使用するwebブラウザを導入していきます。
以下のコマンドを使用するか、公式サイトからChromeをインストールしてください。

```terminal:terminal
sudo apt update
sudo apt upgrade
wget --version
wgetがインストールされていない場合は「sudo apt install wget」を実施
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome-stable_current_amd64.deb
インストール中にエラーが発生したら「sudo apt-get install -f」を実施してエラーを修復
google-chrome
```

上記コマンドを入力してChromeが起動すればインストール成功です。

### ChromeDriverのインストール
```terminal:terminal
brew install chromedriver
```
上記の設定が完了しましたら、ローカルマシン上でスクレイピングする準備が整いました。

## (余談)selenium公式のDockerimageを使う
余談ですが、selenium公式がブラウザとドライバーを内包したコンテナイメージを提供してくれています。
aws上などでスクレイピングを動かすなどを想定しているのであればこちらの方が環境構築の面で行いやすいかと思います。

```yml:Docker-compose.yml
chrome:
    image: seleniarm/standalone-chromium
    ports:
      - 4444:4444
      - 7900:7900 #7900番ポートにアクセスするとRSpecのように自動制御されているブラウザが確認できます。
    shm_size: "2gb"
    tty: true
```

### 注意点
m１以降のarmアーキテクチャのmacをお使いの場合は`image:seleniarm/standalone-chromium`を使用してください。
chromeをDocker上で稼働させようとするとgoogle公式が提供しているブラウザでは対応していないためです。
intel製チップを搭載のmacであったり、amdアーキテクチャ搭載のマシンにつきましては`image: selenium/standalone-chrome`で大丈夫です。

## seleniumで使用するdriverの構築を行う。
ここからは実際にdriverを作成してスクレイピングを行う処理を書いていきます。
スクレイピングを組み込みたい部分に書いてください。
今回はActiveJobに書いていきます。

```ruby:jobs/selenium.rb
require "selenium-webdriver"

class SeleniumJob < ApplicationJob
    queue_as :default

    def perform(*args)

        @wait_time = 10 
        @timeout = 180
    
        Selenium::WebDriver.logger.output = File.join("./", "selenium.log")
        Selenium::WebDriver.logger.level = :warn
    
        options = Selenium::WebDriver::Chrome::Options.new
        options.add_argument('--headless')
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-dev-shm-usage')
        options.add_argument('--window-size=1280x800')
        ua = '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'
        options.add_argument("--user-agent=#{ua}")
    
        @driver = Selenium::WebDriver.for :remote, url: 'http://chrome:4444/wd/hub', capabilities: [:chrome], options: options
    
        @driver.manage.timeouts.implicit_wait = @timeout
        wait = Selenium::WebDriver::Wait.new(timeout: @wait_time)
〜以下省略〜
```

performメソッド内にdriverの設定を記載しています。
- `@timeout`と`@wait_time`については探しているhtml要素が見つからない時に何秒待つかを設定しています。
- `options`ではブラウザの設定を追加しています。
    - `--headless`はブラウザを立ち上げないで実行するオプションで、GUIを持たないマシンでも動作できるようにしてくれます。awsでは必須です。
    - `--no-sandbox`はsandboxモードを解除します。
    - `--disable-dev-shm-usage`はクラッシュする確率を減らします。
    - `--window-size=1280x800`はウインドウサイズを1280×800に固定します。レスポンシブ対応しているブラウザで要素を見つけ出せないという事態を予防するために設定しています。
    - `--user-agent=#{ua}`はuaという変数に格納されている情報にユーザエージェントを書き換えます。これにより自動制御プログラムでスクレイピングしているという情報を相手のサーバに対して隠すことができます。
- `Selenium::WebDriver.for :remote, url: 'http://chrome:4444/wd/hub', capabilities: [:chrome], options: options`について
    - `:remote, url: 'http://chrome:4444/wd/hub'`でリモート環境にあるブラウザを制御できるようにしています。Dockerの場合はこちらで行ってください。Docker以外の場合はこの箇所を`:chrome`としてください。
    - `capabilities: [:chrome]` では使用するdriverを指定しています。公式では`desired_capabilities:`となっていますが、自分の環境では動作しなかったため今後推奨されるこちらを使用しております。

ここまでで、driverの構築と設定が完了しました。

## 作成したdriverを用いて実際にスクレイピングをしてみる。
今回はdriverに使えるメソッドを紹介しますので、実際に動かしてみてください。
- `driver.get(url)` urlで指定したサイトに遷移します。
- `driver.find_element(:html要素 指定するもの)` 表示されたwebページの中から指定されたhtml要素を探し、最初の1つ目を返します。なければエラーを返します。
- `driver.find_elements(:html要素 指定するもの)`表示されたページの中から指定されたhtml要素を探し、配列に格納して返します。1つもない場合、エラーになりません。そのため、if文を使って分岐処理を書けます。
- `driver.submit` find_elementで見つけてきたsubmitボタンを押します。
- `driver.click` find_elementで見つけたsubmitやリンクをクリックします。
- `driver.title` webページのタイトルを取得します。

このメソッド以外にもたくさん使用できるものはありますので、以下の公式サイトを確認してみてください。
https://www.seleniumqref.com/api/webdriver_abc_ruby.html


## 終わりに
いかがでしたでしょうか。
連載企画第２回となる今回はseleniumを用いたスクレイピングをRubyで実行するというものでした。
特に私の環境特有のものかもしれませんが、公式リファレンスに書かれている`desired_capabilities:`を使用してドライバーを作成する際にそんなものないよとエラーになってしまい非常に苦労しましたので、同じエラーの方は少しでも参考になれば幸いです。
次回書くとしたら、AWSとDockerあたりかな〜とふんわり考えています。
その際はどうぞお付き合いをお願いいたします。

## 参考文献
https://www.seleniumqref.com/api/webdriver_abc_ruby.html
https://www.selenium.dev/ja/documentation/
https://zenn.dev/usmoo/articles/133bb9ab4864d7
https://morizyun.github.io/web/selenium-cheat-sheet.html
https://qiita.com/rubys8arks/items/84acba9171d215053b24
https://qiita.com/y-agatsuma/items/ea2c9845ee0a931d5c9c
https://github.com/SeleniumHQ/selenium/tree/trunk/rb
https://github.com/seleniumhq-community/docker-seleniarm