Blow Up by Black Swan

Google Cloud FunctionsでPythonを使ってみる(Part.1 基本的な使い方)

seleniumを使うスクレイピングプログラムをサーバにデプロイしたい!

このような熱い思いから色々調べた結果、使い勝手が良さそうなサーバ環境はIaaSの2強とされるAWS(Amazon Web Service)とGCP(Google Cloud Platform)でした。普通ならここで業界最大手のAWSを選ぶのしょうが、過去ちらっと読んだ記事で、AWSを借りた人が大切なキーを誤って公開してしまい、莫大な請求をされたという話が乗っており(AWSに連絡して事情を話せば不正利用分は取り消されたそうですが。。。)、「AWS怖い((((;゚Д゚)))))))」という完全に身勝手な偏見を持っていたので、GCPを使ってみることに決めました。

ただ、色々調べてからわかったのですが、GCPは意外にもニューカマーのようなところがあり、情報量は圧倒的にAWSが充実していたということです。そもそもサーバやIaaSの知識がない状態で、日本語情報が豊富でないGCP(特にCloud Functions)を勉強するのは大変だったので、同じような経験をされる方もいるだろうと思い、今回記事にすることに決めました。

なお、今回使ったのはGCPの中のCloud Functionsと言われる機能で、AWS lambdaに相当するものです。この2つはかなり構造が似ているようで(というかグーグルがほぼほぼぱくっている?)、AWS lambdaについて書かれている記事がかなり参考になりました。特に今回の最終目標だったseleniumの利用についての情報は日本語、英語での情報、共にかなり少なかったのですが、AWS lambdaの記事から大体の構造を理解することができました。笑 まだまだ日本語情報が少ない領域だと思うので、少しでも多くのこの分野に関心がある方、困っている方の参考になれば幸いです。なお、この記事は3本構成で考えており、それぞれのページでは概ね以下のようなことを書く予定です。

Part1. Cloud Functionsの基本的な使い方: httpリクエストをトリガーとするHTTP functionsの使い方、ローカルマシンからのデプロイ
Part2. Cloud Functionsの様々な使い方: サードパーティパッケージ、自作パッケージの利用、スプレッドシート(GAS)との連携
Part3. Seleniumを使ったスクレイピング
備考. background functionは対象外

1. GCPについて(登録や新規プロジェクトの始め方など)

GCPを使うまでの流れは以下になります。

Googleアカウントの作成 -> GCPに登録(無料体験版への登録から行う) -> プロジェクトを作成

至って普通の流れになりますが、多くの方がGmailの利用などを通してGoogleアカウントを持っていると思うので、実際はGCPのページに行って登録を行うだけです。当分続けると思うのですが、登録から12ヶ月間は300USドル相当まで無料で使うことができキャンペーンをしています。最初に登録するクレジットカードについては本人確認のためで、課金する際は別途課金を有効化する必要があるようです()。実際、私はよく把握していないのですが(笑)、GCP内の機能の多くは一定の無料枠を持っており、また登録後12ヶ月間は300USドル相当まで無料で使うことができるので、ちょっとした利用なら課金を気にすることは全くないと思われます。

GCPへの登録後、実際にGCPのサービスを利用するにはプロジェクトを作成する必要があります。GCPではプロジェクトという単位でアプリなどを管理するようになっています。そのため、全く異なるアプリをデプロイするにはそれぞれプロジェクトを作る必要があります。一方で、1つのアプリの中でGCPの複数の機能を使う場合は一つのプロジェクトで完結させることができます。 このような流れになりますが、具体的な登録の仕方などは下記のサイトを参考にして頂ければと思います。

今回の記事では、Cloud Functionsでpython3.7を利用する基本的な使い方を記載していますが、こちらも複数のサイトで紹介されていますので、併せて確認頂ければと思います(ただ残念ながらまだ公式ページのpython3.7の使い方の説明は英語だけになります)。

なお、公式サイトのチュートリアルで全体を通して使われているコードのgithubリポジトリはこちらになります。リポジトリの”functions”ディレクトリがCloud Functionsのチュートリアルで使用されているコードになります。よく見るとわかりますが、GCPの他の機能のチュートリアルもこのリポジトリに含まれています。

2. HTTP Functions

ここからは公式サイトのリポジトリに添って、Cloud Functionsを実際に使用していきます。事前準備として、このチュートリアル専用のプロジェクトを作成している必要があります。Cloud Functionsでは1つのプロジェクトで複数の関数(Functions)が使えるので、プロジェクトは1つだけで構いません。

今回、最初に利用するHTTP Functionsとはhttpをトリガーとする関数のことです。Cloud Functionsではその名の通りクラウドに関数を設置しておき、最初に決めるトリガーに沿って適宜呼び出すという流れが基本になります。HTTP FunctionsはWEB APIであり、curlなどでURLを叩くことで関数が実行され、指定した値を取得することができます。その他にBackground Functionsという関数で、イベントをトリガーにすることもできます。

先ほどの参考サイトを見て頂くとわかりますが、Cloud Functionsを使用できるためにはプロジェクト作成後には次のようにAPIを有効化し、関数を作成します。これは、GCPコンソールから関数を作成する方法で、最も簡単に関数を作成できる方法です。自身のMacbookのターミナルなどローカルマシンからデプロイする方法は4章で説明しています。なお、GCPコンソールから関数を作成する場合、クラウドサーバの場所は”us-central1″に指定されてしまっているようです。ローカルマシンからのデプロイの場合は変更することができます。

Cloud Functions APIの有効化 -> 関数(Function)の作成

2-1. 基本的な使い方①

ここからはHTTP Functionを通して、Cloud Functionsの基本的な使い方を見ていきます。まずは最も基本的な関数「hello_get」関数を使用してみますが、先ほどのリポジトリではこちらのディレクトリにあります。ちなみにmain.pyの中にはチュートリアル用の複数の関数が格納されていますので、これを元に色々と試してみるとかなり勉強になります。

hello_get関数のコードは以下になります。

def hello_get(request):
    return 'Hello World!'

Google Cloud FunctionsのPythonの一番の特徴は、flaskを使っていることです。上記のコードで、関数の引数になっているのはflaskのRequestオブジェクトです。また戻り値はflaskのmake_responseメソッドでResponseオブジェクトに変換されてクライアントに戻されます。上記のコードでは、クライアントからのhttpリクエストを関数の引数として受け取り、”Hello World!”を返します。実際にGCPコンソールから次のように入力し関数を作成してみます。エディタ上の関数名と実行関数名が一致しないとエラーになります(デフォルトでは実行関数名に”hello_world”と入力されています)

そして、tes1の画面にいきテストしてみます。テスト画面でトリガーとなるイベントには何も入力せず、「関数をテスト」ボタンを押せば、アウトプット欄に「Hello World!」と表示されます。

プロジェクトを作成、APIを有効化、関数を作成、この3つのステップでCloud Functionsを使うことができます。初めて使うと色々とごちゃごちゃした印象も受けますが、一度理解してしまえば非常に簡単に使えるようになっていることがわかります。なお、httpリクエストですので、curlコマンドやpythonコードからももちろんアクセスすることができます。

$ curl https://us-central1-<PROJECT_NAME&ID>.cloudfunctions.net/<FUNCTION_NAME> 

// 戻り値 -> Hello World!

2-2. 基本的な使い方②

次は、HTTPのPOSTメソッドとそこで一緒に送付される様々なデータにも対応した関数についてです。こちらも先ほどと同じリポジトリのファイルに入っています。今回の関数は以下になります。

from flask import escape

def hello_http(request):
    request_json = request.get_json(silent=True)
    request_args = request.args

    if request_json and 'name' in request_json:  #(1)
        name = request_json['name']
    elif request_args and 'name' in request_args:  #(2)
        name = request_args['name']
    else:  #(3)
        name = 'World'
    return 'Hello {}!'.format(escape(name))

get_jsonメソッドではPOSTされたデータを解析しており、argsアトリビュートはURLを解析したもので、jsonデータの解析時にエラーが出た場合にはそのエラーを無視するように設定されています(silent=True)。そして、3つの条件分岐があり、それぞれ次のような意味になります。

  1. nameというキーを持つJsonデータがPOSTされた場合
  2. URLにnameキーを持つクエリがついたGETリクエストの場合
  3. それ以外の場合

この3つについてそれぞれ検証してみます。クエリを使うので、curlが使えるターミナルで行います。

$ curl -X POST https://us-central1-<PROJECT_NAME&ID>.cloudfunctions.net/<FUNCTION_NAME> -H "Content-Type:application/json"  -d '{"name":"Jane"}'

# 戻り値 -> Hello Jane!


# (2) URLにnameキーを持つクエリがついたGETリクエストの場合
$ curl https://us-central1-<PROJECT_NAME&ID>.cloudfunctions.net/<FUNCTION_NAME>?name=Jane

# 戻り値 -> Hello Jane!


# (3) それ以外の場合(キーをnameからcodeに変えた場合とただURLを叩いた場合)
$ curl -X POST https://us-central1-<PROJECT_NAME&ID>.cloudfunctions.net/<FUNCTION_NAME> -H "Content-Type:application/json"  -d '{"code":"Jane"}'
$ curl https://us-central1-<PROJECT_NAME&ID>.cloudfunctions.net/<FUNCTION_NAME>?code=Jane
$ curl https://us-central1-<PROJECT_NAME&ID>.cloudfunctions.net/<FUNCTION_NAME>

# すべての戻り値 -> Hello World!

HTTPリクエストはflaskのRequestオブジェクトを処理することで色々と活用することができます。使う前は色々ややこしいですが、使ってみるとかなりわかりやすいと思います。

2-3. デフォルトのパッケージについて

Cloud Functionsでは前述のようにflaskを活用しています。このflaskや標準モジュールに加え、Cloud Functionsではいくつかのパッケージが最初から設定されています。2019年3月終盤時点では以下になりますが、バージョンやデフォルトのパッケージは随時変わっていくことが考えられるので、リンク先も合わせて確認して頂ければと思います。

click==6.7
Flask==1.0.2
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
pip==18.0
requests==2.19.1
setuptools==40.2.0
Werkzeug==0.14.1
wheel==0.31.1

記載ページ

3. ローカルマシンからのデプロイ

今までGCPコンソールから関数を作る方法を作りましたが、次はローカルマシンからデプロイしてみます。その上で事前準備としてローカルマシンのターミナルでGCP用のコマンドラインツール、gcloudを使えるようにする必要があります。

3-1. gcloudを使えるようにするには

gcloudのインストールの流れは以下になります。

Google Cloud SDKの最新バージョンをインストール -> gcloudコンポーネントのアップデートとインストール

Cloud SDKとは、GCPにホストされているアプリケーションやサービスを利用するための一連のツールのことで、gcloudがコマンドラインになります。次の公式サイトに沿って、インストールしてみて下さい。

上記の2つを参考にして頂ければすぐにgcloudを導入できると思います。なお、gcloudにはCloud Functions用のコマンド以外にもGoogle App Engineなど他のGCPのコマンドも含まれています。下記のページがgcloudコマンドの公式ページで様々なコマンドについて記載されていますので、参考にして頂ければと思います。

3-2. ローカルマシンからGCPのCloud Functionsに関数をデプロイする

それでは実際にローカルマシンからGCPのCloud Functionsに関数をデプロイしてみます。フォルダ構成とそこに実行ファイルの内容は下記になります。デプロイする関数は先ほど利用したhello_http関数を利用します。この構成はCloud Functionsにデプロイする最小のデータ構成になります。フォルダ名は適宜変更して構いません。

# フォルダ構成
myfunction/
  └── main.py
from flask import escape

def hello_http(request):
    request_json = request.get_json(silent=True)
    request_args = request.args

    if request_json and 'name' in request_json:  #(1)
        name = request_json['name']
    elif request_args and 'name' in request_args:  #(2)
        name = request_args['name']
    else:  #(3)
        name = 'World'
    return 'Hello {}!'.format(escape(name))

この関数をCloud Functionsにデプロイして見ます。次のコマンドの--runtime--triggerオプションは必ず指定が必要なオプションになります。

$ gcloud functions deploy hello_http --runtime python37 --trigger-http

このコマンドを実行すると次のように表示されます。

ここに表示されたURLが関数を起動するためのURLになります。先ほどと同じようにcurlで様々なアクセスを行うと返ってくる値が都度変わってくるのがわかると思います。コマンドラインでも非常に簡単にデプロイできるので、ローカル環境で関数を作る場合、GCPコンソールでコピーアンドペーストするよりもミスが起こらなくて良いと思います。また、プログラムの変更をしたい場合は、関数名を同じにしてデプロイすればアップデートされます。この時、ターミナルで表示されるログのversionの部分が”2″などの上位の数字に変更されます。

3-3. デプロイコマンドの様々なオプション

デプロイする際にのコマンドでは色々なオプションを設定することができます。ここでは私が使用したことのあるオプションを紹介します。公式ページはこちらになります。

  • --region…クラウドサーバの場所を指定する。東京であれば”–region asia-northeast1″と指定
  • --memory…関数のメモリーを指定する。128MB、256MB、512MB、1024MB、2048MBから選択できる。デフォルトは256MB
  • --timeout…関数実行のタイムアウト時間。デフォルトは60秒で540秒以上では設定できない

他にもリトライの設定やソースの指定など、様々なオプションがありますので、公式ページで確認して頂ければと思います。

4. Part1の終わり

以上がGoogle Cloud Functionsの基本的な使い方になります。手を動かしながら使ってみれば簡単に理解できると思います。普通の使い方であれば料金を気にする必要もなく、スプレッドシートやGAE(Google App Engine)などと連携すればより幅広い使い方ができると思います。おそらくはbackground functionなんかは色々なサービスや自動化で肝となってくるのかなと思うのですが、私もよく理解していないので、今回は省略してしまいました。

次回はサードパーティや自作パッケージの利用、スプレッドシートなどとの連携方法について記載しています。私が経験したようにサーバやGCPに苦戦されている方やCloud Functionsが何者かいまいち理解できない方々の少しでも参考になっていれば幸いです。読んで頂き、ありがとうございました。