近くのお店で電子マネーが使えるか教えてくれるLINE BotをNode-REDと公式サイトのスクレイピングでさくっとつくる

最近引越したのですが、電子マネーが便利すぎて財布を持たずにその辺の店に入ったら現金のみの不意打ちを食らって恥ずかしくなることがあります。それを回避するために「このお店で電子マネー使える？」をすぐ教えてくれるLINE Botがあったら便利だなあと思い、プロトタイプを作ってみました。

## できたもの（結果）

[![Image from Gyazo](https://i.gyazo.com/1a3aba7605336ac8c9f17d969133f847.gif)](https://gyazo.com/1a3aba7605336ac8c9f17d969133f847)

今回はWAONが使えるかを調べるものにしてみました。
店舗名をBotに送ると、 <https://www.waon.net/shop/> のHTMLを取得してスクレイピングして店舗名を配列で取得し、その配列の中に「ユーザーが送った店舗名」が存在するかしないか、で判断しています。

## 使ったもの

### サービス

- [Heroku](https://www.heroku.com/home)
- [Node\-RED](https://nodered.jp/)

### Node-RED上で追加インストールしたノード

`パレットを管理` からインストールできます。

- [node\-red\-contrib\-line\-messaging\-api \(node\) \- Node\-RED](https://flows.nodered.org/node/node-red-contrib-line-messaging-api)


## 1. Heroku一発ボタンでNode-REDが使えるサーバーを5分で作る

[joeartsea/node\-red\-heroku \- Buttons \- Heroku Elements](https://elements.heroku.com/buttons/joeartsea/node-red-heroku)

クリックするだけでお手軽に完了です。
Node-REDのユーザー名とパスワードは初期値のままは避けてしっかり設定しましょう。

## 2. フロー構成

![Screen Shot 2021-07-29 at 20.18.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/0226e7dd-62bc-8dd7-8eac-a5da33641e60.png)

先頭（左端）は`http in`ノード、末尾（右端）は、インストールした `ReplyMessage`ノードです。

### 1つめ：`http in`

![Screen Shot 2021-07-29 at 20.24.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/cf41ebde-6722-d6f9-a0eb-bb8f77483f02.png)

LINEサーバーからのWebhookを受け付けるノードです。

### 2つめ：`function`

![Screen Shot 2021-07-29 at 20.29.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/57ceb9af-af09-eeab-fe5b-37db5766c48a.png)

スクレイピングを行う次以降のノードで`msg.payload`に入っているLINEメッセージが失われてしまうので、一時的に`msg.line`に退避させています。ここは`change`ノードでも同等のことができます。

### 3つめ：`http request`

![Screen Shot 2021-07-29 at 20.31.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/d60388cf-89c3-aa16-bb2f-88720b017d6f.png)

[WAON公式の「使えるお店を探す」](https://www.waon.net/shop/)ページをただただ取得しているだけです。このノードからは取得されたHTMLがそのまま`msg.payload`に入って流れてゆきます。

### 4つめ：`html`

![Screen Shot 2021-07-29 at 20.46.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/2f0f9d2f-40a2-e99d-c92f-c789284afd9d.png)

1. **p.useShopInner_boxText** をセレクタとして
2. 得られた要素の内側のテキストだけを
3. 得られた数だけ配列にして1つのメッセージにして
4. `msg.payload`に入れて送出

ということをやっています。

![Screen Shot 2021-07-29 at 20.49.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/0875d0b8-ada9-4f7f-6f23-df45c845b031.png)

実際に店舗名が入っているのがこの箇所だったので、この該当セレクタを全て抜き出して配列化しています。
このあたりは以下の記事を参考にしました。

https://qiita.com/n0bisuke/items/bd5329296fc188199993

### 5つめ：`function`

`debug`は省略します。
みんな大好き`function`ノードです。

![Screen Shot 2021-07-29 at 20.57.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/5ba5515d-052b-cd74-cfec-f12036ea1f92.png)

LINEから来たメッセージは`msg.line`に入っており、メッセージ本文が格納されているのは`msg.line.events[0].message.text`と、なんともややこしいところに入っています。ここに入っている文字列は「調べたい店舗名」を想定しているため、この文字列が先ほどの店舗配列が入っている`msg.payload`の中に存在するかを確認します。

そのあと、存在する・しないを表現する「返信メッセージ」を作り、`msg.payload.events[0].message.text`にそのメッセージを入れて送り出します。

### 6つめ：`ReplyMessage`

![Screen Shot 2021-07-29 at 21.03.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/9c3e3ede-6200-f7fa-9820-303258e30619.png)

[LINE Developers](https://developers.line.biz/console)でMessaging APIチャネルを作成し、「チャネルアクセストークン」と「チャネルシークレット」をそれぞれ取得してきてここに入れます。

## 3. LINE DevelopersでWebhook URLを設定

![Screen_Shot_2021-07-29_at_21_06_20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/0b2ae4af-fc4a-01b1-de20-5c413f78b1e9.png)

HerokuのURLに`/webhook`というパスを追加したものがエンドポイントとなります。
LINEとの連携において必要な情報はこのWebhook URLのほかにはシークレットとアクセストークンであり、あとは[LINE公式アカウントマネージャー](https://manager.line.biz/)で、「応答設定」が以下のようになっているか確認するだけです。

![Screen Shot 2021-07-29 at 21.12.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116011/747048c0-2f5a-dbd3-b420-3d93140b29d2.png)

## 4. インポート用のフロー

この記事で作ったNode-REDのフローを試していただけるJSONを以下に貼っておきましたのでご利用ください。
LINE関係のノードはあらかじめインストールしておいてね！

```json
[{"id":"e8b02747.23f5d8","type":"tab","label":"Flow 3","disabled":false,"info":""},{"id":"154b2525.fec9db","type":"http in","z":"e8b02747.23f5d8","name":"LINEサーバーからアクセス","url":"/webhook","method":"post","upload":false,"swaggerDoc":"","x":130,"y":40,"wires":[["bba70408.46ab88"]]},{"id":"bba70408.46ab88","type":"function","z":"e8b02747.23f5d8","name":"検索ワードを保持","func":"// LINEサーバーからの情報を「msg.line」に移動させる\nmsg.line = msg.payload;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":40,"wires":[["a8a136b3.2572f8"]]},{"id":"a8a136b3.2572f8","type":"http request","z":"e8b02747.23f5d8","name":"www.waon.net/shop/ へアクセス","method":"GET","ret":"txt","paytoqs":"ignore","url":"https://www.waon.net/shop/","tls":"","persist":false,"proxy":"","authType":"","x":640,"y":40,"wires":[["94932a54.7ab588"]]},{"id":"11d8c7c5.9be6d8","type":"debug","z":"e8b02747.23f5d8","name":"返信メッセージの中身をdebug表示","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.events[0].message.text","targetType":"msg","statusVal":"","statusType":"auto","x":520,"y":300,"wires":[]},{"id":"94932a54.7ab588","type":"html","z":"e8b02747.23f5d8","name":"店舗名だけ抜き出して配列化","property":"payload","outproperty":"payload","tag":"p.useShopInner_boxText","ret":"text","as":"single","x":940,"y":40,"wires":[["379230a2.79ac6","4d01bc93.b27434"]]},{"id":"379230a2.79ac6","type":"function","z":"e8b02747.23f5d8","name":"検索ワードが店舗一覧の中にあるか？","func":"const searchShop = msg.line.events[0].message.text;\nconst result = msg.payload.findIndex((shopName) => shopName == searchShop);\nmsg.payload = msg.line;\nmsg.payload.events[0].message.text = '電子マネーWAONは「'+searchShop+'」で'+((result == -1)?'のご利用はできません。':'ご利用いただけます。');\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":210,"y":160,"wires":[["567abac0.ee3784","11d8c7c5.9be6d8"]]},{"id":"4d01bc93.b27434","type":"debug","z":"e8b02747.23f5d8","name":"店舗配列をdebug表示","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1220,"y":40,"wires":[]},{"id":"567abac0.ee3784","type":"ReplyMessage","z":"e8b02747.23f5d8","name":"LINEで返信","replyMessage":"","x":590,"y":160,"wires":[]}]
```