「三百万星パワー!」の技術的解説

作ったもの

三百万星パワー!

第8回シンデレラガール総選挙に向けて、本田未央への支援を盛り上げるため、本田未央P向けDiscordチャンネル「miotalk」で立ち上がった企画で、私が制作を引き受けました。


f:id:maraigue:20190514223028p:plain


以下、サイトに書いた解説です。

パワーについて

このサイトにおける集計の更新は、総選挙の投票期間終了直後(5月14日 19:03*1)に停止しましたので、技術的な詳細を公開したいと思います。(ソースコードの公開はしない予定ですが、気が向いたらするかもしれません)

概要

  • Twitterの検索APIで、所望のツイートを集める
  • 手元で保存していない新規の発言であったり、保存している発言でもRT/いいね数が増えていたら、それに応じてパワーを加算する
  • その結果を読み込み、HTMLに整形して出力(毎度結果のデータを読み込むわけではない。そのため、更新間隔=3分単位でのみ内容が更新される)

詳細

基本的なプラットフォーム

  • Rubyで実装しています。Twitter検索を利用するにあたり、以前に自分でtwbot2.rbというTwitterボット向けライブラリを作っていたので、それを活用したかったためです。
  • またページの出力(HTMLの生成)はERBを使っています。HTMLの大枠のみ直接書き、集めたデータを反映する箇所のみRubyのコードで書く、という形になります。
  • 閲覧者からサーバに何かリクエストを出す、という処理が必要ないため、CGIRailsなどは使っていません。単純に、「Twitter検索→ページの出力」という処理を3分間隔で実行しています。

Twitterの検索APIで、所望のツイートを集める

認証トークン付きで、Twitterの検索APIの仕様に従いURLにアクセスします。twbot2.rbでは以下のような記述をします。

TwBot.create("config.yml", "error.log").cui_menu do
  # (中略)
  search_result_JSON = auth_http.get("https://api.twitter.com/1.1/search/tweets.json?result_type=recent&count=100&q=[URLエンコードした検索単語]").body
  # (中略)
end

ここで、検索していたキーワードとして二つだけ公開しておりましたが、実際には以下の四つで検索をしていました。

Twitter APIを通じての検索の場合、RTした結果についても検索にひっかかってしまうため、明示的に「exclude:retweets」を付けないとなりませんでした。またOR @qzwxecrvtbynumiopという表記は、名前欄だけに該当する単語がある場合に検索から外すための裏ワザです(IDは存在しないアカウントであれば何であってもOK)。

取得は3分単位としましたが、もっと高頻度にしてもよかったかなーと思っています。Twitter API上はまだまだ増やせたので(15分あたり180回が上限。今回の場合は毎回検索APIを4回使っていたため、実際には15分あたり45回まで)。

パワーを加算する

ここが実装をどうしようか頭を使った箇所でした。というのは、

  • 重複カウントを避けたい
  • そうしようとすると、単純に考えれば、検索でひっかかった全ツイートを残しておけばよい
  • しかしそれはデータファイルが肥大する問題がある

という理由からでした。

結論としては、検索でひっかかった全ツイートを残すのではなく、以下の条件で残すことにしました。これにより、重複は回避できました。

  • 直近のツイートをいくつ残すかは、事前に決定しておく(今回は1000とした)
  • もし、「保存しているツイートのうち最も古いもの」よりも古いツイートが検索で引っかかった場合は、カウントはしない

なお、この方法でも解決できていない問題として、「検索に引っかからない(Twitter APIでの検索では100件までしか検索できないため、それを超えた場合)過去のツイートがRT・いいねされてもカウントは増えない」というものがありました。これは仕方なく「カウントしない」ということにしましたが、一応対策として「定期的にそのツイートを単独でTwitter APIにより取得する」という方法も考えられました*2

掲載ツイートの決定

サイト内で

流れているのは該当するツイートの一部です。なお掲載基準は勝手ながら非公開です。

と注意書きしていましたのは、それを知られるとイタズラされる恐れがあるということで非公開にしていたのでした。状況次第ではNGワードの導入なども可能性としては検討していましたが、幸いにも変なツイートはそんなにありませんでした。ただ「総選挙投票をしたツイートをモバマス/デレステ内からデフォルトの文言で投稿しただけ」というものについては、掲載ツイートとしては採用しないという扱いにしました。

なお掲載ツイートの優先順位については、以下の計算を行って決定していました。

そのツイートのパワー × (減衰率経過日数)

  • 「そのツイートのパワー」:1 + いいね数 + RT数(実際にはこの30倍がパワーとして加算される)
  • 「減衰率」:新しいツイートほど採択されやすくするための比率。例えば減衰率が0.5の場合、1日経過で0.5倍、2日経過で0.5×0.5=0.25倍、…という具合に優先順位を下げられる*3。今回は減衰率は0.3~0.5の間でランダムとした。

終わりに

Twitter APIを使うだけでなく、さらにそれに加えて技術的・サービス的にいろいろと考えることが多かったため、今回このようにやったことをまとめることにしました。ツイートを収集して~したい、という方にはもしかしたら参考になるかも?という意味もあり。

以前からTwitterボットなどは作っていて、Twitter APIを利用した経験があったこと、また「本田未央のためなら!」という気持ちもあり、このようにサイトを制作していました。このサイトが本田未央の盛り上げに役立っていたなら幸いです。

*1:投票終了は18時59分なのだが、Twitterへの投稿がワンテンポ遅れることも考えられたため、若干の余裕を持たせました。

*2:API制限や通信コストの面から、一度に複数のツイートの情報をID指定で取得できるstatuses/lookupを使うのがよいです。

*3:なお減衰率は日数のみを見ているわけではなく、秒数まで見ています。例えば減衰率0.5で「1.5日(1日と12時間)経過したツイート」の場合、0.51.5≒0.35倍となります。またこれをまともに計算すると指数関数(急速に値が変化する)ゆえに値の誤差の問題が生じやすくなるため、内部的には対数を取った結果、すなわち「log(そのツイートのパワー) + 経過日数×log(減衰率)」で計算しています。