Webページからスクレイピングした単語をGoogleHomeに話してもらった

この記事は「スマートスピーカー Advent Calendar 2017」の11日目の記事になります。

プログラミング歴1年ちょっとの大学二年生が流行に乗ってアドカレ初投稿してみました。
ゆっくりしていってね(´・ω・`)

# LeagueCallというアプリを作ってみた

突然ですが、私はいつも[League of Legends](https://jp.leagueoflegends.com/ja/)という対戦ゲームを遊んでます。
5vs5のチームゲームなんですが、キャラクターごとに有利不利があったりします。
今回はこの有利不利を[OP.GG](http://jp.op.gg/)という戦績サイトからスクレイピングしてGoogleHomeに伝えてもらう
「LeagueCall」というアプリを作りました。
これによって逐一WebでOP.GGを開かなくてもGoogleHomeに聞くだけで、
キャラクター選択時に相手の使うキャラクターに対して有利なキャラクターをすぐに選べることを目指します。

ちゃんと技術的なことも頑張って載せているのでご覧ください！

# キーワード

- Google Home
- Dialogflow
- Firebase (Cloud Functions)
- スクレイピング
- Node.js
- cheerio-https

# 構成

![LeagueCall 構成図.jpg](https://qiita-image-store.s3.amazonaws.com/0/135833/acd987d3-b688-506e-6a42-ddc0c21bc9b9.jpeg)


# どうやってスクレイピングしたのか？

ここが一番気になるところかと思います。
Dialogflowを通してFirebaseで色々と細かいことを行うにはNode.jsで処理を書きます。
じゃあNode.jsでスクレイピングの処理を書けるすごいやつを探そうぜ！ということで
[cheerio-httpcli](https://www.npmjs.com/package/cheerio-httpcli)というモジュールを使いました。

これを使えば、まるでクライアントサイドでjQueryを使っているかのようにWebページのhtmlを
操作することができました。 (※筆者はド初心者なのでこれを作るまでjavascriptにすら触ったことがなかったです)

ぶっちゃけGoogleスプレッドシートとかを通すほうがメジャーだと思いますが、
Firebaseを通してスクレイピングできないのかなーという発想でやった次第です。

# Firebaseを通してスクレイピングなんてあんまり聞かないよ？

それもそうです。なぜならこのアプリを動かすためにはFirebaseの無料プランでは動かせないからです。

[Firebaseのプラン項目](https://firebase.google.com/pricing/?hl=ja)を見てみましょう。
左から、無料プラン・月額制プラン・従量課金制プランです。

![2017-12-09.png](https://qiita-image-store.s3.amazonaws.com/0/135833/765f1326-2309-16d3-1bc0-895e2c6dc866.png)

DialogflowのFullfillmentでFirebaseをデプロイに使うと、Cloud Functionsというサービスを通ります。
注目するべきところは「アウトバウンド ネットワーキング」という項目です。

有料プランでは~G/月となっていますが、無料プランではGoogle関係のサービスにしか繋げられません。
このことを知らずに、動くはずのソースコードとにらめっこして1週間以上時間を費やしたことは言うまでもありません。

今回のアプリでは従量課金制を選択して無事に動作確認をしました。

# それっぽく動きそうなコードを書いたら変数の中身がundefinedになった

筆者はAndroidアプリとかArduinoとかをちょこっとかじっているだけなので、
サーバーサイド言語なんてものはわからないまま開発に挑みました。

そこで一番詰まったことはJavascriptにおいて知っておかなければいけないであろうPromiseについてです。

Promiseって何かというのは下の参考サイトにリンクを貼っておきますが、(←投げた)
非同期処理を操作するためのものと覚えています。

# なぜ非同期処理が必要なのか？

このアプリの手順は以下のようになっています。

1. GoogleHomeにキャラクター名を話しかける
2. firebase上でキャラクター名を取得する。
3. 受け取ったキャラクター名のOP.GGページを指定してスクレイピング開始
4. 有利なキャラクターと不利なキャラクターの名前を取得
5. GoogleHomeに結果を話してもらう

1~3まではスムーズに処理が動きますが、4の部分でWebページからデータを取得するためどうしても時間がかかります。
非同期処理を入れずに実行してしまうと、4の部分が終わる前に5に移行してしまい、結果として取得データを格納する
変数の中身がundefinedになってしまいました。

こういった少し処理が必要なことにはPromiseを使って非同期処理を実装する必要があるという知見を得ました。
cheerio-httpsの使い方は公式ドキュメントや他の方々のQiitaなどの記事にわかりやすくまとまっています。

# 実演

GoogleHome実機でやってみました

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">Qiitaアドカレに載せるデモ映像 LeagueCall demo <a href="https://t.co/qLxWCfTDD2">https://t.co/qLxWCfTDD2</a> <a href="https://twitter.com/YouTube?ref_src=twsrc%5Etfw">@YouTube</a>さんから</p>&mdash; Dai (@daivr7774) <a href="https://twitter.com/daivr7774/status/939816311151984640?ref_src=twsrc%5Etfw">December 10, 2017</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


スマホでもやってみました

<img src="https://media.discordapp.net/attachments/294842484033454081/389345427088211968/Screenshot_20171210-181800.png?width=380&height=676" width=50%>


スクレイピング先のOP.GGサイトです
右側のCounter Championsの欄から取得しています
<img src="https://qiita-image-store.s3.amazonaws.com/0/135833/64288348-6b37-1c09-36ea-bd79bb91748d.png" width=80%>

ソースコードをまとめてます
[Github/LeagueCall](https://github.com/Dai1678/LeagueCall)

# 参考

- [【動画あり】Google AssistantでスマートロックAkerunを音声操作する](http://akerun.hateblo.jp/entry/2016/12/22/061702)
- [cheerio-httpcli で簡単スクレイピングして、Google検索一覧取得](https://qiita.com/xingyanhuan/items/32a29974f1edfb0ddf83)
- [Promiseについて0から勉強してみた](https://qiita.com/toshihirock/items/e49b66f8685a8510bd76)

# 次にやること

ぶっちゃけ、WebでOP.GGを開かずにならなくなったかと言われればそんなことはなく...
なぜかGoogleHomeがアプリ名をうまく認識してくれなくて起動できないことが多いです。

Firebaseの従量課金制を使って動かしていると書きましたが、実際に通信しているデータ量がわからないので
いつ支払いが発生するのか怖いです... 確認方法を教えて頂けると嬉しいです。

今後はこのアプリの機能追加も考えていきながら、忘れ物防止を助けてくれるアプリなども作ってみようかなと思います。