Ruby + Seleniumでweb上のファイルをダウンロードする(Windows)

## きっかけ
引っ越しの準備や掃除の時など、あまり脳を使わない作業の際は耳が寂しくなります。
いつもだったら好きな音楽を聞きながら作業をしていました。
しかし、最近会社のチームメンバーから「技術系のポッドキャスト聞きながらやるのもオススメですよ」と、いいアドバイスをもらいました。
> いかなる時でも勉強しろという圧

そこでオススメされたのが[fukabori.fm](https://fukabori.fm/)でした。

なんと都合のよいことに、各回のポッドキャストをmp3でダウンロードもできます。
これなら家での作業中だけでなく、散歩中など外にいるときにもギガを消費せずにポッドキャストを楽しむことができます。

ただこのポッドキャストは2021/09時点で57回もやっています。
それをチマチマと1回ずつDLするのはめんどくさい。
ということで、お手軽に書けるRubyでまとめてDLする用のツールを作ることにしました。

## 準備
趣味用のMacとかがないのでWindowsでやります。
ということでRuby入れるところからやります。

### Ruby導入
1. RubyInstallerをDL
    *  https://rubyinstaller.org/downloads/
    *  ↑のページから自分のPCのビット数にあったInstallerをDL
    *  基本的にWITH DEVKITの最新版で大丈夫
    *  今回は3.0.2を使用
    *  ビット数の確認方法
        1. タスクバーのwindowsボタン
        1. 設定ボタン(歯車マーク)
        1. システム -> 詳細情報
        1. システムの種類に記載されている
1. Installerを実行
    * Installerはウィザードに従うだけで大丈夫
    * インスコ後に出てくる完了画面で「Run 'ridk install' to setup MSYS2 and development toolchain.」という文言とチェックボックスにチェックがついた状態で表示される
    * 今回は必要ないのでチェックを外してもよい、インストールしておいても損はしない

### selenium webdriver導入
* コマンドプロンプトを開き、↓のコマンドを入力
* `gem install selenium-webdriver`
* ~ gem installedと最後に表示されればOK(~の部分は数字)

### Ruby FFI導入
* コマンドプロンプトを開き、↓のコマンドを入力
* `gem install ffi`
* 1 gem installedと最後に表示されればOK

### Chrome Driver導入
* https://chromedriver.chromium.org/downloads
* ↑のサイトからChrome DriverをDL
* OSごと種類のzipファイルが用意されているので、今回はwin32.zipをDL
* ChromeのバージョンとDriverのバージョンが一致している必要がある
* DLしたファイルを解凍
* ChromeDriver.exeを`C:\Ruby23\bin`に移動
* (↑のディレクトリはRubyインストール時のデフォルト)

## コードを書きます

所要時間1時間半ぐらい。
業務でRuby使ったことないし、体系的にお勉強したこともないので、コードは超適当です。

```ruby
require "selenium-webdriver" # (1)
require 'open-uri'
require "fileutils"
driver = Selenium::WebDriver.for :chrome

# ページにアクセス (2)
driver.get "https://fukabori.fm/"

# ディレクトリ作成 (3)
directoryName = "C:\\fukabori"
unless File.directory?(directoryName) 
  FileUtils.mkdir_p(directoryName)
end

# ページ内にあるリンク要素を取得 (4)
elements = driver.find_elements(:xpath, '/html/body/main/div/div/article[*]/h1/a')

# リンク先のURLのリスト作成 (5)
urls = elements.map {|element| element.attribute('href') }
# ダウンロード処理 (6)
urls.each do |url|
  driver.get(url)
  urlElement = driver.find_element(:xpath, '/html/body/main/div/article/section/p[2]/small/a')
  mp3Url = urlElement.attribute('href')

  tmpfile = URI.parse(mp3Url).open # (7)

  titleNameElm = driver.find_element(:xpath, '/html/body/main/div/article/header/h1/a')
  titleName = titleNameElm.text.tr('/', '')
  fileName = directoryName + "\\" + titleName + ".mp3"
  FileUtils.mv tmpfile.path, fileName  # (8)
  tmpfile.close
end
```
https://github.com/kei3524848/fukabori_downloader/blob/develop/downloader.rb

### (1)
Selenium動かすのとChrome動かすのとファイルDLするためのモジュールの準備。
おまじないのようなものです。

### (2)
driver.get urlでChromeが起動して指定したURLを開きます。

### (3)
mp3ファイルを配置するディレクトリを作成します。
なんとなくディレクトリがなかった場合に作る処理入れました。

### (4) (5)
fukabori.fmのページでは、ポッドキャストの各回のリンクが1ページに全部載っています。
そこでリンクの要素＝aタグをすべて取得して、そこのhrefの値のリストを作成して、処理をぐるぐる回すことにしました。

要素の取得はxpathを用いて指定します。
ページで右クリック -> 検証なり、Ctrl + Shift + iなりで開発者ツールを開きます。
取得したい要素を選択して、右クリック -> Copy -> Copy XPathで取得できます。
今回の場合、要素を指定するとarticle[]の部分の数字が可変なので、ワイルドカードを指定して、すべてのリンク要素を取得しています。

### (6)
リンク先のURL群でループ処理をします。
ページを開いてmp3のURLを取得し、それをDLする処理を繰り返します。

### (7) (8)
mp3ファイルだと、単純に要素をクリックする処理をしてもvideoタグが埋め込まれた再生ページに遷移してしまいます。
そこで、ファイルデータを取得しておいて、FileUtilを使って指定したディレクトリに書き込むみたいなことをします。
ファイル名は記事タイトルの要素から文字列を取得して、それを使用しています。
タイトルに / が含まれているので、そのまま使うとディレクトリが分かれる扱いになってエラーが起きるので、/ の文字列を取り除いて使っています。

## 結果
![SnapCrab_NoName_2021-9-23_17-58-8_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392448/c5fc41ea-17b9-78ef-5fb3-799700ffd397.png)
いい感じにできました。
これで単調作業やお散歩のときにも捗りそうですね。

## その他

* コードに汎用性はない。
    * 対象のページの内容に強く依存するため。
    * でもxpathをそのページ用に変えて、うまく辻褄合わせれば他のページでも使える。
* Headlessで動かすこともできるが、今回は逆に自動でページ遷移してる様子が見てみたかったので、特に設定せず書いた。
* 新しい回が追加されたときのことをガン無視している。
    * ローカルのファイル名とページのタイトル名の一致判定をして、ないものだけDLみたいな処理に拡張したほうがよさげ。