Yahoo天気をスクレイピングして予報をSlackへ通知する
はじめに
ちょっと前にYahoo天気の指数情報に洗濯や熱中症などに混じって、
ビールが追加されて話題になってました。
数ある天気予報の中でビールの指数を出しているところは見たことがなかったので、
Yahoo天気の予報をSlackに通知して毎朝確認しよう!と思ったのが今回の話。
APIを提供してくれていれば楽だったんだけど、
無さそうだったのでスクレイピングして無理やり取ってくることにする。
今回はtypescriptでサクッと作ったものをAWSに乗せて使うことにする。
TL;DR.
事前準備
コード側
今回使うライブラリなどをinstallしておく。
# 今回使うlibrart(httpリクエスト用、HTMLパーサ) $ npm install --save request jsdom # 型定義(好みで) $ npm install --save-dev @types/request @types/jsdom # その他 # TSとかeslintとかwebpackとかetc...
Slack側
ここからwebhook許可しとく
追加手順
プルダウンから通知を送りたいチャンネルを指定してAdd Incoming WebHooks integration
をクリック。
(もしかしたら日本語表記だと違うかも)
Setup Instructions
にあるWebhook URL
をコピーしておく。
これでSlack側の設定は完了
実装
実践ドメイン駆動設計をレイヤードアーキテクチャの辺りまで読んだので、
レイヤードアーキテクチャ的に実装していくことにする。
+ typescriptでDIをやるためにinversify
を入れる。
必須ではないので必要であれば追加でインストール(npm install --save inversify
)。
Interface
とりあえず叩かれる用。
受け取るパラメータも特にないので、ただapplication層を呼び出すだけ。
コード中の@injectable()
と@inject()
はinversify
用の設定。
@injectable()
はDIされる側の設定。interfaceの実装食らうに付けておく。
@inject()
はDIする側の設定。これで、interfaceを呼び出せば対応する実装クラスを引っ張ってこれる。
Application
Domain使ってるところ。
Domデータ取ってくる→欲しいデータに整形する→Slackへ通知の流れ。
Domain
ScrapingService
サイトにGet飛ばしてhtml取ってくるところ。
取ってきたデータをjsdom
を使ってパースして返す。
ConverterService
パースしたデータを使いやすいように整形するところ。
整形したり、必要なデータを取り出したりしている。
ゴリ押しに次ぐゴリ押しで実装してしまったので、 もうちょい綺麗にしたい。。。
InformSlackService
Slcakへ通知するところ。
送るデータを受け取って、Slackのwebhookで送れる形にしてあげる。
Infrastructure
HttpRequest
request
をラップしているだけ。
ここももう少し綺麗にしたいところ。
他のライブラリに変えても良い気がするし、promiseかまさなくても良いようにしたい。
inversify用設定
inversifyのGithubを参考にしながら設定を進める。
inversify.config.ts
interfaceと実装を紐づける設定。
DIコンテナ的な設定の認識。
const container = new Container(); container .bind<WeatherNewsService>(TYPES.WeatherNewsService) .to(WeatherNewsServiceImpl); container .bind<ConverterService>(TYPES.ConverterService) .to(ConverterServiceImpl); container .bind<InformSlackService>(TYPES.InformSlackService) .to(InformSlackServiceImpl); container.bind<ScrapingService>(TYPES.ScrapingService).to(ScrapingServiceImpl); container.bind<HttpRequest>(TYPES.HttpRequest).to(HttpRequestImpl); container.bind<WeatherNews>(TYPES.WeatherNews).to(WeatherNews); export { container };
inversify.type.ts
実行時に識別子が必要なので、宣言しておく。
公式にならってSymbol
を使っているが、クラスでも文字列でもOKだそう。
const TYPES = { WeatherNewsService: Symbol.for('WeatherNewsService'), ConverterService: Symbol.for('ConverterService'), InformSlackService: Symbol.for('InformSlackService'), ScrapingService: Symbol.for('ScrapingService'), HttpRequest: Symbol.for('HttpRequest'), WeatherNews: Symbol.for('WeatherNews') }; export { TYPES };
実行してみる
ここまでのコードを実行してみる。
実際に作ったものはAWSに乗せるように設定が変わっているけど、
概ね下記のようなwebpack.config
を用意すればビルドできるはず。
// output.pathに絶対パスを指定する必要があるため、pathモジュールを読み込んでおく const path = require('path'); const nodeExternals = require('webpack-node-externals'); const slsw = require('serverless-webpack'); module.exports = { mode: slsw.lib.webpack.isLocal ? 'development' : 'production', entry: slsw.lib.entries, devtool: 'source-map', target: 'node', module: { rules: [ { test: /\.ts$/, use: [ { loader: 'ts-loader' }, { loader: 'eslint-loader' } ], exclude: /node_modules/ } ] }, resolve: { extensions: ['.js', '.json', '.ts'], modules: ['node_modules'] }, output: { libraryTarget: 'commonjs', path: path.join(__dirname, '.webpack'), filename: '[name].js', }, externals: [nodeExternals()] };
下記のようなメッセージがSlackに通知されればOK。
まとめ
実装して動かしてみるところまでやった。
思ったより長くなりそうなので、今回はここまでにする。
次回serverless framework
を使ってAWSに上げるところまでやる。
それでは今回はこのへんで。