ツイートのデータをHTMLに反映しよう

app.pyにツイッターの処理部分を写そう

さきほどtwitter.pyで作成したファイルの一部を、app.pyに移行しましょう。

ファイルの中身は、以下のようになっていればOKです。

app.py

from flask import Flask, render_template, request
import os
from dotenv import load_dotenv

import tweepy
import pandas as pd

app = Flask(__name__)

load_dotenv()

CONSUMER_KEY = os.getenv("CONSUMER_KEY")
CONSUMER_SECRET = os.getenv("CONSUMER_SECRET")
ACCESS_TOKEN = os.getenv("ACCESS_TOKEN")
ACCESS_SECRET = os.getenv("ACCESS_SECRET")

auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET)

api = tweepy.API(auth)
@app.route('/', methods = ["GET" , "POST"]) 
def index():
    if request.method == 'POST':
        screen_name = request.form['screen_name'] 
        tweets_df = get_tweets_df(screen_name)
        return render_template(
            'index.html',
            profile=get_profile(screen_name),
            tweets_df = tweets_df,
            grouped_df = get_grouped_df(tweets_df),
            sorted_df = get_sorted_df(tweets_df)
        )
    return render_template('index.html') 

def get_tweets_df(screen_name):
   columns = [
      "tweet_id",
      "created_at",
      "text",
      "fav",
      "retweets"
      ]
   tweets_df = pd.DataFrame(columns=columns) #1
   for tweet in tweepy.Cursor(api.user_timeline,screen_name = screen_name, exclude_replies = True).items(): #2
       try:
           if not "RT @" in tweet.text: #3
               se = pd.Series([ #4
                       tweet.id,
                       tweet.created_at,
                       tweet.text.replace('\n',''),
                       tweet.favorite_count,
                       tweet.retweet_count
                   ]
                   ,columns
                   )
               tweets_df = tweets_df.append(se,ignore_index=True) #5
       except Exception as e:
           print (e)
   tweets_df["created_at"] = pd.to_datetime(tweets_df["created_at"]) #6
   return tweets_df

def get_profile(screen_name):
   user = api.get_user(screen_name= screen_name)
   profile = {
       "id": user.id,
       "screen_name": screen_name,
       "image": user.profile_image_url,
       "description": user.description
   }
   return profile

def get_grouped_df(tweets_df):
    grouped_df = tweets_df.groupby([tweets_df["created_at"].dt.date]).sum().sort_values(by="created_at", ascending=False)
    return grouped_df

def get_sorted_df(tweets_df):
   sorted_df = tweets_df.sort_values(by="retweets", ascending=False)
   return sorted_df

if __name__ == '__main__':
    app.run(host = '0.0.0.0', port = 50000, debug=True)

HTMLに共有したい変数を指定しよう

さて、ここまでで、

  1. ツイートの一覧
  2. ツイッターIDのプロフィールのデータ
  3. 日付ごとにいいね数とリツイート数を集計したデータ
  4. リツイート数順に並び替えたツイート一覧データ

が存在します。これを、index.htmlにデータを返してあげましょう。処理としては、以下の通りです。

from flask import Flask, render_template, request
import os
from dotenv import load_dotenv

import tweepy
import pandas as pd

app = Flask(__name__)

load_dotenv()

CONSUMER_KEY = os.getenv("CONSUMER_KEY")
CONSUMER_SECRET = os.getenv("CONSUMER_SECRET")
ACCESS_TOKEN = os.getenv("ACCESS_TOKEN")
ACCESS_SECRET = os.getenv("ACCESS_SECRET")

auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET)

api = tweepy.API(auth)
@app.route('/', methods = ["GET" , "POST"]) 
def index():
    if request.method == 'POST':
        screen_name = request.form['screen_name'] 
        tweets_df = get_tweets_df(screen_name)
        return render_template(
            'index.html',
            profile=get_profile(screen_name),
            tweets_df = tweets_df,
            grouped_df = get_grouped_df(tweets_df),
            sorted_df = get_sorted_df(tweets_df)
        )
    return render_template('index.html') 

def get_tweets_df(screen_name):
   columns = [
      "tweet_id",
      "created_at",
      "text",
      "fav",
      "retweets"
      ]
   tweets_df = pd.DataFrame(columns=columns) #1
   for tweet in tweepy.Cursor(api.user_timeline,screen_name = screen_name, exclude_replies = True).items(): #2
       try:
           if not "RT @" in tweet.text: #3
               se = pd.Series([ #4
                       tweet.id,
                       tweet.created_at,
                       tweet.text.replace('\n',''),
                       tweet.favorite_count,
                       tweet.retweet_count
                   ]
                   ,columns
                   )
               tweets_df = tweets_df.append(se,ignore_index=True) #5
       except Exception as e:
           print (e)
   tweets_df["created_at"] = pd.to_datetime(tweets_df["created_at"]) #6
   return tweets_df

def get_profile(screen_name):
   user = api.get_user(screen_name= screen_name)
   profile = {
       "id": user.id,
       "screen_name": screen_name,
       "image": user.profile_image_url,
       "description": user.description
   }
   return profile

def get_grouped_df(tweets_df):
    grouped_df = tweets_df.groupby([tweets_df["created_at"].dt.date]).sum().sort_values(by="created_at", ascending=False)
    return grouped_df

def get_sorted_df(tweets_df):
   sorted_df = tweets_df.sort_values(by="retweets", ascending=False)
   return sorted_df

if __name__ == '__main__':
    app.run(host = '0.0.0.0', port = 50000, debug=True)

render_template関数には、引数に

  1. 表示するhtmlファイル
  2. htmlに共有する変数

を指定することができます。今回の場合だと、辞書型のprofile変数などを、引数に入れることができます。

変数名は任意の名前を設定することができます。

https://flask.palletsprojects.com/en/1.1.x/quickstart/#rendering-templates

HTMLの部分を確認しよう

ここまでできれば、あとはもうHTMLの部分を反映するだけです。あと少しですね!実装が簡単な③、④、②の順番で解説していこうと思います。

③プロフィール画面と、ツイートの日時集計をHTMLに表示する

それでは、3の部分について実装していきましょう。まず、③と④の部分は、一つの行rowの中に、1:2の割合で分割されているcolumnがあるので、Bootstrapで先に区切っておきましょう。

<div class="row">
 <!-- プロフィールと日時ツイート集計 -->
 <div class="col-md-4">
 </div>
 <div class= "col-md-8">
 </div>
</div>

それでは、プロフィールの内容を埋めていきましょう。ここの部分ですね。 コード全体がこちらになります。

    <!-- プロフィール -->
   <div class="col-md-4">
   {% if profile %} <!-- #1 -->
     <div class="white-container">
       <div>
         <img src="{{profile.image}}" width="100px">  <!-- #2 -->
         <a href="https://twitter.com/{{profile.user_id}}">@{{profile.user_id}}</a>   <!-- #3 -->
       </div>
       <div>{{profile.description}}</div>  <!-- #4 -->
     </div>
   {% endif %}

#1では、まずFlaskから受け取った値で、profileという値が存在するか確認します。このprofileというのは、さきほど編集したapp.pyで、render_template関数の引数で、htmlにかえしたprofileのデータになります。

       return render_template(
           'index.html',
           profile=get_profile(user_id), #4
           tweets_df = tweets_df,
           grouped_df = grouped_df,
           sorted_df = sorted_df
           )

さきほどのprofileが存在するかを判定するのは、GETリクエストの場合はprofileデータが存在しないので、エラーが起こってしまうんですよね。なので、このようにprofileの値が存在したら、以下の処理をするという風に切り分ける必要があるわけです。

{% if profile %} <!-- #1 -->

 

あとは、画像ファイルを読み込んであげます。

         <img src="{{profile.image}}" width="100px">  <!-- #2 -->

ツイッターのプロフィールページに飛ばしてあげるために、このようにURLを設定してあげています。

         <a href="https://twitter.com/{{profile.user_id}}">@{{profile.user_id}}</a>   <!-- #3 -->

そして、最後にプロフィールの説明を追加してあげます。

        <div>{{profile.description}}</div>  <!-- #4 -->

 

さて、途中に気になるwhite-containerというクラスが存在しましたね。

     <div class="white-container">

 

こちらは、バックグラウンドが白になるように、CSSで設定してあげます。headのstyleタグの中に、以下のCSSを追加します。

body{
 background-color:#e6ecf0;
}
div{
 line-height: 1.2;
}
.white-container{
 border-bottom: 1px solid #e6ecf0;
 padding:20px;
 background-color:#ffffff;
}

bodyで指定しているのは、背景色です。ちょっと灰色がかったバックグラウンドカラーで、これはツイッターの色をそのままコピーしています。ちなみにこのカラーコード#e6ecf0は、開発者ツールの要素の検証を利用して、色をコピーしています。

body{
 background-color:#e6ecf0;
}

 

また、white-containerクラスでは、以下のように下に枠線を入れていて、(border-bottom)さらに、20pxだけ内側に余白を持たせて(padding)、最後にバックグラウンドの色を白色(#fffff)にしています。

.white-container{
 border-bottom: 1px solid #e6ecf0;
 padding:20px;
 background-color:#ffffff;
}

こうすることで、プロフィールのページができるかと思います。

④もっとも評価の高いツイートを表示するHTMLを作成しよう

では、次にこちらの画面の部分を作成していきましょう。 さきにコードの全体像を見ていきましょう。

   <div class= "col-md-8">
   {% if profile %} <!--#1 -->
   <div>もっともリツイート・いいねされたツイート</div>
   {% for index, row in sorted_df.iterrows() %} <!--#2 -->
     <div class="white-container">
       <div>{{row["created_at"]}}</div> <!--#3 -->
       <div id="tweet-text">{{row["text"]}}</div>
       <div>
         <span><i class="far fa-heart"></i>  {{row["fav"]}}</span> <span><i class="fas fa-retweet"></i>  {{row["retweets"]}}</span>
         <a class="pull-right">ツイートを見る</a> <!--#4 -->
       </div>
     </div>
   {% endfor %}
   {% endif %}
 </div>

 

#1では、先ほどとどうようにprofileデータが存在するかどうかを確認しています。 #2では、取得したsorted_dfのデータフレームから、1行ずつ取り出すiterrowsメソッドを利用しています。rowはSeries型の行のデータが格納されています。 #3では、rowの中に入っているそれぞれの値を取得しています。 #4では、classにpull-rightを指定して、ツイートを見るリンクを右に寄せています。これはBootstrapのクラスとなっています。 さて、途中でよくわからないクラスができてきたかと思います。

<span><i class="far fa-heart"></i> {{row["fav"]}}</span> <span><i class="fas fa-retweet"></i> {{row["retweets"]}}</span>

 

この<i class="far fa-heart"></i>というのは、実はアプリのハートマークを記述している部分になります。これはfont-awesomeというライブラリを利用すると、使えるようになります。 font-awesomeは、bootstrapと同じように、CDNというインターネット上のCSSファイルをインポートすることで使えるようになります。くわしくはここのサイトを見てみてください。fa-heartを指定するとハートマーク、fa-retweetを指定するとリツイートマークが表示されます。 https://saruwakakun.com/html-css/basic/font-awesome

②いいね数とリツイート数のグラフを作成しよう

さて、最後に画像の②にあたる部分を作成します。 ここの部分をもう少し細かく見てみましょう。 リツイート数と、いいね数を一日ごとに計算しています。ここの部分の実装を見ていきましょう。実は、HTML上ではこれだけなんですよね。

  <!-- chart -->
   <div class="row">
     <div class="col-md-12">
       <canvas id="chart"></canvas>
     </div>
   </div>

 

すくな!って思った方もいらっしゃるかと思いますが、これは実はchart.jsというライブラリを利用して、JavaScriptからグラフ描画をしています。 まず、chart.jsが組み込めるように、headタグの下にchart.jsのCDNを埋め込みます。

<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Top Tweets Finder</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css";; rel="stylesheet">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous">
<!-- ここにchartjsのCDNを埋め込む -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.4/Chart.min.js"></script>
<!--[if lt IE 9]>
       <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>;;
       <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>;;
<![endif]-->
<style>
body{
 background-color:#e6ecf0;
}
div{
 line-height: 1.2;
}
.white-container{
 border-bottom: 1px solid #e6ecf0;
 padding:20px;
 background-color:#ffffff;
}
#tweet-text{
 padding:20px;
 margin-bottom:10px;
 line-height: 1.2;
}
</style>
</head>

 

そこの部分は、bodyタグの下の部分で実装されているので、見てみましょう。ちょっと長いですが。

<script>
 {% if profile %}
  // bar chart data
  var barData = {
  labels : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                 "{{row['created_at']}}",
             {% endfor %}],
  datasets : [
     {
           label: "Retweets",
           backgroundColor: 'rgba(255, 99, 132, 0.2)',
           borderColor: 'rgba(255,99,132,1)',
           borderWidth:10,
           bezierCurve : false,
           data : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                     {{row["retweets"]}},
                   {% endfor %}]
     },{
           label: "Favorites",
           data : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                     {{row["fav"]}},
                   {% endfor %}],
           type: 'line',
           borderColor: 'rgb(63, 127, 191)',
     }
     ]
  }
  // draw bar chart
 var mychart = document.getElementById("chart");
 var chart = new Chart(mychart, {
   type:'bar',
   data:barData,
   options: {
       scales: {
         yAxes: [
           {
             ticks: {
               beginAtZero: true,
               min: 0,
               max: 1000
             }
           }
         ]
       }
     }
 });
  {% endif %}
</script>

ここに関しては、ほとんどコピペなので、なんとなくしか理解していないです。が、大事なところだけ解説します。 ラベルの指定は、横軸の時間を設定しています。flaskでfor文を利用する場合には、終わりの時にendforという記号を入れてあげる点が注意が必要です。

  var barData = {
  labels : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                 "{{row['created_at']}}",
             {% endfor %}],

 

また、縦軸に使うリツイート数といいね数を、datasetsに格納しています。Favoritesの時に、typeをlineにして、折れ線グラフに変えてあげています。

  datasets : [
     {
           label: "Retweets",
           backgroundColor: 'rgba(255, 99, 132, 0.2)',
           borderColor: 'rgba(255,99,132,1)',
           borderWidth:10,
           bezierCurve : false,
           data : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                     {{row["retweets"]}},
                   {% endfor %}]
     },{
           label: "Favorites",
           data : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                     {{row["fav"]}},
                   {% endfor %}],
           type: 'line',
           borderColor: 'rgb(63, 127, 191)',
     }
     ]
  }

で、グラフ描画の部分がこちらですね。

 // draw bar chart
 var mychart = document.getElementById("chart");
 var chart = new Chart(mychart, {
   type:'bar',
   data:barData,
   options: {
       scales: {
         yAxes: [
           {
             ticks: {
               beginAtZero: true,
               min: 0,
               max: 1000
             }
           }
         ]
       }
     }
 });

縦軸の上限を1000に設定したいです。というのも、ユーザーを単純に比較したいので。その場合はoptionsで設定してあげます。

options: {
       scales: {
         yAxes: [
           {
             ticks: {
               beginAtZero: true,
               min: 0,
               max: 1000
             }
           }
         ]
       }
     }

こんな感じでやると、グラフが描画できるはずです。

おめでとうございます!🎉
このチュートリアルはこれで終了です。
次のコースを探す