#はじめに
私はよく、スクレイピングで収集したデータをCSVファイルで出力しています。
しかし、データを効率よく使うには、CSVファイル内のデータをデータベースなどにストアしなくてはいけません。
せっかくデータ収集を省力化しても、データベースの運用に時間を取られるのは苦痛ですよね?
その悩み、クラウドのデータベースサービスで解決しましょう!
本投稿では、AWS の DynamoDB にCSVファイル内のデータを自動で反映する方法をご紹介します。
これで煩わしいデータベース更新業務とはオサラバです!
#目次
今回は、既にAWSで自動更新したいDynamoDBが存在し、スクレイピングなどで取得したCSVファイルでそのファイルを更新したい場合を想定します。
#AWS CLI の設定
AWSに接続するために、AWS CLI をインストールして初期設定を行います。
表示に従い、各種設定を登録すればOKです。
アクセスキーとシークレットアクセスキーは、AWSのセキュリティ認証ページから「アクセスキーの作成」を実行して用意してください。
参考:AWS CLIのインストールから初期設定メモ
$ sudo pip install awscli
$ aws configure
AWS Access Key ID [None]: {アクセスキー}
AWS Secret Access Key [None]: {シークレットアクセスキー}
Default region name [None]: {AWSのリージョン}
Default output format [None]: {json, yaml, text, table から出力形式を選ぶ}
設定の確認はaws configure list
で行います。
#DynamoDBのテーブル更新用コード (Python)
例えば、スクレイピングなどで取得した下記のようなCSVファイルがあると仮定します。
メンチカツ,パン粉,ひき肉,母
エビフライ,パン粉,エビ,母
エビ天,天ぷら粉,エビ,母
イモ天,天ぷら粉,サツマイモ,母
大福,もち,あんこ,母
これを用いて、以下のDynamoDBを更新します
料理(主キー) | 衣 | 中身 | 料理人 |
---|---|---|---|
メンチカツ | パン粉 | ひき肉 | 母 |
DynamoDBにCSVの内容を追記するには、下記の更新用コードを実行します。
import csv
import boto3
from boto3.dynamodb.conditions import Key
def addDynamoDB():
dynamodb = boto3.resource("dynamodb", region_name="データベースのリージョン")
table = dynamodb.Table("DynamoDBテーブル名") # テーブル名を指定して変数に格納
filepath = "揚げもの.csv"
with open(filepath, "r", encoding="utf-8") as f:
reader = csv.reader(f)
# batch_writer()で、25項目ずつCSVの全てをputする
with table.batch_writer() as batch:
for row in reader:
item = {
"料理": row[0],
"衣": row[1],
"中身": row[2],
"料理人": row[3],
}
batch.put_item(Item=item)
if __name__ == "__main__":
addDynamoDB()
addDynamoDB.py 実行後のデータベース
料理(主キー) | 衣 | 中身 | 料理人 |
---|---|---|---|
メンチカツ | パン粉 | ひき肉 | 母 |
エビフライ | パン粉 | エビ | 母 |
エビ天 | 天ぷら粉 | エビ | 母 |
イモ天 | 天ぷら粉 | サツマイモ | 母 |
大福 | もち | あんこ | 母 |
これでDynamoDBの更新ができるようになりました。
しかしここで問題があります。大福は揚げ物ではないですよね?
table.batch_writer()
はDynamoDBの追記はできますが、項目の削除はできません。
例えば、揚げもの.csvを下記のように修正しても、addDynamoDB.pyではDynamoDBに変化はありません。
メンチカツ,パン粉,ひき肉,母
エビフライ,パン粉,エビ,母
エビ天,天ぷら粉,エビ,母
イモ天,天ぷら粉,サツマイモ,母
DynamoDBとCSVの内容を同期するには、下記の更新用コードを実行します。
import csv
import boto3
from boto3.dynamodb.conditions import Key
def updateDynamoDB():
dynamodb = boto3.resource("dynamodb", region_name="データベースのリージョン")
table = dynamodb.Table("DynamoDBテーブル名") # テーブル名を指定して変数に格納
# DynamoDBに対してクエリを実行、データを取得する
response = table.query(
IndexName="DynamoDBインデックス名", # インデックス名の指定
# その他オプションがあれば以下に記入する
# 例: テーブルの「料理人」が「母」の要素のみを取得したい場合、以下を設定する
KeyConditionExpression=Key("料理人").eq("母") # 例
)
dbItems = response['Items']
# レスポンスが1MBを超えた場合、LastEvaluatedKeyが含まれなくなるまでループする
while 'LastEvaluatedKey' in response:
response = table.query(
IndexName="DynamoDBインデックス名", # インデックス名の指定
ExclusiveStartKey=response['LastEvaluatedKey'],
# その他オプションがあればここに記入する
# 例: テーブルの「料理人」が「母」の要素のみを取得したい場合、以下を設定する
KeyConditionExpression=Key("料理人").eq("母") # 例
)
dbItems.extend(response['Items'])
csvItems = []
filepath = "揚げもの.csv"
with open(filepath, "r", encoding="utf-8") as f:
reader = csv.reader(f)
# batch_writer()で、25項目ずつCSVの全てをputする
with table.batch_writer() as batch:
for row in reader:
item = {
"料理(主キー)": row[0],
"衣": row[1],
"中身": row[2],
"料理人": row[3],
}
batch.put_item(Item=item)
csvItems.append(row[0])
# CSVファイルに存在しない型番は、batch.delete_item()でDBからdeleteする
for dbItem in dbItems:
if dbItem["料理(主キー)"] not in csvItems:
batch.delete_item(
Key={
"料理(主キー)": dbItem["料理(主キー)"],
}
)
if __name__ == "__main__":
updateDynamoDB()
updateDynamoDB.py 実行後のデータベース
料理(主キー) | 衣 | 中身 | 料理人 |
---|---|---|---|
メンチカツ | パン粉 | ひき肉 | 母 |
エビフライ | パン粉 | エビ | 母 |
エビ天 | 天ぷら粉 | エビ | 母 |
イモ天 | 天ぷら粉 | サツマイモ | 母 |
これで、コードを手動で実行すればDynamoDBが更新できるようになりました。
では、これを自動化するにはどうすれば良いでしょうか?
いくつか方法はありますが、今回はそのうちオススメの二つをご紹介させていただきます。
#方法1:仮想マシン(VM)からcronで定期実行
クラウド上で、AWS CLI など環境を整えたVMインスタンスを立ち上げるのが一つの方法です。
VMインスタンスは、過去の記事でも取り扱ったGoogle Cloud Engine の無料枠を使います。
過去の記事:クラウドで手軽に始めるクローリング
VMインスタンス上に上記のコードを上げて、cronで定期実行しましょう。
cronはcrontab -u < ユーザ名 > -e
コマンドで以下ように設定してください。
SHELL=/bin/bash
CRON_TZ="Japan"
00 00 01 * * python3 /< 任意の絶対パス >/updateDynamoDB.py
00 00 01 * *
の部分は、DynamoDBの更新をしたい任意の時間と頻度に変更しましょう。
前回の記事のようなクローラーがある場合、クローラーの実行が終了次第、DynamoDB への反映が実行されるように実装すると便利です。
#方法2:GitHub Actions
CSVファイルをGitHub上で管理することで、git push時に自動でDynamoDBが更新される実装も可能です。
具体的には、GitHub Actions のワークフローで実行します。
Git リポジトリに.github/workflows/
ディレクトリを作成して、その中にYAML形式で定義してください。
YAMLファイルは例えば以下のように記述します。
name: 任意のAction名
on:
push:
branches:
# Run only on changes on master branches
- 'master'
paths:
# Run only on changes on these files
- '**/揚げもの.csv'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 25
- name: Python3.8の設定
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Pythonライブラリのインストール
run: |
python -m pip install --upgrade pip
pip install boto3
- name: AWS認証情報の設定
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: 任意のリージョン
- name: DynamoDBの更新
run: |
python /< 任意の絶対パス >/updateDynamoDB.py
これでmasterブランチにpushされたことをトリガーに、updateDynamoDB.py
が実行されます。
YAMLファイルの記法について、GitHub公式と併せて下記の記事が特に参考になりました。ありがとうございます。
参考:Github Actionsの使い方メモ
GitHubアクションの「AWS認証情報の設定」については、詳しいことはGitHub公式ページで確認できます。
#二つの方法の比較
VMからの実行(GCE) | GitHub Actions | |
---|---|---|
ファイルのタイムスタンプ | 有り | 無し |
環境構築 | GCEのVMインスタンスが必要 | GitHubリポジトリとgit 設定が必要 |
自由度の高い運用1 | 比較的簡単 (クラウドのサービスも利用できる) |
難しい |
所感としては、
VMからの実行はクラウドサービスを合わせて活用したいタスクや、実行時間が長く複雑なタスクに、
GitHub Actions はgitの更新をトリガーにしたいタスクや、実行時間が少なくシンプルなタスクに向いているように感じました。
あなたの目的に合った方を活用してくださいね。
#参考
GithubにSSH鍵登録
GithubのSSH通信設定
AWS CLIのインストール
AWS CLIのインストールから初期設定メモ
Github Actionsの使い方メモ
GitHubアクションの「AWS認証情報の設定」アクション
Github Actionsの使い方メモ
Pythonで手軽に始めるWebスクレイピング
クラウドで手軽に始めるクローリング
-
特殊な運用は煩雑になりがち。例えば、GitHubにpushしたくない中間ファイルを出力閲覧したい場合、中間ファイル用ストレージを別に用意したり、gitのコマンドを工夫する必要がある。
|トリガー|基本はcronのできる範囲のみ|リモートへのpushやpull requestの作成をトリガーにできる|
|料金|USリージョンのf1-microインスタンス1個だけなら無料。リージョンやインスタンスにより、プランに応じて料金が発生する(E2-mediumで、$0.01005/時間)|リポジトリが公開なら無料。非公開なら、Actionsの実行時間が2000分/月を超えると、利用時間に応じて料金が発生する(Linuxで$0.008/分)|
|VMのスペック|選択可能2|固定3| ↩ -
Standard_DS2_v2, CPUx2, メモリ 7Gb, ストレージ 14Gb, MAC Stadium ↩