enebularとimport.ioでPlayStation VRの価格をAWS Lambdaで監視してSlackで通知

## 概要

[import.io](https://www.import.io/)を利用して購入を検討している価格.comの[PlayStation VR CUHJ-16000](http://kakaku.com/item/K0000835986/)のランキング情報を1時間おきに取得して希望金額を下回った店舗だけをSlack通知します。

## import.ioの設定

import.ioのダッシュボード左上にある"New Extractor"をクリックします。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/66fd4a53-ccb4-ac2d-50b4-7de9478a195c.png)

以下の"Insert a URL with data here"に[PlayStation VR CUHJ-16000](http://kakaku.com/item/K0000835986/)のURL（ http://kakaku.com/item/K0000835986/ ）を入力して"Go"をクリックします（もちろん、お好みの商品ページで問題ありません）

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/636dec35-95fe-faa5-15ce-b9e904cc7ef6.png)

以下のように良い具合にランキングの部分をデータ化してくれました。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/9cf6a10f-1c93-bd9c-e18a-cf3934897829.png)

以下のようにすると列が削除できますので必要な列だけにします。今回は店名と価格だけします。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/92c12ba9-0292-c9c0-179d-fe58203cb616.png)

列を絞ったら最後に"Done"をクリックします。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/da027c6c-a6ac-5aa8-6d5e-d35801423f25.png)

## Flowの作成

import.ioで作成したAPIのURLは以下の"Integrate"で確認できます。今回使うURLは"Live query API"です。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/591c6811-38dc-ece5-ddb5-1fa456ff1c11.png)

それでは、enebular Flowを作成します。まずは以下のようにInjectノードとHTTP RequestノードとDebugノードをつなげます。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/e55ddd3b-f41a-23b4-0cc3-bc8054f6ad38.png)

HTTP RequestノードのURLに先ほどのimport.ioの"Live query API"を入力します。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/a115e5d9-9481-b150-3188-05f2151108cb.png)

これで実行するとDebugに以下のような文字列が表示されます。

```json
{
	"extractorData": {
		"url": "http://kakaku.com/item/K0000835986/",
		"resourceId": "8398f7d35a6a30eb23a8e7f5d7e8a005",
		"data": [{
			"group": [{
				"Fontprice price": [{
					"text": "¥64,797"
				}],
				"Wordwrapshop link": [{
					"text": "KADEN SHOP",
					"href": "http://kakaku.com/shop/2184/?pdid=K0000835986&lid=shop_itemview_shopname"
				}]
			}, {
				"Fontprice price": [{
					"text": "¥64,798"
				}],
				"Wordwrapshop link": [{
					"text": "DC-LIFE",
					"href": "http://kakaku.com/shop/2134/?pdid=K0000835986&lid=shop_itemview_shopname"
				}]
			}, {
				"Fontprice price": [{
					"text": "¥64,799"
				}],
				"Wordwrapshop link": [{
					"text": "S-SALE",
					"href": "http://kakaku.com/shop/2119/?pdid=K0000835986&lid=shop_itemview_shopname"
				}]
			}, {
				"Fontprice price": [{
					"text": "¥65,800"
				}],
				"Wordwrapshop link": [{
					"text": "ケレス.com",
					"href": "http://kakaku.com/shop/2390/?pdid=K0000835986&lid=shop_itemview_shopname"
				}]
			}, {
				"Fontprice price": [{
					"text": "¥67,000"
				}],
				"Wordwrapshop link": [{
					"text": "オールワン電気",
					"href": "http://kakaku.com/shop/2059/?pdid=K0000835986&lid=shop_itemview_shopname"
				}]
			}, {
				"Fontprice price": [{
					"text": "¥68,686"
				}],
				"Wordwrapshop link": [{
					"text": "アウトレットプラザ",
					"href": "http://kakaku.com/shop/610/?pdid=K0000835986&lid=shop_itemview_shopname"
				}]
			}, {
				"Fontprice price": [{
					"text": "¥69,032"
				}],
				"Wordwrapshop link": [{
					"text": "エクセラー",
					"href": "http://kakaku.com/shop/2350/?pdid=K0000835986&lid=shop_itemview_shopname"
				}]
			}, {
				"Fontprice price": [{
					"text": "¥69,800"
				}],
				"Wordwrapshop link": [{
					"text": "エクセラー",
					"href": "http://kakaku.com/shop/1455/?pdid=K0000835986&lid=shop_itemview_shopname"
				}]
			}, {
				"Fontprice price": [{
					"text": "¥71,159"
				}],
				"Wordwrapshop link": [{
					"text": "Premium Outlet Plaza",
					"href": "http://kakaku.com/shop/2482/?pdid=K0000835986&lid=shop_itemview_shopname"
				}]
			}, {
				"Fontprice price": [{
					"text": "¥81,000"
				}],
				"Wordwrapshop link": [{
					"text": "QQstore.jp",
					"href": "http://kakaku.com/shop/2243/?pdid=K0000835986&lid=shop_itemview_shopname"
				}]
			}]
		}]
	},
	"pageData": {
		"resourceId": "8398f7d35a6a30eb23a8e7f5d7e8a005",
		"statusCode": 200,
		"timestamp": 1481801622181
	},
	"url": "http://kakaku.com/item/K0000835986/",
	"runtimeConfigId": "0036e3e0-377e-4049-833c-29a70471264b",
	"timestamp": 1481802205829,
	"sequenceNumber": -1
}
```

`Fontprice price`とか`Wordwrapshop link`とかスペースが含まれていると取り扱い難いのでスペースを`_`（アンダースコア）に変換します。以下のようにChangeノードを追加します。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/f87fb94e-5426-d20d-b6e3-0c32ac92e872.png)

Changeノードは以下のようなChange設定にすることで再帰的にスペースをすべて`_`（アンダースコア）に置換します。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/c9eb1b22-6ea6-51c8-d746-c4e2cc0abe96.png)

あと、気をつけなければいけないのはJSONオブジェクトではなく文字列なのでJSONノードを置いてJSONオブジェクトへ変換します。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/e2cc8951-d2da-bcc9-10b8-0c8394d9afe0.png)

次にランキングの各店舗の金額で希望金額を下回ったものだけを抽出する以下のようなコードをFunctionノードに書きます（希望金額がハードコーディングされてますが本来はもっと工夫すべきですね）

```js
var shop, price, data = msg.payload.extractorData.data[0].group;
msg.notif = false; // 希望金額を下回る店舗の有無フラグ
msg.payload = '';

for (i = 0; i < data.length; i++) {

    shop = data[i].Wordwrapshop_link[0].text;
    price = data[i].Fontprice_price[0].text.replace('¥', '').replace(',', '');

    // 希望金額と比較（本来はハードコーディングでない方法にしましょう）
    if (price < 64400) {

        // 希望金額を下回る店舗が1店舗でもあればtrueにする
        msg.notif = true; 

        // 希望金額を下回った店舗の店名と金額とリンクをpayloadに追加
        // Slackで通知するために解りやすい体裁のテキストにしている
        msg.payload += shop + ': ' + data[i].Fontprice_price[0].text + '円 <' + data[i].Wordwrapshop_link[0].href + '>\n'; 
    }
}
return msg;
```

その次にSwitchノードを置いて`msg.notif`が`true`の場合のみ後続へ続くように設定します。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/7ceca651-6052-e851-8662-0f0ad628431b.png)

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/a41f769f-d05e-e961-49f9-f4b0aa285a49.png)

## Slack通知

Slackの"Apps & integrations"をクリックします。

<img width="266" alt="スクリーンショット 2016-12-16 20.25.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/21109/7b101a16-b85c-b046-7d10-f078c82047cb.png">

"Find a new app, or a service you already use"に"webhook"と入れてサジェストされる"Incoming WebHooks"を選択します。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/d777def7-12fe-422d-6a42-7b30dd81cc47.png)

続いて"Add Configuration"をクリックします。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/eb5c6df7-923a-7e73-9f12-a9b4dddb9845.png)

通知したいチャネルを選択して"Add Incoming WebHooks integration"をクリックします。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/9d263dec-ea08-fd88-6424-8ddd59e48d76.png)

これで設定完了です。次の画面の"Example"の通りcurlなどで動作確認しましょう。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/31cd5f06-3b8c-60d5-b0c5-ad9bf210042f.png)

enebular Flowに戻って以下のようにSlackノードを置きます。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/11632975-874b-d01a-807c-8638424633a6.png)

Slackノードの設定は以下のような感じです。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/8e1e7e49-108c-e244-e137-f9d4f264e679.png)

これで一度Flowを実行して以下のようにSlackに通知されればOKです（動作確認なので必ず通知されるように下回る希望金額を設定しましょう）

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/5bc34a07-5461-78c3-e2ab-64b70d278096.png)

## Export to Other ServicesでAWS Lambdaにデプロイ

enebularのFlowエディタは開発用途で一時的に利用するものなので作成したロジックを安定した環境で動かすためにExport to Other ServicesでAWS Lambdaにデプロイします。AWS Lambdaで動かすためにはAWS LambdaのInput/Outputで起動するようにaws lambda request/responseノードをFlowに追加します。

```json
[{"id":"6901f707.bd2f2","type":"inject","z":"c4d430a6.5496f","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":117,"y":29,"wires":[["b55bf317.aaaf1"]]},{"id":"b55bf317.aaaf1","type":"http request","z":"c4d430a6.5496f","name":"","method":"GET","ret":"txt","url":"＜import.ioのLive query API URL＞","x":311,"y":54.5,"wires":[["c11dc614.37d168"]]},{"id":"d2ae2484.3e5918","type":"json","z":"c4d430a6.5496f","name":"","x":125.5,"y":119.75,"wires":[["4b3ebd57.942b54"]]},{"id":"3cb45b47.7ce6f4","type":"debug","z":"c4d430a6.5496f","name":"","active":true,"console":"false","complete":"payload","x":566,"y":168,"wires":[]},{"id":"c11dc614.37d168","type":"change","z":"c4d430a6.5496f","name":"","rules":[{"t":"change","p":"payload","pt":"msg","from":" ","fromt":"str","to":"_","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":507,"y":56,"wires":[["d2ae2484.3e5918"]]},{"id":"7b4236e2.df01","type":"aws-lambda-request","z":"c4d430a6.5496f","x":109.5,"y":73.5,"wires":[["b55bf317.aaaf1"]]},{"id":"14e89f5f.296731","type":"aws-lambda-response","z":"c4d430a6.5496f","x":345.5,"y":181,"wires":[]},{"id":"4b3ebd57.942b54","type":"function","z":"c4d430a6.5496f","name":"","func":"var shop, price, data = msg.payload.extractorData.data[0].group;\nmsg.notif = false;\nmsg.payload = '';\nfor (i = 0; i < data.length; i++) {\n    shop = data[i].Wordwrapshop_link[0].text;\n    price = data[i].Fontprice_price[0].text.replace('¥', '').replace(',', '');\n    if (price < 64400) {\n        msg.notif = true;\n        msg.payload += shop + ': ' + data[i].Fontprice_price[0].text + '円 <' + data[i].Wordwrapshop_link[0].href + '>\\n'; \n    }\n}\nreturn msg;","outputs":1,"noerr":0,"x":253.5,"y":120.5,"wires":[["dcf58bbb.bd5498"]]},{"id":"dcf58bbb.bd5498","type":"switch","z":"c4d430a6.5496f","name":"","property":"notif","propertyType":"msg","rules":[{"t":"true"}],"checkall":"true","outputs":1,"x":376.5,"y":121.5,"wires":[["264fe0f3.637cb8","3cb45b47.7ce6f4","6c12cbb3.8293ac"]]},{"id":"264fe0f3.637cb8","type":"slack","z":"c4d430a6.5496f","name":"","channelURL":"＜Slackで作成したWebHook URL＞","username":"webhookbot","emojiIcon":"","channel":"","x":543.5,"y":119,"wires":[]},{"id":"6c12cbb3.8293ac","type":"delay","z":"c4d430a6.5496f","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":145.5,"y":182.5,"wires":[["14e89f5f.296731"]]}]
```

ここで注意なのがAWS Lambda Responseノードの前にあるDelayノードです。Slack通知の処理が完了する前にAWS Lambda Responseノードが動いてしまうとLambda functionが終了してしまいますのでデフォルトの5秒待つようにしています。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/96b1a387-9ab6-d630-ad21-5207d818c599.png)

次に以下のようにしてAWS Lambdaデプロイ画面を開きます。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/b4337f9c-5f2f-8822-9a23-43016f6561a7.png)

functionの属性を入力してデプロイします。この辺りの詳細は[enebular Depelopers](https://enebular.com/developers/flow-lambda)を参照ください。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/374426de-2084-96fa-8b08-a6bd4f715eae.png)

最後にAWS Lambdaのトリガを設定します。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/d9a7c758-1400-8bbb-51ee-526173dd1eb3.png)

開いたダイアログの赤枠の部分をクリックしていって"CloudWatch Events - Schedule"を選択します。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/c353bc32-4f21-dd0b-1512-80b3e2f209f1.png)

スケジュールを設定する画面になりますので、任意のルール名とdescriptionを入力します。監視間隔は価格.comさんに迷惑がかからないように1時間間隔にします。

![image](https://qiita-image-store.s3.amazonaws.com/0/21109/f46c1e7b-743e-3f88-03c9-cad0767949fa.png)

これで自動化OKです！