9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

バズったツイートへのリアクションを感情分析してみる【Google Natural Language API / Python】

Last updated at Posted at 2019-09-22

概要

バズったツイートへのリアクション(リプライ、引用RT)を、GoogleのNatural Language APIで感情分析してみました。

前日にバズったツイートに対して↑を実行した結果を返すbotも作ったので、よければのぞいてみてください。

実装方法を簡単に書くと、

Twitter APIで指定したRT数以上のツイートを抽出

抽出したツイートに対するリプライと引用RTを取得し整形

Google Natural Language APIへ投げてスコアを測定

(スコアを元にツイート内容を出し分け)

(AWS Lambda + CloudWatch Eventsで定期実行)

となっています(カッコ内はbotを実装する上で必要となる手順です)。

それでは、以下で具体的な実装を紹介します。
なお、プログラムはPythonで書いています。

実装

ツイート取得まわり

Tweepyを使ってツイートの取得を行います。
以下の操作では、取得するツイートにRT(引用RTを除く)が含まれないようexclude:retweetsオプションをつけて行います。

まず、指定したRT数以上のツイートを抽出します。
次にそのツイートへのリプライを取得するのですが、特定のツイートへのリプライのみを取得するエンドポイントは残念ながらありません。
そのため、

  1. バズったツイートをしたユーザへのリプライ全体を、@screen_nameをクエリとして取得
  2. バズったツイートに対してのリプライか否かを、in_reply_to_status_idを用いて判定
    の手順で実現しました。

そして引用RTの取得です。
引用RTの実体はツイートのURLの埋め込みであるため、バズったツイートのツイートIDをクエリとして絞り込むことができます。

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

api = tweepy.API(auth)

tweet_id_str = []
screen_name = []
reply_texts_rows = []

today = datetime.today() + timedelta(hours=9)
yesterday = today - timedelta(days=1)

# 前日のバズったツイートを取得
for status in api.search(q='min_retweets:2000 exclude:retweets until:' + datetime.strftime(yesterday, '%Y-%m-%d'), lang='ja', count=30):
    tweet_id_str.append(status._json['id_str'])
    screen_name.append(status._json['user']['screen_name'])

    query_reply = '@' + status._json['user']['screen_name'] + ' exclude:retweets'

    row = []
    # リプライ取得
    for status_reply in api.search(q=query_reply, lang='ja', count=100):
        if status_reply._json['in_reply_to_status_id'] == status._json['id']:
            row.append(format_text(status_reply._json['text']))
        else:
            continue

    query_quote = status._json['id_str'] + ' exclude:retweets'

    # 引用RT取得
    for status_quote in api.search(q=query_quote, lang='ja', count=100):
        if status_quote._json['id_str'] == status._json['id_str']:
            continue
        else:
            row.append(format_text(status_quote._json['text']))

    reply_texts_rows.append(row)

取得したツイートには、検索クエリで用いた@screen_nameや引用元のURLが混ざってしまっているため、正規表現を用いてこれらを取り除きます。

def format_text(text):
    text=re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
    text=re.sub('\n', " ", text)
    text=re.sub(r'@?[!-~]+', "", text)
    return text

最後に整形したツイートをひとつのリストにまとめれば、感情判定をするためのデータの準備は完了です。

感情分析

準備したデータをGoogle Natural Language APIに投げ、感情分析を行います。
リストのデータをループを回して1つずつ投げるとAPIの無料枠を割とすぐに超えてしまうため、あらかじめ結合しておきます。

データをAPIに投げて判定を返すanalyze_sentiment関数は、ほぼサンプルプログラムと同じコードです。

count = 0
for reply_texts_row in reply_texts_rows:
    reply_texts = " ".join(reply_texts_row)
    result = analyze_sentiment(reply_texts)

    # 以下botでのツイート用
    description = judge_description(result)
    message = datetime.strftime(yesterday, '%Y-%m-%d') + '(' + str(count + 1) + ')\nリアクション分析結果:' + description + 'https://twitter.com/' + screen_name[count] + '/status/' + tweet_id_str[count]
    api.update_status(message)
    count += 1
def analyze_sentiment(content):
    client = language_v1.LanguageServiceClient()

    if isinstance(content, six.binary_type):
        content = content.decode('utf-8')

    type_ = enums.Document.Type.PLAIN_TEXT
    document = {'type': type_, 'content': content}

    response = client.analyze_sentiment(document)
    sentiment = response.document_sentiment
    return sentiment.score, sentiment.magnitude

感情分析のスコアを得るための実装はここまでです。

最後に、スコアを元にbotでツイートする内容を決めるための関数に渡してあげます。
以下の判定は私の所感によるところが大きいので、文面から「スコアはこんなもんだったんだな」と読み取る程度にしていただければ幸いです。
スコアについての詳細は、公式のドキュメントをご覧ください。

def judge_description(result):
    if -0.1 < result[0] < 0.1:
        if result[1] < 20:
            message = "ニュートラル😐"
        elif 20 <= result[1] < 40:
            message = "ポジティブ・ネガティブがやや交錯🔥"
        elif 40 <= result[1] < 60:
            message = "🔥ポジティブ・ネガティブが交錯🔥"
        elif 60 <= result[1] < 80:
            message = "🔥🔥ポジティブ・ネガティブが多く交錯🔥🔥"
        elif 80 <= result[1]:
            message = "🔥🔥🔥ポジティブ・ネガティブが非常に多く交錯🔥🔥🔥"

        return message
        
    else:
        if result[0] >= 0.1:
            sentiment = "ポジティブ"
            flag = 0
        elif result[0] <= -0.1:
            sentiment = "ネガティブ"
            flag = 1
        
        if 0.1 <= abs(result[0]) < 0.3:
            prefix = "やや"
            suffix = "🙂" if flag == 0 else "🙁"
        elif 0.3 <= abs(result[0]) < 0.5:
            prefix = ""
            suffix = "😊" if flag == 0 else "😖"
        elif 0.5 <= abs(result[0]) < 0.7:
            prefix = "とても"
            suffix = "🥰" if flag == 0 else "😰"
        elif 0.7 <= abs(result[0]):
            prefix = "非常に"
            suffix = "🎉" if flag == 0 else "😱"

        return prefix + sentiment + suffix

賛否両論があるツイート(特に政治系)に対してのリアクションを分析すると、スコアはニュートラルに近いものの感情強度(magnitude)の値が大きくでる傾向があったため、あえて判定を加えてみました。

プログラムをAWS Lambdaにデプロイし、CloudWatch Eventsで定期実行してあげればbotの完成です。
(Lambdaにデプロイする際、Google Natural Language APIを動かすための環境変数の設定が必要なので、サービス アカウントキーをデプロイファイルに含め、コンソールで環境変数として指定してあげます。)
Cloud9でのデプロイがお手軽だったので、これくらいの簡単なプログラムをLambdaに乗せてみたい!という場合におすすめです。

プログラムの全体はgithubに置いています。
https://github.com/matsuri0828/tweet_emotion

以上、お読みいただきありがとうございました。

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?