Google Cloud FunctionsのPythonランタイムでChrome + Seleniumを利用する

poetry使い方 プログラミング

こんにちは、自由になりたいデータサイエンティストです。
今回は初めての技術メモになります。

この記事は、以下のようなひとを対象にしています。

  • Python+Selenium+Chromeでスクレイピングを行いたい
  • 上記環境でファイルのダウンロードを行いたい
  • ローカル環境ではなく、クラウド環境(サーバーレス)で実行したい
  • GCP環境と親和性の高いことをしたい(BigQueryの利用など)

前提として、Google Cloud Functions(以降GCF)のJavascriptランタイムにはSeleniumがプリインストールされていますので、原則的にローカル環境と同じコードで動作するはずです。
本記事は、Pythonで書きたい!という方向けに書いていきますので、以降はGCFのPythonランタイムの話をしてお読みください。
(ほかのランタイムでも同様のことが可能かもしれませんが、試したことがないので保証できません。)

前提として、GCFではPythonライブラリのインストール/利用は当然可能ですが、そのほかのアプリケーションなどのインストールは難しいです。

GCFでのSelenium利用手順

  1. GCFへのheadless-chromiumの導入
  2. GCFへのChromedriverの導入
  3. GCFへのSeleniumのインストール
  4. PythonからSeleniumを利用する環境のセットアップ
  5. おまけ:ハマったこと

ちなみに、開発環境としてはGoogle Cloud Shellを利用しています。
GCPサービスとの連携が取りやすくエディタ機能もしっかりしていてブラウザだけで利用できるのでとても便利です。

ファイル構成は以下の感じです。

gcf-selenium/
 ├ localpackage/
 |    ├ __init__.py
 |    └ mypackage.py
 ├ chromedriver
 ├ headless-chromium
 ├ main.py
 └ requirements.txt

外部ライブラリが必要ない場合は、localpackageディレクトリは不要です。

GCFへのheadless-chromiumの導入

インストールなく実行できるheadless-chromiumを公開してくださっているGitHubがあるので、そこから安定版をダウンロード・解凍します。
AWS Lambda(AWS版GCFのようなもの)用に用意されていますが、GCFでも問題なく動きます。

この際、メジャーバージョンを確認してください。

GCFへのChromedriverの導入

上記でダウンロードしたheadless-chromiumのメジャーバージョンに合わせて、こちらのページからChromedriverをダウンロード・解凍します。
バージョンが合わないとエラーが起こるので注意してください。

GCFへのSeleniumのインストール

これはPythonパッケージとしてインストールできるので、requirements.txtで対応します。

requirements.txt

selenium
Flask==1.0.2

これだけですね。flaskは、httpトリガーを利用する場合に利用されるので入れておきましょう。
(無くても動くかもしれませんが、試していません。)
ほかに入れたいライブラリがあれば入れておきましょう。

PythonからSeleniumを利用する環境のセットアップ

Selenium利用時はいろいろなお作法が必要です。
selenium driverのセットアップは関数化しておくと便利です。


from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import os

def get_driver(dl_path):
    path = os.getcwd()
    driverPath = path + "/chromedriver"
    headlessPath = path + "/headless-chromium"

    options = Options()
    options.binary_location = headlessPath
    options.add_experimental_option("prefs", {
        "profile.default_content_settings.popups": 0,
        "download.prompt_for_download": "false",
        "download.directory_upgrade": "true",
        "plugins.always_open_pdf_externally": True
    })
    options.add_argument('--headless')
    options.add_argument("--disable-gpu")
    options.add_argument("--window-size=1280x1696")
    options.add_argument("--no-sandbox")
    options.add_argument("--hide-scrollbars")
    options.add_argument("--enable-logging")
    options.add_argument("--log-level=0")
    options.add_argument("--v=99")
    options.add_argument("--single-process")
    options.add_argument("--ignore-certificate-errors")
    options.add_argument("--disable-dev-shm-usage")
    driver = webdriver.Chrome(driverPath, options=options)

    driver.command_executor._commands["send_command"] = (
        'POST',
        '/session/$sessionId/chromium/send_command'
    )
    driver.execute(
        "send_command",
        params={
            'cmd': 'Page.setDownloadBehavior',
            'params': { 'behavior': 'allow', 'downloadPath': dl_path }
    })

    return driver

設定内容の意味についてはドキュメントなどをご参照ください。
ざっくりご説明しておくと、options.add_experimental_optiondriver.command_executordriver.executeはダウンロードに関する設定です。ファイルをダウンロードしない際はこの辺りを消しても動作するはずです。
dl_pathは見てわかる通り、ダウンロード先のディレクトリです。ダウンロードファイル名は指定できないため、ディレクトリ分けなどしておくと後で便利です。

セットアップについての説明は以上になります。
あとはローカルで実行する際と同じ手順でスクレイピングを実行してください。

おまけ:ハマったこと

Seleniumを実行するうえでハマったこととその解決策をざっとご説明します。

クリック動作が反映されない?

問題点
自分のブラウザ上で行ったクリック動作と同じことをしているはずなのに、.click()後に撮ったスクショでは画面遷移が起こっていない

原因
クリック後の動作にアニメーションが含まれていた

解決策
time.sleep(1)でアニメーションが終了するのを待った
(アニメーションの抑制とか調べればありそう)

Webページの要素名が実行のたびに変わる

問題点
ChromeのDevツールでxpathを抽出したのに、要素が見つからない旨のエラーがでる

原因
スクレイピング対策なのか、同じページのはずなのにアクセスのたびに要素名が変わる仕様になっていた

解決策
/div/div/div/div/div[2]/button[3]のように、手動で要素順序を指定した

要素がクリックできないといわれる

問題点
固定名の要素を指定してクリック動作をすると、無効な要素と言われる

原因
同名の要素が同一ページに無効状態で隠れており、そちらをクリックしようとしていた

解決策
有効時に付与されるクラス名を探し、そこから手動で要素順序を指定した

以上です。
また別の問題に遭遇したら、同じようにメモを取っていきます。

最後に

Seleniumは非常に便利ですが、セットアップが少し難点かと思います。
同じようにハマる方が出ないよう、このページが参考になれば幸いです。

タイトルとURLをコピーしました