GoogleCloudFunctionsでSeleniumを使う

# はじめに
GoogleCloudFunctionsでSeleniumを使う必要があり、[こちら](https://blowup-bbs.com/gcp-cloud-functions-python3/)を参考にさせていただきました。
ただGoogleCloudFunctionsの仕様が変わってしまったのか、ひと手間必要だったので記事にまとめてみました。


# headlessChromeを用意する
**chromedriver**と**headless-chromium**を取得します。


https://github.com/ryfeus/gcf-packs
こちらをダウンロードして解凍します。

下の画像の通りselenium_chromeフォルダ→sourceフォルダにファイルがあります。
※headless-chromiumはsourceフォルダにあるheadless-chromium.zipを解凍します。
![chromedriver.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237144/0133ef06-21ed-8042-6f79-24b6823ee0e0.png)

# GoogleCloudFunctionsにデプロイするフォルダを用意する
chromedriver、headless-chromium、main.py、requirements.txtをフォルダに入れます。
※python以外の言語を使用する場合、適宜変更してください。
![folder.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237144/9a157312-8c75-a677-37df-5f5ada226064.png)

# GoogleCloudFunctionsでSeleniumを使用する
## chromedriverとheadless-chromiumを別フォルダへコピーする
今回の一番のポイントです。
GoogleCloudFunctionsへデプロイすると、chromedriverなどのファイルはカレントディレクトリ"/user_code"にあります。ただしこの配下のファイルには**アクセスできません**。代わりに"/tmp"フォルダであればユーザーが自由にアクセスできます。

つまりchromedriver・headless-chromiumを"/tmp"フォルダへコピーする必要があります。
またファイル権限を変更します。ファイル権限の変更については[こちら](https://www.lifewithpython.com/2017/10/python-add-file-permission.html)を参考にしました。

```python:ファイル権限の変更する関数
def add_execute_permission(path: Path, target: str = "u"):
    """Add `x` (`execute`) permission to specified targets."""
    mode_map = {
        "u": stat.S_IXUSR,
        "g": stat.S_IXGRP,
        "o": stat.S_IXOTH,
        "a": stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH,
    }

    mode = path.stat().st_mode
    for t in target:
        mode |= mode_map[t]

    path.chmod(mode)
```

```python:カレントディレクトリから"/tmp"へコピー&権限の変更
    driverPath = "/tmp" + "/chromedriver"
    headlessPath = "/tmp" + "/headless-chromium"

    # copy and change permission
    print("copy headless-chromium")
    shutil.copyfile(os.getcwd() + "/headless-chromium", headlessPath)
    add_execute_permission(Path(headlessPath), "ug")

    print("copy chromedriver")
    shutil.copyfile(os.getcwd() + "/chromedriver", driverPath)
    add_execute_permission(Path(driverPath), "ug")
```

## Chromeのオプション
https://github.com/ryfeus/gcf-packs
こちらのmain.pyを参考に設定しましたが、```"user-agent=" + UserAgent().random"```は必要ないようです。また```disable-dev-shm-usage```を設定しています。
※あまり詳しく調べていません・・・

## プログラムソース・設定ファイル
上記の内容を踏まえたプログラムソースです。

※(2022/1/30更新) hidemanさんよりサンプルコードが動かないよ！とご指摘いただきました。
　以前と設定が変わってしまったようです…修正版に差し替えました。hidemanさんありがとうございます！

```python:main.py
import os
import shutil
import stat
from pathlib import Path

from selenium import webdriver

global driver


def add_execute_permission(path: Path, target: str = "u"):
    """Add `x` (`execute`) permission to specified targets."""
    mode_map = {
        "u": stat.S_IXUSR,
        "g": stat.S_IXGRP,
        "o": stat.S_IXOTH,
        "a": stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH,
    }

    mode = path.stat().st_mode
    for t in target:
        mode |= mode_map[t]

    path.chmod(mode)


def settingDriver():
    print("driver setting")
    global driver

    driverPath = "/tmp" + "/chromedriver"
    headlessPath = "/tmp" + "/headless-chromium"

    # copy and change permission
    print("copy headless-chromium")
    shutil.copyfile(os.getcwd() + "/headless-chromium", headlessPath)
    add_execute_permission(Path(headlessPath), "ug")

    print("copy chromedriver")
    shutil.copyfile(os.getcwd() + "/chromedriver", driverPath)
    add_execute_permission(Path(driverPath), "ug")

    chrome_options = webdriver.ChromeOptions()

    chrome_options.add_argument("--headless")
    chrome_options.add_argument("--disable-gpu")
    chrome_options.add_argument("--window-size=1280x1696")
    chrome_options.add_argument("--no-sandbox")
    chrome_options.add_argument("--hide-scrollbars")
    chrome_options.add_argument("--enable-logging")
    chrome_options.add_argument("--log-level=0")
    chrome_options.add_argument("--v=99")
    chrome_options.add_argument("--single-process")
    chrome_options.add_argument("--ignore-certificate-errors")
    chrome_options.add_argument("--disable-dev-shm-usage")

    chrome_options.binary_location = headlessPath

    print("get driver")
    driver = webdriver.Chrome(executable_path=driverPath, options=chrome_options)


def seleniumSample(request):

    settingDriver()

    global driver
    try:
        print("URL get")
        driver.get("http://[hoge]")
        targetPath = '[HOGE]'
        ret = driver.find_element_by_xpath(targetPath)
        ret = ret.get_attribute("alt")
        print(ret)

    finally:
        print("driver quit")
        driver.quit()

    return ret
```

またデプロイ時にインストールするパッケージをrequirements.txtに記載します。

```text:requirements.txt
google-cloud-error-reporting==0.30.0
selenium
setuptools
```

# まとめ
ユーザーが触れるフォルダへheadlessChrome等をコピーすることでSeleniumを実行できました。
ちょっとしたスクレイピングであればCloudFunctionsで実行するのもひとつの手段になるかと思います。

何か参考になれば幸いです。ではでは。


# 参考

[Google Cloud FunctionsでPython+seleniumでスクレイピングしてみる](https://blowup-bbs.com/gcp-cloud-functions-python3/)
[[GoogleCloudFunctions(GCF) Python] GCFのOSや挙動に関するメモ](https://note.com/17num/n/nc75339b8652d)
[Python Tips：Python でファイルに権限を追加したい](https://www.lifewithpython.com/2017/10/python-add-file-permission.html)