Twitter相性診断アプリを制作した話


まえがき

自分は現在、開発集団「Weekend Engineer」に所属しておりまして、そこでの課題ということで小さなアプリを制作しましたので色々思い返しつつ紹介したいと思います。

製作開始当時、プログラミングに触り始めて約1ヶ月半。この制作でWebフレームワークにも初めて触りました。
そのため、おかしいところは正直めちゃくちゃあると思います!ご親切な方がいらっしゃいましたらぜひご指摘の方をお願いします

制作物

まず以下が実際に制作したものになります。

内容としては

  1. @抜きの公開アカウント2人のTwitterIDを入力
  2. 2人のツイートから性格を分析する
  3. グラフとパーセンテージで性格の一致度として結果を表示する

というTwitterログイン機能もなく大変にシンプルなアプリになっています。

サイトへのURLはこちらです!
現状公開アカウント同士での診断ができる状態です。ぜひやってみてください!

また、実際のコードは以下です。
主にこの記事では1番目のリポジトリのコードを参照しながら説明していきます。

技術概要

主要言語

  • Python 3.6.6
    おなじみPython!今回はほぼすべてをPythonで書いてます。

API

  • Twitter API
  • IBM Watson Personality Insights
    日本語で書かれた文章を送ると、様々な角度から性格に関してのいろんな数字を出力してくれます

ライブラリ / ツール

  • pipenv
    Herokuに対応した仮想環境を構築するために導入
  • Heroku CLI
    Heroku用CLIツール
  • Flask
    PythonベースのマイクロWebフレームワーク。
    HelloWorldまでがめっちゃ速いけど公式ドキュメントが初心者には鬼
  • Jinja2
    Flask備え付けのPythonテンプレートエンジン。HTMLでPythonのデータを表示・処理できる
  • sixohsix/twitter
    PythonでTwitter APIに対して認証/操作を行う際の作業を楽にしてくれるライブラリ
  • watson-developer-cloud/python-sdk
    IBM公式のPython用Watson APIユーティリティライブラリ
  • Chart.js
    結果のグラフ化で使用。JavaScriptでグラフを描画できるライブラリ
  • jQuery
    ローディング表示に使用
  • Bootstrap
    全体的なスタイリングでそのまんま使用。CSS/JSフレームワーク

デプロイ環境

  • Heroku
    みんな大好きPaaSプラットフォーム

記事で説明している主なファイル

  • main.py
    基本処理とWebページレンダリング処理の全部入り
  • config.py
    APIにアクセスするためのトークン情報保管場所
  • templates/
    • index.html
      最初に表示されるページ
    • result.html
      結果を表示するページ

制作過程

ここからは実際のプログラムの流れ(関数名)と、詰まった記憶のあるポイントをつらつら書いていこうと思います。
自業自得ですが、期間が空いて記憶が薄れているので、あやふやな表現になるかもしれませんがご了承ください。

ツイートを取得する(main.py - get_user_tweets)

いわゆる「APIを叩く」というやつですね。
JSONでいろいろとやり取りをしないといけないところがtwitterのライブラリのおかげでかなり楽になりました。

以下は処理部分の要約です。config.pyでは各項目でゲッターを用意してますので、それで情報を呼び出しています。

from twitter import Twitter, OAuth, api 
import config

# configからトークン情報を取得
CK = config.get_consumer_key()
CS = config.get_consumer_secret()
AT = config.get_access_token()
AS = config.get_access_secret()
# OAuth認証
twitter = Twitter(auth=OAuth(AT, AS, CK, CS))

# オプションを与えてツイートを取得して a_timeline に放り込む
a_timeline = twitter.statuses.user_timeline(
    screen_name=[ユーザーID],
    count=200,
    include_rts='false',
    tweet_mode='extended'
)

これだけでscreen_nameで指定したユーザーの「RT抜き」で「省略なし」のツイート情報が200件取れます。
statuses.user_timelineの部分がTwitterのREST APIのアクセスポイントURLに対応してまして、ここを変えることで他の情報も取れるみたいです。
参考: Twitter開発者ドキュメント日本語訳 REST API

ただ、tweet_mode=extendedに至るまでの道のりがやっかいでした。
詳しくは以下の記事に書いてありますが、画像つきツイートだと画像のURLに押されてツイート末尾が省略されてしまう問題があって、この問い合わせ方法をtwitterライブラリを使用して再現するのに少し手こずりました。
最終的には上記のオプションをくっつけるだけで良かったみたいです。
参考: 140文字以上のツイートを取得する

取得したツイートの整形(main.py - get_shaped_tweets)

ツイートを取得したらば、コレをもってWatsonにそのまま突っ込めばいいかといえば当然違います。
雑多なツイートの中には

  • 外部リンクURL
  • リプライ先のユーザーID
  • ハッシュタグ

やら余計な情報が入っていたり、

  • >, <などの実体参照
  • 改行コードの\n

が含まれていたりするので、これらをいい感じに整形しないといけません。
とはいえやることといえば、上記の項ごとに正規表現re文字列操作replaceでガリガリそれっぽいものを取り除いたり入れ替えたりしてるだけですね。

整形したツイートをJSONに変換(main.py - tweets_conv_json)

上記でいい感じにしたツイートをWatson APIが読めるような形式のJSONに変換します。
とはいえここも決められた構造になるようにネストされた辞書型を作成して放り込んでるだけです。
具体的には以下のような構造のJSONにします。

{
  "contentItem": [
    {
      "content": "ツイートされた文章",
      "contenttype": "text/plain",
      "language": "ja"
    },
    {
      "content": "ツイートされた文章",
      "contenttype": "text/plain",
      "language": "ja"
    },
    ...
  ]
}

ここのあたりに関しては以下のIBM公式ドキュメントでもまさにTwitterのツイートを解析する例として書いてありましたので拝借させていただきました。
参考: IBM Cloud資料 / Personality Insights / プロファイルの要求

JSONに変換したツイート群をWatson APIに送る(main.py - get_insights_analytics)

こちらもWatson Developer Cloudが提供してるpython-sdkのおかげでアクセスが楽になってます。
以下、処理部分を要約

from watson_developer_cloud import PersonalityInsightsV3

# configからトークン情報を取得
UN = config.get_username()
PS = config.get_password()
personality_insights = PersonalityInsightsV3(version='2017-10-13', username=UN, password=PS)

def get_insights_analytics(user_name):
    with open([ツイート群へのPATH], 'r', encoding='utf-8_sig') as tweets_json:
        profile = personality_insights.profile(
            tweets_json.read(),
            # JSONを処理するモードに設定
            content_type='application/json',
        )

これで解析結果として様々な方面から性格を分析した結果がprofileの中に格納されます。

分析結果から必要なものを抽出(main.py - get_big5)

返ってきたJSONから必要なものを取り出してます。
具体的には分析結果から人間の性格の上で重要な5つの特徴「BIG5」に関連する項目の「percentile」の値を抽出しています。
ではまずこの抽出の基準について説明します。

詳しくは下記の資料類に解説を譲りますが、ざっくり説明しますと
「BIG5」とは

  • 開放性(Openness)
    新たな美的・文化的・知的な経験に開放的か
  • 真面目さ(Conscientiousness)
    計画性・責任感・勤勉さの度合い
  • 外向性(Extraversion)
    関心がどれだけ外の人・モノに向けられているか
  • 協調性(Agreebleness)
    他人と協調的に行動する傾向の度合い
  • 精神安定性(Emotional lange)
    感情的反応が一般に予測のできるものであるか、その反応に整合性があるか

の上記5つの要素のことをいい、このモデルを軸にPersonality Insights(以下P.I.)は性格を分析しています。
つまり、この5大要素だけわかれば、ざっくりと人物の傾向が読めるというわけです。

かつ、このそれぞれの「percentile」という値はどのようなものかというと、
実際に入力されたテキストを分析した結果のスコアを、P.I.内で標本とされているデータ集団と比較し、どの程度の順位にいるかを百分位数で表した数値
になります(間違ってたらすみません)

以上の情報を踏まえて、この処理部分では「BIG5」のそれぞれの要素の「percentile」に該当する数値だけを抜き出しています。

参考:

抽出したものを表示する数値に変換する(main.py)

これは複数の関数に処理を分散してまして、どれも簡単な処理なのでまとめて紹介します。

  • get_big5_diff
    2人のユーザーのBIG5の値を引き算して差を求める
  • get_diff_percent
    get_big5_diffで出た差の百分位数を100倍してroundして整数のパーセンテージにする
  • get_diff_avg
    それぞれのパーセンテージを総合した平均値を出す

正直、自分は数学的素養がないのでここらへんは超テキトーです。せっかくPython使ってるのに。
正確な数値を出す方法とかあればコメントで教えてください…そもそもググるための数学的手法の言葉もわからないので。

表示する(main.py, index.html, result.html)

ここまできたらあとはFlaskを使用していい感じにして表示するだけですね。
先述のとおり、以下のツールをそれぞれ

  • Chart.js
    レーダーチャートで結果を表示
  • Bootstrap
    全体的にボタンやカードを整形
  • jQuery
    ローディング表示
  • Jinja2
    Pythonで処理された諸々の結果をHTMLに反映

これらの用途で使用しております。

ページのルーティング処理もシンプルなものです。
main.pyの後半部分がまるまるFlaskの表示処理部分になってまして

  • '/'index.htmlを表示
  • 「診断する」ボタンが押されたら、相性分析のプログラムを動かしたあとに'/result'result.htmlを表示
    • IDが一つでも未記入だったらエラーを表示してindex.htmlに戻す
    • ユーザーが見つからない/鍵アカウントだったらエラーを表示してindex.htmlに戻す

となっています。

ただ、意外にもここが最大の詰まりポイントでした。
最初はFlaskのページレンダリング処理は別々にする予定だったんですが、ファイルの分割が全然上手いこと行かなくて、そもそもフレームワークからFlaskで大丈夫なのかとか考えて1週間くらい無駄にした気がします。
ちなみに上記の通り、main.pyに全部のせしたまま次の開発サイクルに突入してしまったので現在も解決はしてません…。
個人的に最大の課題点です。

反省点

まず個人的に思った反省点としては

  • トークン情報込み込みのconfig.pyの存在
    のちにこのやり方での情報保管はセキュリティ的にマズいことを知って、フォーク先では環境変数を読む方法に改修済みです。
    最近GitHubでもこのやり方でトークンを保存しているタイプのプロジェクトは注意してくれるみたいですね。
  • BIG5関連処理を分割しすぎ?
    単純に、もっとまとめられましたね。
    コレもそうですが、適当に処理を関数化しまくったせいで粒度が揃ってない感じあります。
  • ファイル分割出来てない
    Flask、Pythonに関しての理解が甘かったです。
    最近解決法らしきものを見つけたので、時間があれば改修したい。

感想

APIの操作、Webフレームワーク、ファイル読み書きなどなど。だいたいやること全部がはじめての中で、1ヶ月で一応デプロイまでこぎつけれたのはほんといい経験になりました。
ただ、盛り込みたかった機能をFlask周りの事項でいろいろ見送ったので、フォーク先の個人開発で実験してアップデートをしばらく続けたいところです。とはいえ3回目のサイクルが迫ってるので掛かれるかどうかはわかりません。
もし開発して追加した機能でもあれば記事にしようかなと考えてます!
何にせよ、これが記念すべき「最初に作ったWebアプリ 」ということで完成したことにとりあえず満足しております。
ありがとうございました!