SlackでKibelaのURLを中身が分かるように展開してみる

4 min read読了の目安(約4000字

はじめに

この記事はSlackアドベントカレンダーの14日目の記事です。
Kibela記事のURLに対してUnfurlを行うSlackアプリですが、一工夫して内容を把握しやすくしてみました。

現時点での結果

Kibela記事のURLをSlackに貼ると以下のように展開されます。まあ、見て分かる方も多いと思いますが、青空文庫の「銀河鉄道の夜」をテキストにしたものを記事本文にしました。

なぜGoogle Meetの背景ぼかしが最強なのかのMarkdownをコピってきた記事だとこんな感じ。

ソースコードはkounoike/slack-kibela-appにて公開しています。インストール手順などは現時点で未記載です。ごめんなさい。

本格的に作るまでの流れ

Kibela APIのcontentSummaryHtmlの場合

Kibelaのgraphql APIでは記事(note)について contentSummaryHtml というフィールドでサマリが得られます。しかし、実際にはサマリと言っても単に先頭からある程度の分量を取ってきているだけにすぎません。記事の内容を「要約」しているとは言えない場合が多いです。

例1: 銀河鉄道の夜

 ジョバンニは勢いよく立ちあがりましたが、立ってみるともうはっきりとそれを答えることができないのでした。ザネリが前の席からふりかえって、ジョバンニを見てくすっとわらいました。ジョバンニはもうどぎまぎしてまっ赤になってしまいました。先生がまた言いました。
「大きな望遠鏡で銀河をよっく調べると銀河はだいたい何でしょう...

例2: Meet 最強記事のコピー

はじめに
最近ついに、Google Meetに背景ぼかし機能が利用可能になりましたよね。日本語だとインプレスのケータイ Watchの記事などで紹介されてます。確か2020年9月末前後で順次リリースされていたと思います。 このときは「背景ぼかし...

最初はこの contentSummaryHtml を使ってURL展開していたのですが、時々妙に改行が多く縦長になってしまう記事が出たりするなど、今一つ利便性が上がらない状態でした。

WordCloudの導入

ある日、全然別のことを調べてる過程で自然言語処理のことを調べることになり、「そういえばWordCloudってあったなー」って見ていたりしました。そのときに「WordCloudなら要約っぽくなるんじゃないか?」と閃きました。当初はTypeScriptでBoltを使っていたので、kuromojiやd3-cloudなどを使っていました。BoltのサンプルをベースにしてHerokuのFree dynoで動かしていました。

しかし、軽く動かすくらいは出来たもののどうも安定しないようでした。調べてみると単純にメモリ使用量がfree dynoの制限を超えていました。ならば、と思ってPythonに移植してみたもののやはりメモリ制限の壁は厳しかったです。時々アクセスがあってURL展開するだけのものに月数ドルもかけるのも微妙にもったいないです。同じ観点でAWSで各種サービスで継続的に動かすのはNG。Lambdaであれば使用量もそれほどではないので良いかと思うものの、各種ライブラリやフォント、形態素解析用の辞書諸々を含めるとZipの展開後サイズ250MBの壁が立ちはだかりました。

Lambda container image support

そんな中、AWSの2020年のre:InventイベントでAWS Lambda の新機能 – コンテナイメージのサポートが発表されました。コレです!コレならイメージが大きくなってもLambdaで動かすことができます。

さて、道具は揃いました。後は作るだけです。

構造

リポジトリのトップで3つのディレクトリがあります。それぞれに対応して3つの部分に分かれています。

cdk

環境構築するのにAWS CDKを使っています。このディレクトリにCDK関連のソースが入っています。

bolt-app

Slackサーバとの対話を行うBolt for Pythonのアプリがあります。Slack Appなので3秒以内に応答しなければならない中で工夫がしてあるのは1日目の記事の通りです。ゆっくり実行してよいところで後述のwordcloud-appを呼び出してWordCloud画像生成などを処理させます。1つにまとめるとコンテナイメージの肥大化やライブラリ初期化などの問題で3秒ルールを守りにくくなるため、こちらのbolt-appは普通のZipデプロイするLambdaにして、依存関係を最小限にしてあります。

wordcloud-app

WordCloudを生成する本体です。単にLambdaで作っても良かったのですが、今回はちょっと方向性を変えてStep Functionsを活用してみることにします。Step Functionsを使うことで条件分岐(画像が生成済みかなどのチェック)をStep Functions側に回し、Lambdaの起動自体を抑えたり、一定個数の並列度で並列ループを回したりすることが簡単に実現できました。

WordCloud生成の仕組み

ワードクラウドを単純に作る場合、以下のような流れになります。

記事URL -(Kibela API)-> HTML -(クレンジング)-> テキスト -(形態素解析)-> 分かち書き -(WordCloudライブラリ)-> WordCloud画像

しかし、この流れでは一つの記事の中での単語出現頻度(TFと言うそうです)しか見ていません。あるKibelaチームであれば、共通で頻出する単語(例えばWeb開発だったらHTMLとかブラウザとか)があると思います。記事をまたいで頻出する単語は重要度が低く、他の文書で出てこない単語が頻出する場合の重要度を上げる仕組みをTF-IDFと呼ぶそうです。

ステップの構造

最初に全記事を形態素解析して画像を生成しておくステップがこちら。複数を束ねているところが並列ループで回るようになっています。

記事が作成/更新されたときのステップがこちら。とりあえず決め打ちで全部更新することにしています。

URLがリンクとして貼られたときのステップがこちら。TFをため込んだタブ区切りテキストファイルやワードクラウド画像と記事の最終更新時刻を比較して不要なステップをスキップしています。

おわりに

CDKの中ではコンテナイメージをディレクトリ指定するだけで生成できていたり、SSMのパラメータストアを使って必要な設定をしていたりすることなど解説することはまだまだありますが、とりあえずこの辺で…

明日(もう今日になってますが)はk-akieさんで「GCP: Google Cloud Functions で Slack アプリを試してみた」とのことです。今回私はAWSべったりでやってみましたが、GCPだとどうなのでしょうね、気になる記事です。