とりゅふの森

GCPデータエンジニアとして生きる

Cloud Functionsで処理したデータをCloud Firestoreに保存する

f:id:true-fly:20210906002515p:plain

おはようございます、今日はGCPのサーバレスサービス、Cloud Functionsと、NoSQLサーバレス、Cloud Firestoreを用いたアプリケーションを作ってみます。

Cloud Functions、Cloud Firestoreについてはそれぞれ以下の記事でもどうぞ。

www.true-fly.com

www.true-fly.com

今回のテーマはこちら!

Cloud Scheduker + Cloud Functions+ Cloud FirestoreでRSSデータ収集アプリケーションを作成しよう!

今回作成するもの

今回作成するものは以下のとおりです。
当ブログのRSSデータをCloud Functionsで収集し、データをCloud Firestoreに書き込みます。
Cloud FunctrionsはHTTP関数とし、Cloud Schedulerでスケジュール実行されるようにします。 f:id:true-fly:20210905000838p:plain

サービスアカウントの設定をする

GCPコンソールから、[IAMと管理] > [サービスアカウント] > [サービスアカウントを作成]と進みます。

console.cloud.google.com

名前や説明は任意です。 サービスアカウントに「編集者」ロールを付与します。

その後、認証情報(秘密鍵)をservice_account.jsonというファイル名でローカルに保存しておきます。

Cloud Firestoreの設定

「true-fly-rss」という新しいコレクションを作成しておきます。
最初のドキュメントは適当に作成し、後で削除します。 f:id:true-fly:20210905233921p:plain

Cloud Functions関数の作成

今回はPython3.8で作成していきます。

ローカルディレクトリ構成

ディレクトリ構成は以下の通りになります。
まずはローカルで動くようにプログラムを書いていきます。

.
├── .gcloudignore
├── main.py
├── requirements.txt
└── service_account.json

環境変数の設定

service_account.jsonがあるディレクトリで以下を実行します。プログラム上で参照する環境変数です。

export PROJECT_ID=自分のGCPのプロジェクトID
export GOOGLE_APPLICATION_CREDENTIALS="$PWD/service_account.json"

Pythonライブラリのインストール

以下のrequirements.txtをローカルに作成します。

cachetools==4.2.2
certifi==2021.5.30
charset-normalizer==2.0.4
google-api-core==2.0.1
google-auth==2.0.2
google-cloud-core==2.0.0
google-cloud-firestore==2.3.1
googleapis-common-protos==1.53.0
grpcio==1.39.0
idna==3.2
packaging==21.0
proto-plus==1.19.0
protobuf==3.17.3
pyasn1==0.4.8
pyasn1-modules==0.2.8
pyparsing==2.4.7
requests==2.26.0
rsa==4.7.2
six==1.16.0
urllib3==1.26.6
xmltodict==0.12.0

その後、以下のコマンドでローカルにライブラリをインストールしましょう。

pip install -r requirements.txt

.gcloudignoreの作成

.gcloudignoreファイルを作成します。
今回はCloud Functions関数をgcloudコマンドでローカルからデプロイするため、サービスアカウントのJSONファイルだったり、不要なファイルをデプロイしないように記述しておきます。

service_account.json

Pythonファイルの作成

以下のファイルをmain.pyとして作成しておきます。
本ブログからRSSデータを取得し、Cloud Firestoreに書き込むプログラムです。
ローカルで実行しても動くように作成しています。デプロイする前にローカル環境でも実行できることを確認しておきましょう。

import json
from datetime import datetime
import os

from google.cloud import firestore
from google.cloud.firestore_v1 import collection
import requests
import xmltodict


def __get_rss_data():
    res = requests.get('https://www.true-fly.com/rss')
    res_xml = xmltodict.parse(res.text)
    json_object = json.dumps(res_xml)
    dic = json.loads(json_object)

    return_json = []
    for row in dic['rss']['channel']['item']:
        return_json.append({
            'key': row['link'].replace('https://www.true-fly.com/entry/', '')
                              .replace('/', ''),
            'url': row['link'],
            'title': row['title'],
            'description': row['description'],
            'published_at': datetime.strptime(row['pubDate'], '%a, %d %b %Y %H:%M:%S %z'),
            'categories': row['category'],
        })
    return return_json


def __set_documents(data):
    db = firestore.Client()
    my_collection = db.collection('true-fly-rss')

    for row in data:
        doc_ref = my_collection.document(row['key'])
        row.pop('key')
        doc_ref.set(row, merge=True)


def execute(request):
    rss_data = __get_rss_data()
    __set_documents(rss_data)
    # returnを書かないとHTTP関数がエラーになるので、空dictを返します
    return {}


if __name__ == '__main__':
    execute(None)
データが冪等になるように、38行目のドキュメントをsetする時に、`merge=True`のオプションを設定しています。ドキュメントのキーはURLを加工したもので、1記事1ドキュメントになるようになっています。

Cloud Funtions関数をデプロイする

Cloud Functions関数をデプロイします。今回はgcloudコマンドでデプロイします。
以下のコマンドで、Python3.8のHTTP関数を作成します。
--entry-pointはプログラムのエントリポイントとなる関数名を書きます。
--no-allow-unauthenticatedは、関数呼び出し時に認証を必要とさせるオプションです。
--service-accountは、どのサービスアカウントで関数を実行するかを設定するオプションです。

gcloud config set  [プロジェクトID]
gcloud functions deploy get-rss-data \
    --region asia-northeast1 \
    --entry-point execute \
    --runtime python38 \
    --trigger-http \
    --no-allow-unauthenticated \
    --service-account=[サービスアカウント]

コマンド実行し、以下のメッセージと、関数の設定詳細が標準出力されたら成功です! Deploying function (may take a while - up to 2 minutes)...done.

WebUIで関数が見られることを確認しましょう。

f:id:true-fly:20210906002148p:plain

Cloud Schedulerの設定

以下を参考にHTTP関数を呼び出しするようにジョブを設定します。詳細は省略。 www.true-fly.com

実行結果

Cloud Scheculerから手動実行して、結果を確認してみましょう。
Cloud FirestoreのWeb UIからtrue-fly-rssコレクションを確認してみます。

f:id:true-fly:20210906002800p:plain

無事データが登録されていることが確認できました!

まとめ

以上、今回はCloud Scheduker + Cloud Functions+ Cloud Firestoreを組み合わせて一つのサービスを開発する方法について学びました。
フルクラウドで、サーバレスで、プログラムのスケジュール実行と、データの読み書きができます。

無料枠の範囲内で、GCPでちょっとしたスケジュール実行サービスを開発したいのであれば、Cloud Scheduker + Cloud Functions+ Cloud Firestoreの組み合わせがおすすめです!

今回の例のように、RSSデータだったり、スクレイピング、外部APIなどでデータを収集し、それをスキーマレスのCloud Firestoreに貯めるといった流れは、実際の業務のデータ収集基盤としても応用できそうです!

GCPの教科書

GCPの教科書

Amazon