python + selenium入門

Webテストやスクレイピングで使われるseleniumの使い方をざっと勉強したので、とりあえず使えるようにするところまでを記事にまとめておきます。

## seleniumとは
web自動化ツールです。スクレイピングツールなんかとは違い実際にブラウザを動かしてアクセスしますので、java scriptを使っているようなウェブページの動作テストも可能であるのが特徴です。chromeやfirefoxなどのドライバが提供されています。

公式ドキュメント
https://selenium-python.readthedocs.io/api.html

## 使用上の注意点

自分のサーバーのサービスについてテストを行う場合は問題ないですが、インターネット上のページに対してスクレイピング等を行う場合、サーバー側に負荷が掛かってしまう、著作権侵害にあたる、サービス側が規約で禁じている、などによって法的責任が発生したり、アカウントが凍結されたりする可能性もありますので注意しましょう。

以下のページが詳しいです
https://vaaaaaanquish.hatenablog.com/entry/2017/12/01/064227

## 試行環境
windows10 64bit
python == 3.7.4
selenium == 3.141.0
chromedriver-binary == 80.0.3987.16.0
jupyter notebook

## ChromeDriver.exeをダウンロードする

以下からChromeDriverバイナリをダウンロードします
https://sites.google.com/a/chromium.org/chromedriver/home

2020年2月16日現在の最新安定版は80.0.3987.106ですので今回はこれを使っていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/37f4b84e-910c-6ed2-5f82-4ea99e929604.png)

ダウンロードしたzipファイルを解凍すると実行ファイルが入ってますので、このファイルを適当なフォルダに配置したらChromeDriverバイナリの準備は完了です。配置するフォルダは開発プロジェクトフォルダにしておけば管理がしやすいし、相対PATHも短くなるのでおススメです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/986e715a-dcbd-8aa1-9e90-b94adc4dedf9.png)

## インストール
seleniumとchrome driverをインストールします。pipで簡単にインストールできます。chromedriver-binaryのバージョンは上記と同じにします。

```terminal:terminal
pip install selenium
pip install chromedriver-binary==80.0.3987.106
```

## ページを開く

executable_path=に上記で配置したchromedriver.exeのPATHを指定してwebdriverオブジェクトを作り、getメソッドでページを開きます。以下ではpythonコードと同じ場所に置いたので./chromedriver.exeになっています。

```python:python
import chromedriver_binary
from selenium import webdriver

# webdriverオブジェクトを作る (ブラウザが開く)
driver_path = './chromedriver.exe'
driver = webdriver.Chrome(executable_path=driver_path)

# urlを指定してブラウザで開く
url = 'https://www.google.com/'
driver.get(url)
```
実行するとchromeが起動して指定したurlを開いてくれます。見た目はchromeそのものですが「自動テスト ソフトウェアによって制御されています」と表示されているので見分けがつきます。

普通のchromeと同様に操作できますのでログイン操作などを手動でやっておいてから、テストのみseleniumで実行するような事も出来ます。


## ソースを表示する
そのまま表示するだけなら以下で出来ます

```python:python
driver.page_source
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/442fe02b-af11-eabb-13e5-bd84e7b94112.png)

## 子要素を全部取得する
driver以下のhoge要素をすべて取得するにはfind_elements_by_xpath(".//hoge")のように書きますが、hogeをワイルドカードにすると子要素すべてを取得できます。

```python:python
driver.find_elements_by_xpath(".//*")
```
これを使って雑にツリー表示したりもできます

```python:python
def makeTree(parent, depth, padding='>'):
    if depth >= 1:
        childs = parent.find_elements_by_xpath(".//*")
        for child in childs:
            print('%s %s  %s' % ((padding.replace('>', '├->')), str(child.tag_name), str(child.text)[:40].replace('\n', ' ')))
            makeTree(child, depth-1, '│ '+padding)

max_depth = 4
section = driver.find_element_by_xpath(".//section")
print(section.tag_name)
makeTree(section, max_depth)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277233/033134ae-fb6a-9da3-a820-800b7d127b42.png)
hoge.textが子要素の本文も取得できてしまうので、重複して表示されちゃってますが、ざっと確認する程度ならこれでもいけると思います。

## タグの探し方

```
<html>
  <head>
    <title>タイトル</title>
  </head>
  <body>
    <div class="item" id="001">
      <span class="name">ジョナゴールド</span>
      <span class="plice">160円</span>
      <a href="https://qiita.com">link</a>
    </div>
    <div class="item" id="002">
      <span class="name">姫リンゴ</span>
      <span class="plice">120円</span>
      <a href="https://qiita.com">link</a>
      <img src="apple.png">
    </div>
  </body>
</html>
```
上記のようなhtmlから2つの商品名と価格をxpathで取得する場合は以下のような書き方になります。他にもやり方があるようなので詳しくは公式ページを見てください。
https://kurozumi.github.io/selenium-python/locating-elements.html

```python:python
# class="item"であるdiv要素を取得する
items = driver.find_elements_by_xpath("//div[@class='item']")

# 取得した要素から１つ下の階層から商品名と価格を探します
for item in items:
    name = item.find_element_by_xpath("./span[@class='name']")
    plice = item.find_element_by_xpath("./span[@class='plice']")
    link = item.find_element_by_xpath("./a")

    print(name.text)
    print(plice.text)
    print(link.get_attribute("href"))
```
まず、最初に開いたページを保持したselenium.WebDriverオブジェクトであるdriverからfind_elements_by_xpathメソッドで、class="item"であるようなdiv要素を取得しています。find_elementsと複数形にするとlist形式で複数要素を取得します。

取得した要素もselenium.WebDriverオブジェクトなので、同じやり方で更に下の階層の要素を探すことが出来ます。1つ下の階層の場合は"./"、任意の階層下がって探す場合は".//"で書き出せば良いようです。

最終的に目的の要素にたどり着いたら情報を取得します。タグ間に書かれている値はtextで、タグに書かれているリンクなどはget_attribute()で取得できます。

実行すると以下のようになります。

```
ジョナゴールド
160円
https://qiita.com/
姫リンゴ
120円
https://qiita.com/
```
画像をダウンロードしたいときは以下のように書けば良いようです。base64でhtmlに画像が埋め込まれている場合はbase64パッケージを使って、そうでない場合は普通にバイナリで書き出します。

## 画像を保存する
```python:python
import shutil
import requests
import base64

def saveImage(src):
    path = './' + src
    if 'base64,' in src:
        with open(path, 'wb') as f:
            f.write(base64.b64decode(src.split(',')[1]))
    else:
        res = requests.get(src, stream=True)
        with open(path, 'wb') as f:
            shutil.copyfileobj(res.raw, f)

img = driver.find_element_by_xpath("//img")
src = img.get_attribute("src")
saveImage(src)
```

慣れるとすごく便利ですね。
レッツトライ！


## 更新履歴
2019.8.21 chromedriver-binaryのバージョンを揃える手順について追記しました
2020.2.16 ChromeDriverバイナリの使用方法変更に対応


## 2020.2.16追記: 

以前の記事では以下のエラーが出るように仕様変更されたので修正しました

```
selenium.common.exceptions.WebDriverException: 
Message: ‘chromedriver’ executable needs to be in PATH. 
Please see https://sites.google.com/a/chromium.org/chromedriver/home
```