reCAPTCHAのサイトを毎日自動スクレイピングする (2/7: スクレイピング)


1. [要件定義〜python環境構築](https://qiita.com/kamyu1201@github/items/65a86ec1e28ec9969dcb)
1. **サイトのスクレイピング機構を作る**
1. ダウンロードしたファイル(xls)を加工し、最終成果物(csv)を作成するようにする
1. S3からのファイルダウンロード / S3へのファイルアップロードをつくる
1. 2captchaを実装
1. Dockerコンテナで起動できるようにする
1. AWS batchに登録


# スクレイピング機構の作成

今回のサイトはReactを使っており、javascriptの動作であるためseleniumを使います。

```:ファイル構成をおさらい
├── app
│   ├── drivers          seleniumドライバーを置く
│   └── source           
│       └── scraping.py  処理
└── tmp
    ├── files
    │   └── download     スクレイピングでダウンロードしたファイルを置く
    └── logs             ログ(seleniumログなど)
```

### driverのダウンロード

どのブラウザを使ってseleniumを動かすのか、決める必要があります。
候補としては下記の3つくらいがありそう。

- ヘッドレス専用ブラウザとして一世風靡した？ [PhantomJS](http://phantomjs.org/download.html)
- 最近 headlessモードができて今から主流になりつつある [Chrome](https://sites.google.com/a/chromium.org/chromedriver/downloads)
- 初代seleniumといえば [Firefox](https://github.com/mozilla/geckodriver/releases)

最初はChromeを使っていたのですが、後に出てくる`Xvfb`との相性が悪く、うまく動作しなかったため… Firefoxを使うことになりました。上記URLからドライバーをダウンロードし、`/drivers/` 以下に配置してください。[^1]

また、Firefoxのgeckodriverを動かすためには、OSにFirefoxがインストールされていることが必要となります。まだ入っていなければ [公式サイト](https://www.mozilla.org/ja/firefox/new/)からダウンロードしてもらえればと。

[^1]: 確か、最新のもの(mac版)をダウンロードした気がします…。なお、macOS上の所定の位置にdriverを配置すると 起動時にファイルを指定しなくてよくなるようですが、今回はその方法を使っていません。



### ダウンロード先フォルダを準備

ついにコーディングです。

1. ダウンロードしたファイルを特定フォルダに入れる
1. 1を加工して、成果物データ(csv)を作成。別のフォルダに入れる
1. 2をS3に送る

としていくことにするため、ダウンロード先のフォルダを用意します。

```python:scraping.py
date = datetime.now().strftime('%Y%m%d')
dldir_name = os.path.abspath(__file__ + '/../../../tmp/files/download/{}/'.format(date))
dldir_path = Path(dldir_name)
dldir_path.mkdir(exist_ok=True)
download_dir = str(dldir_path.resolve())
```
import文などは最後にまとめて紹介します。
…なんだかコードが冗長な気がしますが、動作したのでこれでいいことにします。


### seleniumの起動までを書く

次に、seleniumでgeckodriverを起動させるところまでを記載します。
Firefoxの場合、普通に起動するとダウンロードダイアログが出てしまうので、それが出ないようにオプションをいろいろと指定する必要があります。

```python:scraping.py
driver_path = os.path.abspath(__file__ + '/../../drivers/geckodriver') #driverの位置を指定します。
fp = webdriver.FirefoxProfile()
fp.set_preference("browser.download.folderList",2) # 「ダウンロードフォルダを指定する」ためのオプションだったような。
fp.set_preference("browser.download.dir", download_dir)
fp.set_preference("browser.download.manager.showWhenStarting",False) #何だろう？わからない
fp.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
log_path = os.path.abspath(__file__ + '/../../../tmp/logs/geckodriver.log') #何もしないでいると、実行ファイルの位置にlogファイルが生成されてしまう
driver = webdriver.Firefox(firefox_profile=fp,executable_path=driver_path,service_log_path=log_path)

driver.maximize_window() #Windowサイズによってサイドメニューが消えたりする…
driver.implicitly_wait(10)  #10秒間指定の項目がなければエラー
```

要注意なのは `helperApps.neverAsk.saveToDisk` のオプションです。
ここで指定したファイル形式に限り、「ダウンロードしますか？」というダイアログがでないようになります。今回ダウンロードするxlsファイルは**application/vnd.openxmlformats-officedocument.spreadsheetml.sheet**になるようでした。[^2]

[^2]: 正式なxlsのファイル形式は違うものなのですが、実際にダウンロードされるファイルの形式を指定する必要があります


ちなみに、chromeだとこんなに簡単でした

```python:scraping.py
driver_path = os.path.abspath(__file__ + '/../../drivers/chromedriver')
options = webdriver.ChromeOptions()
options.add_experimental_option("prefs", {"download.default_directory": download_dir})
driver = webdriver.Chrome(executable_path=driver_path, options=options)
```


### ログイン処理

今回のサイトの場合は、下記のようにするとログインができました。

```python:scraping.py
#ログイン
driver.get(LOGIN_URL)
mail_address = driver.find_element_by_id("mail_address")
mail_address.send_keys(config.mail_address)
password = driver.find_element_by_id("password")
password.send_keys(config.password)
submit_button = driver.find_element_by_css_selector("button.submit")
submit_button.click()
```

…ただし、クリックしたときに reCAPTCHAが出ますので、手動でこれを解除する必要があります。
あとで2captchaを使って解決するものですが、この時点ではまだないので、出たら自分で解除します。

解除するまで処理を待ってもらうため、wait処理を入れます。下記の例だと、最大100秒待つことができます。

```python:scraping.py
try:
  WebDriverWait(driver, 100).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, "※ログイン後に表示される要素"))
  )
except TimeoutException:
    driver.quit()
    exit('Error:ログインに失敗しました')
```


### データの取得

さて、ようやくデータの取得です。こんな感じにやりました。

```python:scraping.py
# 検索ワードを使って検索
search_box = driver.find_element_by_xpath(SEARCH_BOX_XPATH)
search_box.clear()
search_box.click()
search_box.send_keys(word) #検索窓に入力
time.sleep(INTERVAL) #React処理を待つため
search_box.send_keys(Keys.RETURN) #Returnキー → 最初の検索結果が選択される

# メニューを開く
try:
    driver.find_element_by_xpath(MENU_XPATH).click()
except ElementNotInteractableException:
    #メニューが選択できないとき → アコーディオンが開かれていない
    driver.find_element_by_xpath(MENU_OPEN_XPATH).click()            
    driver.find_element_by_xpath(MENU_XPATH).click()

# ダウンロード
driver.find_element_by_xpath(DOWNLOAD_XPATH).click()
```

- 最近のreactiveなフロントエンドフレームワークの場合、csspathでの指定が難しかったりします。そのため、基本的にはxpathを使って要素を指定することになりました。
- React等の場合、javascriptの動作が終わるのを待たないとうまくいかないことが多々ありました。`time.sleep()`をうまく活用しましょう。
- 要素がないときには `ElementNotInteractableException`がthrowされます。「この要素があるか否か？」を調べるメソッドは無いようなので、うまく活用しましょう。
- 通常のサイトなら、ダウンロードはわざわざクリックしなくても URLを叩けば手に入るものが多いかと思うのですが… 今回のサイトはclickする必要がありました


### 完成

これまでに紹介したものをつなげてやれば、一旦ダウンロード部分は完成です！
最後に、import文を紹介しておきます。

```python:scraping.py
import os
import time
from pathlib import Path
from datetime import datetime

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import ElementNotInteractableException
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException
```


続きはまた。