Hirosaji が 2021年05月02日21時54分24秒 に編集
初版
タイトルの変更
投資信託の儲けをモニタリングする
タグの変更
obniz
Node
サーバレス
AWS
メイン画像の変更
本文の変更
# なに作ったの 投資信託の儲け(評価損益)と総額(評価額)を監視するモニターを実装しました。 実装したのは、投資信託のデータを定期的に取得し、obniz board のディスプレイに表示する監視システムです。 システムの構成要素は、すべて AWS 上に構築しました。 # なぜ作ったの 通常、自分の投資信託の金額を確認するには、PC やスマホで契約する証券サービスのマイページにアクセスする必要があります。 この「マイページにアクセスする」手間を省くため、今回は投資信託の金額を常時モニターするシステムを作りました。 # 実行例 決まった時間になると、モニターが更新されます。 (個人情報なので、デモ動画では適当な金額を表示しています) @[youtube](https://youtu.be/ptzwoWGv380) # 必要なモノ - obniz board 1Y - AWS アカウント # システム構成 ![キャプションを入力できます](https://camo.elchika.com/3c64db017a8544648cf28c8541cba8ce542912b5/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63643739353239622d366434662d343138302d396432362d3433383137633834346233322f61336236393130312d633065312d346432362d623436322d343635646130636135636130/) ## CloudWatch:定期実行をスケジューリング CloudWatch Event をトリガーに、用意した Lambda 関数を実行します。 定期実行のスケジュールなどは、AWS コンソール上で設定できます。 ## Lambda①:投信データを取得 自分の投資信託の情報をリクエストまたはスクレイピングし、S3 にデータを保存する Lambda 関数です。 一通りの処理が終わった後、obniz のモニターを制御する別の Lambda 関数を実行します。 ### ソースコード ```js:index.js const chromium = require('chrome-aws-lambda'); const puppeteer = require('puppeteer-core'); const AWS = require('aws-sdk') const s3 = new AWS.S3() const lambda = new AWS.Lambda({apiVersion: '2015-03-31'}); exports.handler = async (event, context) => { const response = { statusCode: 200, body: JSON.stringify('done'), }; try { /************************************ ** ここでpuppeteer等でデータを取得する ** *************************************/ const sum_value = "<取得した評価額>"; const profit_value = "<取得した評価損益>"; const now_date = await new Date().toLocaleString("ja"); const s3_get_params = { Bucket: 'YOUR_BUCKET_NAME', Key: 'YOUR_JSON_NAME.json' }; const _data = await s3.getObject(s3_get_params).promise(); const data = await JSON.parse(_data.Body) await data.push({ ts: now_date, sum: sum_value, profit: profit_value }) const s3_put_params = { Bucket: 'YOUR_BUCKET_NAME', Key: 'YOUR_JSON_NAME.json', Body: JSON.stringify(data) }; await s3.putObject(s3_put_params).promise(); await browser.close(); await lambda.invoke({ FunctionName: "LAMBDA②_FUNC_NAME", InvocationType: "RequestResponse", Payload: JSON.stringify(event) }).promise(); } catch (error) { return context.fail(error); } finally { if (browser !== null) { await browser.close(); } } return context.succeed(response); }; ``` ### 開発のTips #### **1. Lambda にはパッケージサイズ制限がある** AWS Lambda には[デプロイできるパッケージサイズに制限](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/gettingstarted-limits.html)があります。 - 50 MB (zip 圧縮済み、直接アップロード) - 250 MB (解凍、レイヤーを含む) Puppeteer などのスクレイピングに使われるライブラリはサイズが大きいため、この制限を超過してしまう場合がほとんどです。 そこで、必要なライブラリ類を圧縮したり、 Lambda Layer を利用したりなど、サイズの大きなライブラリを使うための工夫が必要になります。 - 参考サイト - [ヘッドレスChromeをAWS Lambda上のPuppeteerから操作してみた | Developers IO](https://dev.classmethod.jp/articles/run-headless-chrome-puppeteer-on-aws-lambda/) この時、メモリやタイムアウト時間など、Lambda 関数の実行環境も併せて調整しましょう。 #### **2. lambda:Invoke API を呼び出すにはカスタムポリシーの編集が必要** Lambda 関数から別の Lambda 関数を実行するもっともメジャーな方法は、Invoke API を使うことです。 Invoke API を使うためには、`lambda:InvokeAsync` や `lambda:InvokeFunction` の権限を付与する必要があります。 - 参考サイト - [Lambda アクションのリソースと条件 | AWS](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-api-permissions-ref.html) ## S3:投信データを保管 取得した投資信託のデータを保存するストレージです。 後述する Lambda 関数からバケット内のファイルを読み込むため、事前にアクセス許可の設定をしておきます。 ## Lambda②:obniz のモニターを更新 S3 に保存した投資信託のデータを描画した Canvas 要素を作り、obniz のディスプレイに反映する Lambda 関数です。 ### ソースコード ```js:index.js const Obniz = require("obniz"); const request = require('request'); const path = require('path'); const { createCanvas, registerFont } = require('canvas'); exports.handler = async (event, context) => { const obnizID = process.env["OBNIZ_ID"]; const accessToken = process.env["ACCESS_TOKEN"]; const obniz = new Obniz(obnizID, { access_token: accessToken }); const response = { statusCode: 200, body: JSON.stringify('done'), }; const connected = await obniz.connectWait({ timeout: 5 }); if (connected) { const handleDisplay = (json) => { obniz.display.clear(); const targetObj = json[json.length - 1]; registerFont('YOUR_FONT_PATH', { family: 'FONT_NAME' }) const canvas = createCanvas(obniz.display.width, obniz.display.height); const ctx = canvas.getContext('2d'); const _rate = targetObj.profit / (targetObj.sum - targetObj.profit); const rate = Math.round((_rate * 10000)) / 100; const prefix = (targetObj.profit > 0) ? "+" : "-"; const kaomoji = (targetObj.profit > 0) ? "^^" : "^^;"; ctx.fillStyle = "white"; ctx.font = "12px 'FONT_NAME'"; ctx.fillText(targetObj.sum + "円", 5, 17); ctx.fillText(prefix + targetObj.profit + "円 (" + prefix + rate + "%)", 5, 37); ctx.fillText(targetObj.ts.split(" ")[0] + " updated", 5, 57); ctx.fillText(kaomoji, 100, 17); obniz.display.draw(ctx); } const jsonPath = "https://YOUR_BUCKET_NAME.s3-ap-northeast-1.amazonaws.com/YOUR_JSON_NAME.json"; await request.get(jsonPath, function (error, response, body) { if (!error && response.statusCode == 200) { handleDisplay(JSON.parse(body)); } }); } await obniz.wait(500) await obniz.resetOnDisconnect(false); await obniz.close(); return await context.succeed(response); }; ``` ### 開発のTips #### 1. Lambda で文字を利用するためにはフォントデータが必要 文字を載せる Canvas 要素を作るには、フォントが必要です。 Lambda は(おそらく)デフォルトでフォントをサポートしていないため、フォントデータを追加する必要があります。 ここでオススメなのが、好きなフォントを集めて Lambda Layer を作っておくことです。 Lambda Layer は別の Lambda でも再利用ができるので、今後のことを見越して、この機にぜひ作っておいてはいかがでしょう。 #### 2. obniz ライブラリのメソッド呼び出し順に注意 Javascript はシングルスレッドですが、async / await を使うと少し実行順に工夫が必要です。 特に、プログラム終了後の制御に関わる `obniz.close()` や `obniz.resetOnDisconnect()` あたりの呼び出しには注意してください。 # まとめと応用例 今回は日常の習慣を効率化するため、投資信託を監視するモニターを作り、その制作方法を紹介しました。 データの取得先を変えれば、他にも色んなデータを監視することができます。 例えば... - 近所のコロナ感染者数(各自治体が公開するAPIにて取得) - Webサイトの訪問者数(利用するアクセス解析ツールのAPIにて取得) - 近隣スーパーの混雑度(Googleマップの混雑情報をスクレイピング) - 狙っている商品の価格(ネットショップの商品ページをスクレイピング) - 競馬で狙う馬のオッズ(競馬サイトをスクレイピング) など、その応用の幅はアイデアの数だけ広がります。 ぜひ皆さんもお手持ちの obniz で、身近なデータをモニタリングしてみてはいかがでしょうか。