超言理論

特に益もない日記である

簡単なリプライ収集スクリプト書いてみた

対話システムを研究する一環で、Twitterのリプライを利用する手法がある。
三連休中に、Streaming APITwitterのstatus_id参照を使ってリプライを集める簡単なプログラムを用意したので、ついでにここで忘れないように解説を入れておく。
ちなみに、後輩から熱烈なPython3のプッシュを受けたので、コードはPython3仕様である。
一応、Python3の勉強を軽くやってから書き始めているが、バージョンを変えてまだ1時間くらいしかコーディングしてないので何か間違ったことを書いているかもしれないので、何かあったら教えてください。
あと誰か参考書下さい。

Dive Into Python 3 日本語版

Dive Into Python 3 日本語版

さらに言うと、Python3にはTwitterのライブラリがあまりないので、注意したい。(Python2のときはtwitter-pythonとかいろいろあったけど。)
今回はsixohsix/twitter · GitHubTwitterライブラリを利用した。データの構造がそっくりそのままTwitter API公式仕様なのでかなり使いやすい。

以下コード。

#-*- encoding: utf-8 -*-

from twitter import *
import os,sys,time

#NG words
#ここでタプルに追加した単語(記号)を含むツイートは収集から除外される。
#今回は、URLやHashtag、【定期】のように使われるカッコなどを除外とした。
check_chara = ('http', '#', '\\', '【','】')

#Retry MAX
retry_max = 10
#Retry time
retry_time = 10

#ここにはTwitter Devで登録したアプリケーションのCONSUMER_KEY,SECRETを記入する。
CONSUMER_NAME = 'YOUR APPLICATION NAME'
CONSUMER_KEY =  'YOUR CONSUMER KEY'
CONSUMER_SECRET = 'YOUR CONSUMER SECRET'

#過去に認証したときのTokenを探す。
TWITTER_CREDS = os.path.expanduser('.credentials')
if not os.path.exists(TWITTER_CREDS):
  #無ければ、OAuthで認証
  oauth_dance(CONSUMER_NAME, CONSUMER_KEY, CONSUMER_SECRET, TWITTER_CREDS)
oauth_token, oauth_secret = read_token_file(TWITTER_CREDS)

# token
# Tokenを用いて、Streaming APIで新着ツイートを見つけるstreamと、普通のTwitter APIで'in_reply_to_status_id'を参照するtwitterの2つを用意する。
stream = TwitterStream(auth=OAuth(oauth_token, oauth_secret, CONSUMER_KEY, CONSUMER_SECRET))
twitter = Twitter(auth=OAuth(oauth_token, oauth_secret, CONSUMER_KEY, CONSUMER_SECRET))

#in_reply_to_status_idから宛先となるツイートを参照する。
def show(_status):
  #print('IN:\n'+str(_status), file=sys.stderr)
  #リトライを設定
  for r in range(retry_max):
    #言語が日本語、in_reply_to_status_idが明示されており、禁止単語を含まないツイートの場合
    if 'lang' in _status and _status['lang'] == 'ja' and 'in_reply_to_status_id' in _status and not _status['in_reply_to_status_id'] is None and 'text' in _status and check(_status['text']):
      try:
        #ツイートを参照
        status=twitter.statuses.show(id=_status['in_reply_to_status_id'])
      except:
        #print('! Error\tRetry to read in_reply_tweet - '+str(r), file=sys.stderr)
        time.sleep(retry_time)
        continue
      #print('OUT:\n'+str(status), file=sys.stderr)
      #宛先のツイートが日本語、in_reply_to_status_idが明示されており、禁止単語を含まないツイートの場合
      if 'lang' in _status and _status['lang'] == 'ja' and 'text' in status and check(status['text']):
        #標準出力で応答の対を出力
        print(str(status['id'])+'\t'+trim(status['text'])+'\t'+str(_status['id'])+'\t'+trim(_status['text']))
        #さらに、宛先のツイートがin_reply_to_status_idを持つ場合は、さらに参照を行う。
        if 'in_reply_to_status_id' in status and not status['in_reply_to_status_id'] is None:
          show(status)
        break
      else:
        #print('! Info\tin_reply_tweet is not acceptable', file=sys.stderr)
        break
    else:
      break

#改行を適当に置き換える
def trim(text):
  return text.replace('\r','-br-').replace('\n','-br-')

#禁止文字のチェックを行う
def check(text):
  for char in check_chara:
    if char in text:
      return False
  return True

#Streaming APIからツイートを読み出す
def main():
  while 1:
    #ストリームに接続して適当にツイートを読み出す
    statuses = stream.statuses.sample()
    for status in statuses:
      #参照に送る
      show(status)

if __name__ == '__main__':
 main()

1~2日動かしてみた感じ、1万ペア/日くらいの収集効率だったので、まぁ及第点かなというところ。
Streaming APIを使っているところもTwitterのid参照に代えて、idを1から順番に舐めていけば一応すべてのリプライペアを集められるはずだけど、あまりにも古いidの参照は失敗するかもしれない。
あと、(あまりこういうことを書くと怒られそうだけど)アプリケーション認証したアカウントを複数用意しておいて、リトライする際に適当に利用するアカウントを変更するともっと効率的かつ高速に集められるかもしれない。

あと、このプログラムで集めたリプライのペアは極稀に重複するので、適当にuniqでもとってやるとよい。

cat rawdata | sort | uniq > cleandata

あと、データはタブ区切りになっているので、本文を対訳的にとりだしたい時は以下のように2フィールド目と4フィールド目のみ区切りに従って取り出せばよい。

cat cleandata | awk 'BEGIN { FS = "\t" } ; { print $2 }' > in
cat cleandata | awk 'BEGIN { FS = "\t" } ; { print $4 }' > out

何かあったらご連絡ください*1

*1:なお、プログラムは公開しますが、このプログラムで収集されたデータに関しては責任を持ちませんのでよろしくお願いいたします。


Copyright © 2012-2016 Masahiro MIZUKAMI All Rights Reserved.