Python で画像や URL やリプライを含まない日本語のツイートを集める


経緯

機械学習用に日本語の文章がたくさん欲しくなったため、
Twitter の Streaming API でツイートをたくさん集めてみることにしました。

自分で集めなくとも、どこかに同じようなことをやっている人のファイルがあれば、それを使おうと思いましたが、数分ほど Google で検索して、ちょうど良いものが見当たらなかったため、2017 年の書き初めということで、自分で書いてみました。

Twython というライブラリを使っています。 (私は以前は Tweepy を使っていたのですが、どうやら最近は Twython の方が人気のようです。)

除外したいツイート

  • 画像を含むツイート
  • URL を含むツイート
  • ハッシュタグを含むツイート
  • リツイート
  • リプライ・メンションを含むツイート

こういったツイートは、コーパスの材料として使うには不向きだと考えたので、除外しています。

ファイルの形式

  • 1 行 1 ツイートで LF 区切り
  • ツイートに含む改行は CR 区切り

という形式にしています。

このようにすれば後々も改行の情報を残すことができ、かつ「1 行 1 ツイート」というプログラムから扱いやすい形式にできます。

使い方

こんな風に実行すると、標準出力に出力してくれます。
この例では有効な 10 ツイート取得すると終了します。 (-n オプションでこの回数は指定できます。)

$ python tweetcorpus.py -n 10

応用

こんな風にシェルしておけば、途中でエラーがあってもほぼ無限に取り続けられます。
tee で TTY をパイプしているので、進捗も目視で見ていられます。
gzip をパイプしているので大量のツイートを集めても少し安心です。

$ while true; do python -u tweetcorpus.py -n 500 | tee /dev/tty | gzip -cn >> tweet.gz ; sleep 1 ; done

(↑ で使っている gzip の結合については gzip 圧縮したテキストファイルは cat でつなげても大丈夫だよ を参照)
(↑ で使っている Python の -u オプションについては Python で stdout/stderr のバッファを無効にするオプション を参照)

個人的に、プログラミング言語ごとの gzip モジュールを使うよりも、このようにプログラム本体をシンプルにして、パイプでつなげていくスタイルが好きなのでこのようにしています。

環境変数

Twitter の API 用の OAuth トークンは環境変数 APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET から読み取ります。

Twitter でアプリケーションを作成し、
次のようなファイルを用意して

.env
#!/bin/sh
export APP_KEY='XXXXXXXXXXXXX'
export APP_SECRET='XXXXXXXXXXXXXXXXXXXX'
export OAUTH_TOKEN='XXXXX-XXXXXXXXXX'
export OAUTH_TOKEN_SECRET='XXXXXXXXXX'
source ./.env

などと予め読み込んでおきましょう。

準備

Python 環境があれば Twython をインストールすれば OK です。

$ pip3 install twython==3.4.0

ソース

tweetcorpus.py
import argparse
import html
import os
import sys

from twython import TwythonStreamer


class CorpusStreamer(TwythonStreamer):

    def __init__(self, *args,
                 max_corpus_tweets=100,
                 write_file=sys.stdout):
        super().__init__(*args)
        self.corpus_tweets = 0
        self.max_corpus_tweets = max_corpus_tweets
        self.write_file = write_file

    def exit_when_corpus_tweets_exceeded(self):
        if self.corpus_tweets >= self.max_corpus_tweets:
            self.disconnect()

    def write(self, text):
        corpus_text = text.replace('\n', '\r')
        self.write_file.write(corpus_text + '\n')
        self.corpus_tweets += 1

    def on_success(self, tweet):
        if 'text' not in tweet:
            # ツイート情報以外を除外 (通知など)
            return
        if 'retweeted_status' in tweet:
            # リツイートを除外
            return
        if any(tweet['entities'].values()):
            '''
            tweet.entities.url
            tweet.entities.media
            tweet.entities.symbol
            など自然言語処理だけでは扱えない情報を含むツイートを除外
            '''
            return
        text = html.unescape(tweet['text'])
        self.write(text)
        self.exit_when_corpus_tweets_exceeded()


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-n', '--number-of-corpus-tweets',
                        type=int, default=100)
    parser.add_argument('-o', '--outfile',
                        type=argparse.FileType('w', encoding='UTF-8'),
                        default=sys.stdout)
    parser.add_argument('-l', '--language', type=str, default='ja')

    app_key = os.environ['APP_KEY']
    app_secret = os.environ['APP_SECRET']
    oauth_token = os.environ['OAUTH_TOKEN']
    oauth_token_secret = os.environ['OAUTH_TOKEN_SECRET']

    args = parser.parse_args()
    stream = CorpusStreamer(app_key, app_secret,
                            oauth_token, oauth_token_secret,
                            max_corpus_tweets=args.number_of_corpus_tweets,
                            write_file=args.outfile)
    stream.statuses.sample(language=args.language)


if __name__ == '__main__':
    main()

環境

せっかくなので、最新版の Python 3.6 で試してみましたが、おそらく 3 系で twython のインストールができれば動作すると思います。

  • Python 3.6.0 (default, Dec 29 2016, 18:49:32) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
  • twython==3.4.0