TwitterAPI でツイートを大量に取得。サーバー側エラーも考慮(pythonで)

TwitterAPI を利用してツイートを大量にダウンロードしてみます。ダウンロードしたツイートを MeCab や Word2vec の元ネタにするつもりですが、とりあえず、データ取得の部分にしぼって書いてみます。

前半は API の基本事項の説明なので、そんなのもう知ってるよ、な方は 後半の 「大量にダウンロード」 にとんでください。

- 目次 -

スポンサーリンク

TwitterAPI とは

ツイートを投稿、閲覧するための API です。簡単に利用できるので、ツイッターにアクセスする独自アプリケーションを手軽に作成できます。

TwitterAPI を利用するには

  • アプリケーションの登録
  • 4つの認証キー取得

が必要にになります。
手順はいろいろなサイトで紹介されていますが、たとえば ここ で確認できます。

仕組み

REST形式で要求を出し、JSON形式で結果を受け取るのが基本的な仕組みです。用途に応じて様々なエンドポイントが定義されており、パラメータもそれぞれ異なります。いくつか例をあげると

●キーワードを指定してツイートを取得したければ

URL https://api.twitter.com/1.1/search/tweets.json
必須パラメータ q : 検索ワード

●特定ユーザーのツイートをまとめて取得したければ

URL https://api.twitter.com/1.1/statuses/user_timeline.json
必須パラメータ user_id : ユーザーID
 または
screen_name : スクリーン名(@で表示される名称)

●ツイートを投稿したければ

URL https://api.twitter.com/1.1/statuses/update.json
必須パラメータ status : 投稿ツイート

オプションのパラメータとして

  • count
    取得件数を指定します。
  • max_id
    ツイートID の最大値を指定します。
    大量のツイートを取得する場合、一度に取得できるツイート数は限られているので、何度もリクエストを出すことになります。その際 max_id を指定すると、前回取得したツイートの次からを取得対象にできます。

取得データ

取得データは ステータスコード、ヘッダー部、テキスト部に別れます。

ステータスコード

API 呼出しの正常・異常終了はステータスコードで判定します。200 が正常終了を意味します。

ヘッダー部

ヘッダ部には以下に説明する制限情報がのっています。

制限情報とは

TwitterAPI には一定時間内にアクセスできる回数に上限があります。
たとえば、キーワードで検索する

https://api.twitter.com/1.1/search/tweets.json

は 15 分間に 180 回までと決まっており、この規定回数を超えてアクセスするとエラーになります(コード:429)。もちろん永遠にアクセスできないわけではなく、15 分毎にアクセス制限はリセットされます。

そうした制限に関する情報がヘッダ部にのっており、以下のキーで取得できます。

  • X-Rate-Limit-Remaining
    アクセス可能回数 (アクセスするごとに -1 されていきます)
  • X-Rate-Limit-Reset
    アクセスが可能になるまでの時間 (アクセス可能回数が 0 になっても、この値の時間まで待てば回数はリセットされます)

補足

制限情報を確認するための専用エンドポイントもあります。
https://api.twitter.com/1.1/application/rate_limit_status.json

一発目の検索前にアクセス可能回数を調べるという手堅い実装をするなら、このエンドポイントが便利です。
こちらのソース の checkLimit で使ってます。

テキスト部

取得したツイートはテキスト部にのっています。テキスト部のフォーマットはエンドポイントによって差異があるので、ツイートを取り出す際は注意が必要です。
たとえば

  • キーワード検索(https://api.twitter.com/1.1/search/tweets.json)の場合
    ⇒ テキスト部の statuses の下にツイートが置かれる
  • ユーザー名で検索(https://api.twitter.com/1.1/statuses/user_timeline.json)の場合
    ⇒ テキスト部の直下にツイートが置かれる

という違いがあります。

取得データ フォーマットイメージ

以下はキーワード検索時の取得データのフォーマット(ごく一部の抜粋)です。ユーザー名で検索した場合は statuses という項目そのものが存在せず、テキスト部直下にツイートがぶら下がっています。
twitter APIのレスポンス

retweeted_status は
リツイート時のみセットされる

コピペ用

status_code
headers
┗ X-Rate-Limit-Remaining
┗ X-Rate-Limit-Reset
text
┗ statuses
   ┗ text
   ┗ id
   ┗ user
     ┗ screen_name
     ┗ followers_count
   ┗ created_at
   ┗ retweeted_status
     ┗ text
     ┗ id

サンプル

簡単なサンプルをコーディングしてみます。

ツイート取得

以下で、”沖縄旅行” を含むツイートを 10 件取得できます。

ツイート投稿

投稿は以下で OK。

◇◇◇

基本事項の説明はここまでです。次節でデータのダウンロードに取りかかります。

大量にダウンロード

ここからが本題ですが、ツイートを大量に取得するためのプログラムを作ります。一回に取得できる件数は 100 とか 200 件程度の上限があるので、ループしながら API 呼出しを繰り返すことになります。ただし、回数制限があるので、ひたすら API を呼び続けるわけにはいかず、考慮が必要です。

方針として

  • 「キーワードで取得」 と 「ユーザーを指定して取得」 の2機能を備える
  • ツイートは一件づつ yield する
  • 回数制限に到達したら、制限解除まで sleep し、解除後に再開する

ことにします。

クラスは3つ。

TweetGetter クラス図

ソース

このまま実行すれば “渋谷” をふくむツイートが 3000 件取得できます。
コメントを入れ替えれば “AbeShinzo” さんのツイートが取得できます。

オプション

collect メソッドの引数で以下の 3 点が指定可能。

  • total : 取得件数
    デフォルト -1 (無制限に取得。ただし Twitter 側の設定する上限がある)
  • onlyText : メッセージ本文だけ取得する
    デフォルト False
  • includeRetweet : リツイートを含める
    デフォルト False

エラー対策

サーバー側に起因するなんらかの理由でエラーが発生する場合があります。以下、対処法です。

Service Unavailable

status_code:503

サーバー側の処理が追いつかない場合に発生します。しばらく sleep してアクセスを再開すれば問題ありません。必要な sleep 時間はサーバーの状況によると思いますが、今まで見た範囲では 30 秒の sleep で大丈夫でした。

Too Many Requests

status_code:429

180回/3分 等の回数制限を無視してアクセスすると発生します。
X-Rate-Limit-Remaining を確認しながらアクセスすれば通常は発生しません。

正しい手順として、X-Rate-Limit-Remaining が 0 になった場合は X-Rate-Limit-Reset まで sleepします。ただし、sleep 時間が微妙にサーバーとずれる可能性があるため、アクセスを再開する前に下記のエンドポイント

https://api.twitter.com/1.1/application/rate_limit_status.json

remaining (アクセス可能回数)
reset        (アクセスが可能になるまでの時間)

を確認します。

X-Rate-Limit-Remaining が入ってない

理由は不明ですが、レスポンスに X-Rate-Limit-Remaining が入ってないことが稀にあります。そうしたケースでは、下記のエンドポイント

https://api.twitter.com/1.1/application/rate_limit_status.json

remaining (アクセス可能回数)
reset        (アクセスが可能になるまでの時間)

を確認します。

スポンサーリンク
その他の記事
    • あ、抜けてますね。しかも、ところどころ。。。

      直しました。

      2.7 で動かしてるので、すぐ括弧を忘れてしまいます。
      ありがとうございました。

  1. プログラム大変参考にさせてもらっています。
    質問なのですが
    idを取得してidの人の自己紹介文を取得することは可能ですか?

    • こんな感じでしょうか。

      ① ツイートから id を取得する
      tweet['id']

      ② id からユーザー情報を取得する(別途、リクエスト)
      url = 'https://api.twitter.com/1.1/users/show.json'
      params = {'user_id':'xxxxxxxxx'}
                   ↑
                 ①で取得したやつ

      res = session.get(url, params = params)

      ③ 自己紹介を取りだす
      t = json.loads(res.text)
      print (t['description'])

      ======================
      ちなみに

      安倍さんの自己紹介はこれで取得できました。

      url = 'https://api.twitter.com/1.1/users/show.json'
      params = {'user_id':'468122115'}
      res = session.get(url, params = params)

      t = json.loads(res.text)
      print (t['description'])

  2. 分かりやすい記事をありがとうございます。

    大量にツイートを取得する際は最近のツイートしか取得できないという使用でしょうか。

    キーワードによっては取得件数が少ししか取れないものもあるようでして

    お願いします。

    • tas さん、どうも。

      ネットの情報ですが、APIでは過去1週間のツイートしか取れないようです。
      キーワードによっては、ほとんどツイートがないケースもあるかもしれませんね。

  3. 管理人様

    回答頂きありがとうございます。
    ツイートID で過去の記事を取得するとかありますね。

    streamingAPI をずっと動かしつづけるしかないのでしょうか・・・(T_T)

  4. 管理人様
    tweetの投稿された位置情報(投稿場所情報)を同時に取得するにはどのようにすればよろしいでしょうか?

    • yuさん、こんにちわ

      coordinates という項目に経度、緯度が入るようです。
      掲載ソースの場合、こうなります。(インデントがうまく入ってませんが)

      if tweet['coordinates'] is not None:
      print (tweet['coordinates']['coordinates'][0]) # 経度
      print (tweet['coordinates']['coordinates'][1]) # 緯度

      位置情報付きでつぶやく人は少ないので、実際に coordinates に値が入るツイートも少ないようです。

  5. はじめましてこんにちは。
    自分は研究でTwitterのツイート内容の解析について勉強中なのですが、テキストに実行結果を出力する方法がいまいちわからないので教えていただけませんか?

    • SUSUMUさん、こんにちは
      テキストファイルに出力ということでしたら、こんな感じでできると思います。
      for ループのインデントが入ってませんが、そこは付け足してください(f.write の箇所)。

      import codecs

      getter = TweetsGetter.bySearch(u'渋谷')

      f = codecs.open('/tmp/tweets.txt', 'w', 'utf-8')

      for tweet in getter.collect(total=3):
      f.write('——————————————–')
      f.write('\n')
      f.write(str(tweet['id']))
      f.write('\n')
      f.write(tweet['created_at'])
      f.write('\n')
      f.write(tweet['user']['screen_name'])
      f.write('\n')
      f.write(tweet['text'])
      f.write('\n')

      f.close()

      • こちらのプログラム私も参考にさせて頂いているのですが以下のようなエラーが改善できません。
        Traceback (most recent call last):
        File "text.py", line 242, in
        f = codecs.open('/tmp/tweets.txt', 'w', 'utf-8')
        File "C:\Program Files\Python35\lib\codecs.py", line 895, in open
        file = builtins.open(filename, mode, buffering)
        FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tweets.txt'

        何かお力添え頂ければ幸いです。

  6. 管理人さま
    とてもわかりやすい記事をありがとうございます.
    当方超初心者で野暮な質問だとは重々承知なのですが
    Twitter Streaming APIとはまた別のAPIなのでしょうか?
    それとも上記APIをPHPではなくPythonで動かしてみた,とうことなのでしょうか?

    • renaco さん、こんにちわ。

      ここで紹介した TwitterAPI は過去のツイートを取得するものです。

      Twitter Streaming API は、今現在つぶやかれたツイートをリアルタイムで取得するものです。動かしっぱなしにしとけば最新ツイートがどんどん送られてきます。

      • インデントが入ってないですけど、Streaming はこんな感じで動くようです。

        import requests
        import json
        from requests_oauthlib import OAuth1

        CK = '8uYOL8qfRB—————'
        CS = 'HFIcvU2nCB—————————————-'
        AT = '104137178—————————————–'
        AS = '4ViVoEMdiH———————————–'

        url = "https://stream.twitter.com/1.1/statuses/filter.json"

        auth = OAuth1(CK, CS, AT, AS)

        r = requests.post(url, auth=auth, stream=True, data={'track' : u'渋谷'})

        for line in r.iter_lines():
        if len(line) == 0:
        continue

        tweet = json.loads(line)
        print ('—————- ')
        print ('{} {}'.format(tweet.get('created_at'), '@'+tweet.get('user').get('screen_name')))
        print (tweet.get('text'))

  7. 初めまして。
    こちらのプログラム大変参考にさせて頂いております。
    初心者の質問で申し訳ないのですが、取得したツイートを形態素解析してExcelで表にしたいと考えているのですが、こちらにそういったプログラムを組み込む事は可能でしょうか?
    もし可能であれば、方法等ご教授頂けますと幸いです。

    • aris さん、こんにちわ。

      形態素解析は janome を使うのが手軽だと思います。
      excel出力はやったことがないのでよく分かりませんが、検索すればいろいろなライブラリが出てくるようです。

      どんな表を作るのかにもよりますが、大体こんな流れではないでしょうか。

      ① tweet['text']を janome に渡して単語に分解、
      ② その結果を集計(単語のカウント等)
      ③ excel に出力

  8. お返事遅れて申し訳ありません。
    色々解説下さりありがとうございます。
    janome等調べて試行錯誤してみようと思います。
    また知恵をお貸しいただければ幸いです。

  9. わかりやすい記事で初心者の私も大変参考になっています。
    質問なのですが画像付きのツイートのみを取得し画像のidを取得するといったことは可能でしょうか?

    • ggさん、こんにちわ。

      詳細は未確認ですが、検索キーに filter:media と付け加えると画像や動画付きのツイートが取得できるようです。

      getter = TweetsGetter.bySearch(u'渋谷 filter:media')

  10. 初心者でもわかりやすい記事で参考にさせていただいており、大変勉強になります。
    お忙しいところ大変申し訳ないのですが、プログラム作成に手こずっており、自分で解決する事ができなかったため、教えていただければと思いコメントさせていただきました。
    現在、ツイートを30000件取得し、ツイート3000件ずつテキストに保存をするプログラムを組もうとしています。
    管理人様のプログラムを実行するとAPIアクセス回数は100件に2回〜3回ほどなのですが、下記のプログラムのようにif文を付け加えると極端に増えて10回以上アクセスしてしまい、ツイートを取得するのに時間がかかってしまいます。アクセス回数を減らすにはどうしたらいいでしょうか、お時間あるとき二返信くださると大変助かります。

    if __name__ == '__main__':
    # キーワードで取得
    getter = TweetsGetter.bySearch(u'リセット')
    cnt2 = 1
    cnt = 0
    cnt3 = 0
    count = 0
    f = codecs.open('tweet.txt', 'w')
    for tweet in getter.collect(total=30000):
    cnt += 1
    cnt3 += 1
    f.write('————————————-'+str(cnt))
    f.write('\n')
    f.write(tweet['text'].strip())
    f.write('\n')
    if cnt % 3000 == 0 and not(cnt3 ==30000):#3000件ごとにファイルを作る、3000件目はテキストファイルを作らない
    cnt2 += 1
    cnt = 0
    f.close
    f = codecs.open('tweet'+str(cnt2)+'.txt', 'w')#次のファイルに移るときcnt2をカウントしてファイル名にする
    elif cnt3 == 30000:
    f.close
    else:#if文実行何回で1回アクセスしているかみる
    count += 1
    print(count)
    f.close

    • ymk さん、こんにちわ。

      拝見したところ、else の処理は cnt が 1 から 2999 の場合、常に通るように見受けられます。ということは、closeしたファイルに write していることになります。

      その辺が影響して挙動がおかしくなっているのではないでしょうか。

  11. 大変わかりやすい記事をありがとうございます。
    tweetを大量に取得したいと思い、collectのtotal=500,000にしてみたところ、wait表示が表示されず、9400件ほどしか取れていないのに関わらずプログラムが実行完了してしまいます。
    mongoの方の問題だとも思ったのですが、DB名を変えても同様の動作になってしまいます。また、一週間分しかAPIでは取れないからかとも考えたのですがtotal=50,000だと問題なく動くのでこれも原因ではないと思いました。どうしたら挙動が改善するのかお時間のある時にでもアドバイスをいただけると嬉しいです。

    • さく さん、こんにちわ。

      もしエラー表示がない状態で終了したのならデータがそれだけしかなかったのだと思いますが ??? ちょっと判りかねます ???

      • 管理人さま
        早速のご返信ありがとうございました。
        また、別件なのですがcollectの値を指定しなければ一週間分の全データを取得することは可能でしょうか?
        度々質問をしてしまい、申し訳ありません。

  12. 初めまして。
    現在独学でpythonのスクレイピングを学んでいたところでしたので、
    掲載頂いた記事がとても分かりやすく非常に感謝しております。
    「大量にダウンロード」の章で記載いただいたプログラムを自分で作成してみたところ、
    以下のエラーがどうしても消えず、困っています。。
    requests.exceptions.MissingSchema: Invalid URL 'https//api.twitter.com/1.1/application/rate_limit_status.json': No schema supplied. Perhaps you meant http://https//api.twitter.com/1.1/application/rate_limit_status.json?

    URLが有効ではないエラーなのかなと思っているのですが、
    どのように改善したら良いか私の方では分からない状況でして、、
    エラーの回避方法についてもし何かお分かりでしたらご教授いただけますと大変助かります!

  13. こんにちは。
    python学習中のため、いつも参考にしております。
    一つお聞きしたいのですが、このプログラムに「リツイート数」、「いいね数」
    を実装することは可能でしょうか。

    • まさおかさん、こんにちわ

      twitter の以下サイトにAPI検索で取得可能なデータが載っています。

      ttps://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets

      「Example Response」に記載があれば、取り出すことができます。

コメントはお気軽に