ツイートのデータを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に共有したい変数を指定しよう
さて、ここまでで、
- ツイートの一覧
- ツイッターIDのプロフィールのデータ
- 日付ごとにいいね数とリツイート数を集計したデータ
- リツイート数順に並び替えたツイート一覧データ
が存在します。これを、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関数には、引数に
- 表示するhtmlファイル
- 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
}
}
]
}
}
こんな感じでやると、グラフが描画できるはずです。
このチュートリアルはこれで終了です。