PythonによるSelenium Tips

[マグロ大学(近畿大学)アドベントカレンダー 2018](https://qiita.com/advent-calendar/2018/kindai)の19日目
# はじめに
何番煎じだっていうSeleniumの記事です。
使っていてパーツパーツしてるなと思ったので、各パーツ単位で解説してます。
Seleniumってすごいですね。基本的なHTMLとCSSが分かっていれば（分からなくても）難しいことをせずにデータのスクレイピングができます。機械学習やりたいけど、素材の集め方分からないって人は学習コストほぼなしでデータをそろえることができるのでおすすめです。
後、勝手にブラウザが動き回るのは見てても楽しいし、テンション上がる。

# Seleniumとは
所謂ヘッドレスブラウザーができるやつです。コードを書いて自動でブラウザを操作することができます。こいつのすごいところはブラウザを実際に動かしているので、Reactなど動的に要素を生成するホームページでも要素を取得することができます。
ただ、難点としてBeautifulSoupで解析してrequests等で要素を取得する場合に比べて実行速度が遅いです。並列化すれば多少はマシになるかもしれないです。（やってないので知らない）

# 使うもの
- python3系
- selenium
- Chrome
- Chromeのドライバー (PATHを通しておくと楽 本記事では通した前提)

# Import
import系一覧

```python
# 行動起点 全ての始まり
from selenium import webdriver
# キーの入力を受け付ける
from selenium.webdriver.common.keys import Keys
# スクロール等の動作をする場合必要
from selenium.webdriver.common.action_chains import ActionChains
# 最初にオプションとしてつけることでヘッドレスにできる
from selenium.webdriver.chrome.options import Options
# 必須 サーバー側に配慮のないリクエストはDoS攻撃と同義
import time
```
# 基本的な使い方
```python
# ---上記import文達---

# ヘッドレスにする場合、下記の4行を使う
# options = Options()
# options.add_argument('--headless')
# options.add_argument('--disable-gpu')
# driver = webdriver.Chrome(options=options)
driver = webdriver.Chrome()

# ドライバが設定されるまでの待ち時間を設定する。
driver.implicitly_wait(10)
# リンクに転移
driver.get("http://www.python.org")
# 要素の取得
element = driver.find_element_by_name("q")
# 要素に対しての操作
element.clear()
element.send_keys("pycon")
element.send_keys(Keys.RETURN)
# ドライバ終了
driver.close()
```
pyconって文字がsearch欄に入力されて一瞬でブラウザ消えたのが見えれば動いてます。
使えるメソッド関連は下記の記事がわかりやすく、まとまっています。
[Selenium webdriverよく使う操作メソッドまとめ](https://qiita.com/mochio/items/dc9935ee607895420186)

# リンクを新規タブで開く
- Chromeデフォルトの機能 Ctl+Enter（MacはCommand+Enter）で新規タブを開く
- JavaScriptを使う

上記2つの方法が使えます。
ただし、１つ目の方法はあくまでもリンクを踏んだ時にしか使えません。

```python
# リンク要素
link_element = ... #driver.find_element

# ---1つ目の方法---
# デフォルト機能で新規タブを開く (MacならCONTROLをCOMMANDにする)
link_element.send_keys(Keys.CONTROL, Keys.ENTER)
# ---２つ目の方法---
# javascriptで新規タブを開く
driver.execute_script("window.open(arguments[0], 'newtab')", link_element)

# タブの変更
driver.switch_to.window(driver.window_handles[1])
# 何らかの処理
...
# タブの消去
driver.close()
# 元のタブに戻る
driver.switch_to.window(driver.window_handles[0])
```
# 特定の要素までスクロール
ActionChainsを使うと特定の要素までスクロールできます。

```python
# スクロールする目的地の要素
scroll_element = ... #driver.find_element
# 初期化
actions = ActionChains(driver)
# 動作の決定
actions.move_to_element(scroll_element)
# 実行
actions.perform()
```
# リストなど同系の要素を全て取得する
基本的に複数の要素をまとめて取得するときは`find_elements`メソッドを使ってリストで取得するのが普通です。
しかし、取得したい要素が他のクラス名と被っていて余計なものまで取得してしまうような場合には、少し手間ですがX-Pathを使うのがおすすめです。
基本的にまとめて取得したいものは要素の形がほぼ一緒で番号だけが違うと言うことが多いのでそこを利用して、`format()`メソッドで部分的に変更して要素を取得していきます。

```python
# ---普通---
elements = driver.find_elements_by_class_name("・・・")
# ---X-Pathを使う例---
elements = []
# format()メソッドで使う添字
i=1
While True:
    try:
        # X-Pathは各自で解析してformat()で変えるべきところを探してください
        elements.append(driver.find_element_by_xpath('//*[@id="content"]/div/section/div[1]/div[{}]'.format(i))
        # X-Pathの要素をずらしていく
        i+=1
    except:
        break
```

# 画像を保存する
画像を保存するのはrequestsを使う方が簡単
スクリーンショットでいいなら`save_screenshot(export_file_name)`メソッドを要素の後につけるだけで撮れます。

```python
# 入ってない場合はpipでinstall
import requests
# 高水準ファイルライブラリ
import shutil

# ファイル名
export_file_name = ...
# imageタグのsrcやcssのbackground-image等、画像のパスを取得
img = icon.value_of_css_property("background-image")
# 画像を取得
res = requests.get(img, stream=True)
# 画像を保存
with open(export_file_name, "wb") as f:
    shutil.copyfileobj(res.raw, f)
```
# 最後に
情報が結構バラバラに点在してたので自分が使った分だけでも、まとめてみました。
基本的にSeleniumは情報量が多いので、ググれば結構親切に出てきてそういった面でも学習コストは低いと思います。
例外処理と`time.sleep()`だけ忘れずにスクレピングを楽しみましょう。