PythonのSeleniumでログインありのwebページをPDF保存する

# はじめに

- Python の Selenium を使って、ログインありの web サイトのページを pdf としてダウンロードした時のメモです
- Selenium は自動でブラウザを操作できるツールです。web ページの自動テストなどに使う事が多いようです

# 環境

- macOS Mojave 10.14.6
- python 3.6.0
- Chrome

# 事前準備

以下ライブラリを pip でインストールします

- selenium
- chromedriver-binary

```shell
pip install selenium chromedriver-binary
```

# selenium での操作

## ドライバの設定

- selenium でブラウザを操作するために必要なドライバをインスタンス化します

```python
import chromedriver_binary
from selenium import webdriver

options = webdriver.ChromeOptions()
driver = webdriver.Chrome(options=options)
```

## ログインする

- セキュリティのため、ログインに必要な id とパスワードを環境変数から読み取るようにします

```python
import os

login_id = os.environ["MY_LOGIN_ID"]
login_password = os.environ["MY_LOGIN_PASSWORD"]
```

- `driver.get()`で URL を指定し、ログインページを取得します
- 今回はログイン対象ページの ID/パスワードを入力するテキストボックスとログインボタンが id 属性値で特定できたため、`driver.find_element_by_id()`でページの要素を取得します
- 取得した要素に対して`send_keys()`で値を入力し、`submit()`でログインします

```python
driver.get("<ログインページのURL>")
driver.find_element_by_id("username").send_keys(login_id)
driver.find_element_by_id("password").send_keys(login_password)
driver.find_element_by_id("authform").submit()
```

## クリックする

- 取得した要素に対して`click()`でクリックします

```python
driver.find_element_by_id("hogeid").click()
```

## 表の中の要素を取得する

- 表の中の要素の取得には`find_elements_by_xpath()`を使いました
- XPath の取得には chrome のデベロッパーツールが便利です。対象要素の HTML を右クリックして Copy XPath でコピーできます
- 以下のような表の`dataA-2`のリンクを取得したいとします

| header1 |         header2         | header3 |
| :-----: | :---------------------: | :-----: |
| dataA-1 | [dataA-2](http://dummy) | dataA-3 |
| dataB-1 | [dataB-2](http://dummy) | dataB-3 |

```html:html
<table class="hoge fuga piyo">
  <thead>
    <tr>
      <th>header1</th>
      <th>header2</th>
      <th>header3</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        dataA-1
      </td>
      <td>
        <a href="http://dummy">dataA-2</a>
      </td>
      <td>
        dataA-3
      </td>
    </tr>
    <tr>
      <td>
        dataB-1
      </td>
      <td>
        <a href="http://dummy">dataB-2</a>
      </td>
      <td>
        dataB-3
      </td>
    </tr>
  </tbody>
</table>
```

```python:python
driver.find_element_by_xpath(
            "//table[contains(@class,'hoge')]/tbody/tr[1]/td[2]/a")
```

- 今回は取得したい表の class が複数指定されていたので`contains`でフィルタしました
- class が一つの場合は`"//table[@class='hoge']/tbody/..."`でよさそう

## 指定した秒数待つ

- ページに遷移してから表の表示に少し時間がかかっていたようで、要素の取得ができなかったことがありましたが、`implicitly_wait()`で待ち時間を設定するとうまくいきました

```python
driver.implicitly_wait(n)
```

## ページを pdf としてダウンロードする

- chrome の印刷機能で pdf として保存します
- ドライバにオプションを追加し、ダウンロードするパスを指定します
- ドライバのオプションに`--kiosk-printing`を加えると、印刷ダイアログを開いた後に自動で印刷ボタンを押してくれます
  - ドライバのオプションに`--headless`オプションを加えると、ブラウザを立ち上げずに実行する「ヘッドレスモード」で実行できますが、`--kiosk-printing`オプションとの併用はできないようです。実際に併用してみましたが pdf をダウンロードできませんでした（悲）

```python:ドライバの設定
options = webdriver.ChromeOptions()

options.add_experimental_option("prefs", {
    "download.default_directory": "~/Downloads"
})

options.add_argument('--kiosk-printing')

driver = webdriver.Chrome(options=options)
```

- 印刷ダイアログを開くためには、javascript を実行する`execute_script()`を使用しました。ドライバの`--kiosk-printing`オプションにより、これでPDFが保存されます

```python
driver.execute_script('window.print();')
```

# 参考

- [Selenium API(逆引き)](https://www.seleniumqref.com/api/webdriver_gyaku.html)
- [Selenium で印刷(print preview)を経て PDF を保存する方法](https://takuya-1st.hatenablog.jp/entry/2017/06/21/140000)
- [Selenium で要素を選択する方法まとめ](https://qiita.com/VA_nakatsu/items/0095755dc48ad7e86e2f)
- [Python + Selenium + Chrome で自動ログインいくつか](https://qiita.com/memakura/items/dbe7f6edadd456da1c5d)