Docker-Selenium を使って印刷プレビューを介しての PDF 保存をやってみた

Docker-Selenium を使って印刷プレビューを介しての PDF 保存をやってみた背景

会社で利用している監視サービスで一々 Web ブラウザを使って監視対象分のサーバーの監視レポートを毎月ダウンロードして顧客に提出ということをしてたので、今まで興味はあったけど手が出せてなかった Python と Selenium に入門したついでにこの課題を解決してみたのですが、思った以上に詰まったので内容をメモします。

Docker-Selenium を使って印刷プレビューを介しての PDF 保存を自動化する前の手作業

  1. ログイン画面
    • id, pass を入力してログインする
  2. ダッシュボード画面
    • 監視対象のサーバーの一覧が表示されている
    • サーバー名をクリックして詳細画面に遷移する
  3. 詳細画面
    • 月毎の監視レポートのリンクの一覧が降順表示されている
    • 一覧上の最新の月のリンクをクリックして監視レポート画面に遷移する
  4. 監視レポート画面
    • 印刷ボタンがあるので、それをクリックして印刷プレビューを介して PDF を保存
  5. 2〜4 をサーバーの数だけ繰り返す

ファイル構成

ディレクト構成
Copied!
workspace
    └─ dev
        └─ shm               ← ファイルの保存先
    └─ docker-compose.yml    ← docker-selenium用のdocker-composeファイル
    └─ main.py               ← Selenium実行ファイル

Mac に Selenium をインストール

Copied!
pip3 install selenium

Selenium Grid の準備

オーバースペックみたいですが Selenium Grid を使ってみます。
利用するイメージは selenium/standalone-chrome です。

docker-compose.yml
Copied!
version: '3'

services:
  chrome:
    image: selenium/standalone-chrome
    ports:
      - 4444:4444
    volumes:
      - $PWD/dev/shm:/dev/shm

Docker-Selenium を使って印刷プレビューを介しての PDF 保存処理の作成

main.py
Copied!
import sys
import time
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities


def search_and_download(driver):
    driver.get('https://hogehoge/ligin')

    # idとパスワードを入力してログイン
    id = driver.find_element_by_id("id_username")
    id.send_keys("ID")
    password = driver.find_element_by_id("id_password")
    password.send_keys("パスワード")

    login_button = driver.find_element_by_xpath('//*[@type="submit"]')
    login_button.click()

    # ダッシュボード画面で監視対象のサーバーの詳細画面へのリンクを全て取得し、アドレスだけ別変数に退避
    list_items = driver.find_elements_by_xpath('//a[starts-with(@href,"/reports")]')
    links = [list_item.get_attribute('href') for list_item in list_items]

    for link in links:
        try:
            # 詳細画面へ遷移し、最新の監視レポートリンクを取得
            driver.get(link)
            detail = driver.find_element_by_xpath('//a[starts-with(@href,"/reports/report/detail/")]')

            # 監視レポート画面へ遷移し、印刷ボタンをクリックすると自動でPDF保存されます
            driver.get(detail.get_attribute("href"))
            print_button = driver.find_element_by_xpath('//button[starts-with(@onclick,"window")]')
            print_button.click()
            time.sleep(3)
        except:
            print('監視レポートなし')
            continue

if __name__ == '__main__':
    query = ' '.join(sys.argv[1:])

    caps = DesiredCapabilities.CHROME.copy()
    caps['chromeOptions'] = {
        'args': [
            '--kiosk-printing',
        ],
        'prefs': {
            'savefile': {
                'default_directory': '/dev/shm'
            },
            'printing': {
                'print_preview_sticky_settings':{
                    'appState': {
                        'recentDestinations': [{
                            'id': 'Save as PDF',
                            'origin': 'local',
                            'account': '',
                        }],
                        'selectedDestinationId': 'Save as PDF',
                        'version': 2
                    }
                }
            }
        }
    }

    options = {
        'command_executor': 'http://localhost:4444/wd/hub',
        'desired_capabilities': caps
    }

    with webdriver.Remote(**options) as driver:
        driver.implicitly_wait(1)
        search_and_download(driver)

Docker-Selenium を使って印刷プレビューを介しての PDF 保存処理の内容

Copied!
caps['chromeOptions'] = {
    'args': [
        '--kiosk-printing',
    ],
    'prefs': {
        'savefile': {
            'default_directory': '/dev/shm'
        },
        'printing': {
            'print_preview_sticky_settings':{
                'appState': {
                    'recentDestinations': [{
                        'id': 'Save as PDF',
                        'origin': 'local',
                        'account': '',
                    }],
                    'selectedDestinationId': 'Save as PDF',
                    'version': 2
                }
            }
        }
    }
}

chromeOptions で chrome の webdriver の細かな設定を行えます。

  • --kiosk-printing

    • このオプションを指定すると、window.print()を実行し印刷しようとすると、印刷プレビューがすぐに閉じられ、Chrome に事前に設定した設定が自動的に使用されます。
    • 因みにこのオプションは --headless とは併用できないようです。その為、--headless をつけると PDF がダウンロードされませんでした。
  • prefs.savefile.default_directory

    • 今回一番はまったところです。印刷プレビューを介したファイル保存で使用するファイル保存先の設定です。selenium でファイル保存関連を検索すると download.default_directory といった download を使用した記事が多いのですが、それでは保存できませんでした。やっとたどり着いたのがこのプロパティで無事保存ができました。
  • printing

    • このオプションで印刷プレビューの設定ができます。今回は PDF での保存が目的なのでそれを選択するようにしています。

Docker-Selenium を使って印刷プレビューを介しての PDF 保存をやってみた まとめ

目的は達成しましたがまだまだわからないことだらけでした。
本当は main 処理の最後に後始末として driver.quit() を入れたのですが、
selenium.common.exceptions.WebDriverException: Message: No active session with ID ~ というエラーが出て結局解決できませんでした。
あと作ってる最中ずっと、公式リファレンスはどこかにないの?と思っていました。
特に ChromeOptionssavefile は色々ググってやっと見つけたのが個人ブログでした。
その後、ChromeOptions savefile でぐぐりましたが全く情報がなく、
savefile といったプロパティの存在はを知るにはどうしたらいいのかと今でも悶々としています。