読者です 読者をやめる 読者になる 読者になる

プログラマでありたい

おっさんになっても、プログラマでありつづけたい

ServerLessで、Amazonのほしい物リストから安売り情報を通知するBotを作ったよ

 Serverless Advent Calendar 2016の11日目です。ちょっと趣向を変えて、Serverlessでアプリを構築する場合、こんな感じになったよというのを紹介しようと思います。

Amazonのほしい物リストから商品情報を抜き出して、安売りしていたら通知してくれるBot



 最近の読書はもっぱらKindleさんです。いつでもどこでも、そして保管の場所を取らないというのは素晴らしいですね。このKindleですが、定期/不定期にセールをやっています。問題は頻度と対象が多すぎて、欲しい本を見つけられないという問題です。Kindleのセール情報をまとめているサイトなども多数ありますが、そちらを追うのも大変ですよね。
 そこで、自分が欲しい本だけのセール情報を取得するという方法を考えてみましょう。解決策は簡単で、気になった本は自分の欲しいものリストに追加していき、そこの本が安くなったら通知してくれるBotを作ればよいのです。

Botのアーキテクチャと処理の流れ



 Botは、DynamoDBとLambdaで構成されています。つなぎの部分として、SQSやCloudWatch Eventsを利用しています。
f:id:dkfj:20161212015001p:plain

処理の流れとしては、次のような感じです。

  1. bot宛(@WishlistSales)宛に欲しい物リスト(公開)のURLをつぶやく
  2. CloudWatch Eventsで定期的に起動しているLambdaが、Twitterのmentions_timelineを取得しDynamoDBに登録
  3. DynamoDB StreamsでLambdaを起動し、欲しい物リストのURLから全ページ取得しSQSに登録。※欲しい物リストは、2,500件(250ページ)まで登録出来るので、改ページとアイテム取得を分離するため
  4. SQSのキューを元に登録されているASIN(ISBN)を取得し、DynamoDBに保存
  5. 毎日LambdaからProduct Advertising API(元祖AWS)を利用し、書誌情報を取得し保存
  6. 前日に較べて安くなっていたらTwitterで通知

作ってみて気づいた点



 上記のアプリを、Node.jsで作ってみました。今までもAPI Gateway + Lambdaという組み合わせで使うことが多かったのですが、今回はバッチアプリとしてのNode.jsです。そしたらハマりました。Node.jsは基本的に、非同期処理です。それでバッチを作ると、単純に上から下の流れだと上手く動きません。最初は無理やり同期的に作ってみたのですが、やっぱりしっくりきません。素直にCallbackで処理を書くと、待ち時間に並列で動かすことが出来て成る程という感じでした。ノンブロッキングI/Oの深淵を少し垣間見れたような気がします。やっぱりこの考え方は、身につけないと駄目ですね。
 ちなみにServerLessという点では、特段困る点は少なかったです。強いて挙げれば、lambdaのタイムアウトがあるので1度のバッチサイズを小さくする設計ですね。これは結果的には、処理分割の上で良い影響が出たと思います。lambdaの処理時間は1分⇒5分と延びましたが、もし今後延びたとしても、1つの処理時間は1分以内に保てるような設計を心がけた方が良いと思います。それ以外で言うと、Lambdaに環境変数を保持できるようになったのは大きいです。これだけでも結構開発しやすくなっています。

SQSとStep Functions



 Step Functionsが出たということで、早速使ってみようと検討してみました。しかし、今回の処理でいうとSQSとCloudWatch Eventsで充分でした。理由としては、それぞれの処理の実行のタイミングがバラバラということです。イベント駆動のものもあれば、定期的に実行するというものもあります。そんな訳で、まだStep Fuctionsを使えていません。どっかで使ってみたいですね。
 SQSについては、個人的にはシンプルで非常に使い勝手が良いです。要望としては、SQS⇒Lambdaの直接的なトリガーを作ってくれれば、もっと便利になるのではと思います。まぁ冷静に考えるとLambda同士のつなぎの部分は、Step Functionsでも良いかもしれません。そのうち、再検討します。

生産性



 上記の処理は、実はまだ全部できていません。日々の価格変動と変動を検知して呟く部分が作りかけです。前半分の処理は、新幹線の往復の時間だけでほぼ出来たので、わりと効率的に作れているかなと思います。環境構築の時間がほぼ不要で、処理に専念できるというのは強いですね。記述量も少なく、全部出来たとしてもステップ数は1,000いかないですね。たぶん500くらいなのではという感じです。

感想



 年内中には、ちゃんと公開しようと思います。自分だったらこういう構成にするというのがあれば、コメント頂ければありがたいです。登録しといたら、そのうち安売り情報が届くはずです。
 この程度のアプリであれば、一人で作れて運用負荷も殆どありません。ServerLessというのは目的ではなく、自分がやりたいことを楽に出来る手段だと捉えて、是非いろいろ遊んでみてください。
 最後にちょっとだけ宣伝すると、最近執筆したデータを集める技術に欲しい物リストを取得するウニョウニョも書いています。興味があれば、読んでみてください。

チップス



 あまり技術的な要素は無かったのですが、幾つかチップスも紹介しておきます。

Twitter関係

 Twitterの呟き取得の際、デフォルトでは短縮化されたURLしか取得できません。引数でinclude_entitiesの項目をtrueにしておくと、entities.urls[n].expanded_urlという場所に配置されます。1ツィートで複数あれば、複数項目でてきます。あと、既読を無視する方法が解らなかったので、最終取得Tweetの日時をDynamoDBに保存するようにしています。

  var params = {include_entities: 'true'};
  client.get('statuses/mentions_timeline', params, function(error, tweets, response) {
    if (!error) {
      tweets.forEach(function(tweet) {
          var curDate = new Date(tweet.created_at);
          if (lastDate >= curDate) {
            console.log("new tweet is not exists");
          } else {
            putUrl(tweet.entities.urls[0].expanded_url, tweet.user.screen_name, curDate);
          }
      });
    }
  });
DynamoDB関係

 Node.jsに関しては新旧複数のライブラリが存在します。公式のBluePrintの、dynamo.putItem/getItemとやっているようなのは古い書き方をしています。 公式のチュートリアルを最初に読みましょう
http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/gettingstartedguide/GettingStarted.NodeJs.03.html#GettingStarted.NodeJs.03.01


See Also:
cheerio-httpcliを使って、Amazonのほしい物リスト(Wish List)から商品コード(asin)を抜き出す

参照:
Serverless Advent Calendar 2016

データを集める技術 最速で作るスクレイピング&クローラー (Informatics&IDEA)

データを集める技術 最速で作るスクレイピング&クローラー (Informatics&IDEA)

Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例

Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例