Help us understand the problem. What is going on with this article?

ツイートを長期間収集する実験(集計&中身確認編)

More than 3 years have passed since last update.

前回まで。

  • プログラム準備完了。
  • ライブラリの不具合修正実施
  • 実行開始!
    • 一応最初のピークは問題なかった?
  • とりあえず集計してみよう
  • 外から覗いてみよう

時間ごとの集計をしてみる

 ひとまず1週間ほど経過しました。とりあえず今のところ無事に動いているようです。
 となると、次にほしいのは現状の入力数とか、時間ごとの集計になるわけですが。

>>> db.xxx.count()

 とやれば、カウント自体は上がっているのがわかるのですが、いくらなんでも正確とは言い難い……どうにかして、時間単位のツイート数集計に持ち込む必要があります。

MapReduceで集計

 MongoDBでの集計手段は何やら複数あるようですが、とりあえず「薄い本」をもとにMapReduceでもってして集計してみることにします。
 ……JavaScriptも触るの初めてなんだけどなぁ、とか思いつつ、ググりまくって

check.js
db.xxxxx.mapReduce(
    // map
    function() {

        d = new Date(Date.parse(this.created_at) + 32400000);
        p = d.getFullYear() + "/" + ("0" + (d.getMonth() + 1)).slice(-2) + "/" + ("0" + d.getDate()).slice(-2) + " " + ("0" + d.getHours()).slice(-2) + ":00:00"
        emit(
            p,
            1
        );
    },

    // reduce
    function(key, values) {
        return Array.sum(values)
    },

    {
        query: {},
        out: "TweetsCount"
    }
)

 こんな感じに。
 (JavaScriptの日時formatって、こんなめんどい手段しかないんですっけ?)

# mongo localhost/TwitterDB check.js

 こんな感じで実行すると、別のCollection「TweetsCount」に結果が格納されます。

(ピーク発生個所だけ抜粋)
{ "_id" : "2016/10/28 13:00:00", "value" : 67 }
{ "_id" : "2016/10/28 14:00:00", "value" : 47 }
{ "_id" : "2016/10/28 15:00:00", "value" : 102 }
{ "_id" : "2016/10/28 16:00:00", "value" : 103 }
{ "_id" : "2016/10/28 17:00:00", "value" : 2850 }
{ "_id" : "2016/10/28 18:00:00", "value" : 5317 }
{ "_id" : "2016/10/28 19:00:00", "value" : 4324 }

 とりあえず取れてるようです。なーんか全体的に少ないかな?という気もするのですが(ピーク1ケタ足らん気がする……たぶん検索キーの都合)
 この程度の実行なら、さほどの負荷にはならないようです。100日分とかまとまったらまた変わってくるのかもしれませんが……。

DBの中身をチェックしよう

 現在動かしている取得プログラムは、画面上の標準出力に何も出ないので、何が格納されたのか確認できません。
 MongoDB直接覗けば見れますが、リアルタイムの取得状況までは分からないですし、見るだけなら不要な項目がJSON上にたくさん入っており、率直に言って見づらいです。

 この辺どうにかして、リアルタイムで(またはある程度の追従で)ツイート内容がチェックできるスクリプト、がほしいよなぁ、という話になります。

 なんかいろいろ手段もありそうなのですが、手っ取り早くて大げさにならなくて、見たいものだけ見るような感じにがりごりやったらこんな感じ(実装2hくらい)。

InsertChecker.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-

from pymongo import MongoClient
import json

import time

# MongoDB 接続関係の変数
HOST = 'mongo'      # ホスト
PORT = 27017            # ポート(デフォルト:27017)
DB_NAME = 'TwitterDB'   # DB名
COL_NAME= 'xxxxxx'    # コレクション名


# ------ ここからメイン処理 ------

try:
    Client = MongoClient(HOST, PORT)
    db = Client[DB_NAME]
    Collection = db[COL_NAME]
    print('DB準備完了')

    twCnt = Collection.count()  # 一番最初のカウント取得

except pymongo.errors.PyMongoError as exc:
    # 接続エラー
    print('DB接続エラー')


while(True):        # 無限ループ
    try:

        if(Collection.count() > twCnt):
            for doc in Collection.find(skip=twCnt):

                if("retweeted_status" in doc):
                    if("extended_tweet" in doc["retweeted_status"]):
                        # RTの長文
                        print("RT @" + doc["retweeted_status"]["user"]["screen_name"] + ": " +doc["retweeted_status"]["extended_tweet"]["full_text"])
                    else:
                        # RTの短文
                        print("RT @" + doc["retweeted_status"]["user"]["screen_name"] + ": " +doc["retweeted_status"]["text"])
                else:
                    if("extended_tweet" in doc):
                        # 長文
                        print(doc["extended_tweet"]["full_text"])
                    else:
                        # 短文
                        print(doc["text"])

                print(u"{name}({screen}) {created} via {src}".format(name=doc["user"]["name"], screen=doc["user"]["screen_name"],
                    created=doc["created_at"], src=doc["source"]))
                print(u"--------------------------------------------------")

        twCnt = Collection.count()

        # チェック間隔は3秒ごとくらいで(流速次第で10秒でもよいか?)
        time.sleep(3)

    except KeyboardInterrupt:
        # CTRL+C
        print('CTRL + C で終了。')
        ExitCode = 0
        break

 (途中のprint()文の記法が変わっているのは、参考資料からのコピペが混じるためです)
 やってることは簡単で、定期的にコレクションの数をチェックして、多くなっていれば増えた分を整形して出力する、というものです。

 なにやらツイート文言の拡張なる仕様変更があった結果、中盤のifで以下の4パターンに分岐させることに。
・RTのツイートで、かつ拡張仕様(長文)のツイートのパターン
・RTのツイートで、かつ標準仕様(短文)のツイートのパターン
・通常のツイートで、かつ拡張仕様(長文)のツイートのパターン
・通常のツイートで、かつ標準仕様(短文)のツイートのパターン

 あとは時間表示してユーザ名表示しておっけー。こんなにウルトラ適当でも、"tail -f"っぽく表示される!ステキ!

小改造

 これでもだいたいやりたいことを満たしているのですが、便利を追求したくなるのが人情。

Fav数、RT数を表示

 RTされまくってるツイートが、実際どれだけRTされてるの? くらいはほしいよねーとか思うわけで。

print("\n<RT: " + str(doc["retweeted_status"]["retweet_count"]) + " cnt, Fav: " + str(doc["retweeted_status"]["favorite_count"]) + "cnt >")

 こんな感じのをRTの際に表示を行う分岐の中に挿入(インデントは揃えること)。RTされるたびにカウントアップしてるのがわかります。

時刻表示を読みやすいように

 Twitterから送られるJSON内の時刻表示は、日本人的には読みづらいのでこれを改変。

d = datetime.strptime(doc["created_at"],'%a %b %d %H:%M:%S +0000 %Y') + timedelta(hours=9)

 とやってdatetime形式に変換ののち時刻格納個所を

created=d.strftime("%Y/%m/%d %H:%M:%S")

 こんなに置き換えれば完成。「from datetime import datetime, timedelta」も忘れずに。

クライアント名表示からHTMLタグを抜く

 ツイートがどんなクライアントから送信されたかが表示できるのですが、この項目、HTMLタグが入れられるためか、実際タグが入ってて見づらいことこの上ありません。
 「import re」として正規表現を有効にして、doc["source"]を指定している箇所でこんな感じにすればあら不思議、タグがきれいになくなります。

src=re.sub("<.+?>", "", doc["source"])

(タグ以外がなくなる可能性もないわけではないが、今のところうまくいってる。)

つぎになにやるか(ノープラン)

 とりあえずこれで、
・ 時間単位でのツイート数
・ リアルタイムで取得しているツイートの表示

 ができるようになりました。やはりちゃんとツイートが流れているのを見ると精神的に安心しますね。

 次の目標は
・現在一番RTされてるツイートの抽出
・Twitterクライアントの利用統計
・ツイートから店舗(換言)名を抽出して、言及回数を測る
 あたりでしょうか。ちまちま作ってみようと思います。

(しつこく続く。)

yuhkan
へっぽこプログラマです。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away