C#でtwitterizerをつかってみた3 TwtterAPIのアクセス回数を削減する方法[.NET][C#][twitterizer][twitterAPI][Tips]

■TwitterAPIの制限
TwitterAPIには「1時間に150回まで」の制限があります。
なので、定期的につぶやくbotなどではそこまで困りませんが、
ちょっと工夫して、ユーザ画像や過去のつぶやき、フォロワー、フォローしている人(のことを英語ではFriendと言うらしい。一方的な関係なのになんか変…)の情報を取得しているうちに、すぐ上限に達してしまったりします。

前回の日記では、そういう工夫はせずに、書いていましたが、
今回は、これをできるだけ避けるような、アクセス回数を節約する方法を実装してみました。


■制限の節約方法
解決法は、まず誰でも思い付きそうなのが、「一度取った情報は退避する」てことです。
文章で説明するよりも、図で説明した方が早いでしょう。
ようするに、こういうことです。

ご利用は計画的に、てことですね。


今回は、一番よく使われると思われる、ツイッターのユーザ情報もろもろを取得するTwitterUser.Show()メソッドだけに注目して、Dictionaryクラスに格納してみます。


なお、ユーザ情報は10進数のId(_twitterUser.Id、Decimal型)から取得されるか、
もしくはTwitterID(_twitterUser.Name、string型)から取得されるかわからないので、
ちょっとメモリ的には冗長ですが、両方用意しました。
こうすることで、同じユーザ情報を取得する場合は、どちらから先に呼びだされても、
API呼びだし回数を1回に抑えることができます。


具体的には、一度呼びだしたユーザ情報TwitterUserクラスのインスタンス(_twitterUser)を、Dictionary[Key=TwitterID][Value=_twitterUser]
Dictionary[Key=10進数のId][Value=_twitterUser]
に格納しました。


サンプルソース
C#ソースコードを一部載せておきます。

using System;
using System.Collections.Generic;
using System.Text;

// Twitterizerライブラリを使っている
using Twitterizer;

// Windows.Forms依存にしたくない時はコメントアウト+修正
using System.Windows.Forms;
using System.Drawing;
using System.Net;
using System.IO; 

namespace everyoneCanCreate.Twitter
{
    public class MyTwitterTools
    {
      //…
   //他の処理
   //…

        #region ユーザ情報などを辞書に登録する処理: getTwitterUser()など、ほとんどのメソッドで共通して使われる処理なので、TwitterAPIアクセス回数を節約するために、少し工夫している
        /// <summary>
        /// (1時間に150アクセスまでという)TwitterAPIアクセス回数節約のために、一度読み込んだデータを辞書に格納して使うか。
        /// trueだと処理はほんの少し重たくなるかもしれないが節約。falseだと余計なことはせずAPIをじゃんじゃん使う。
        /// 
        /// ※現時点では、trueにしないとすぐ1時間150回をすぐ使いきっちゃうので、trueにしている。
        /// </summary>
        public static bool p_isUseDictionary・TwitterAPIアクセス回数削減のために辞書を使うか = true;
        /// <summary>
        /// 一度getTwitterUser・ユーザ情報を取得(@からはじまるtwitterID)で読み込んだTwitterUserクラスのインスタンスを格納している辞書です。
        /// 
        /// (1時間に150アクセスまでという)TwitterAPI実行数節約のために、一度読み込んだユーザのデータを取得するときに使用します。
        /// </summary>
        private static Dictionary<string, TwitterUser> p_Users_KeyTwitterID・今まで取得したユーザの辞書 = new Dictionary<string, TwitterUser>();
        /// <summary>
        /// 一度getTwitterUser・ユーザ情報を取得(TwitterUser.Idのユーザ識別子)で読み込んだTwitterUserクラスのインスタンスを格納している辞書です。
        /// 
        /// (1時間に150アクセスまでという)TwitterAPI実行数節約のために、一度読み込んだユーザのデータを取得するときに使用します。
        /// </summary>
        private static Dictionary<decimal, TwitterUser> p_Users_KeyDecimalId・今まで取得したユーザの辞書 = new Dictionary<decimal, TwitterUser>();

        /// <summary>
        /// ツイッターID(@で始まる英数字記号の文字列)からユーザ情報TwitterUser型のインスタンスを取得します。失敗した場合は、nullが入ります。
        /// 
        /// ※このクラス内では、twitterAPI実行数を節約するため、getUser・ユーザ情報を取得(..)の代わりに呼びだしています。
        /// </summary>
        /// <returns></returns>
        public static TwitterUser getUser・ユーザ情報を取得(string _twitterID)
        {
            if (p_isUseDictionary・TwitterAPIアクセス回数削減のために辞書を使うか == true)
            {
                // APIを呼びだす前に、既に辞書に格納されていないかを確認
                if (p_Users_KeyTwitterID・今まで取得したユーザの辞書.ContainsKey(_twitterID) == true)
                {
                    // これでAPI呼びだす回数を節約できる。
                    return p_Users_KeyTwitterID・今まで取得したユーザの辞書[_twitterID];
                }
            }
            // 普通にAPIを呼びだして取得
            TwitterUser _user = null;
            TwitterResponse<TwitterUser> _res = TwitterUser.Show(_twitterID); // これでも150回のTwitterAPIアクセス回数は消費するから注意して!
            if (_res.Result == RequestResult.Success)
            {
                _user = _res.ResponseObject;
            }
            else if (_res.Result == RequestResult.Unknown)
            {
                checkError・エラー処理("TwitterID「" + _twitterID + "」のユーザが見つかりませんでした。", _res.ErrorMessage);
            }
            else
            {
                checkError・エラー処理("ツイッターIDからユーザ情報(TwitterUser)取得時のエラーです、", _res.ErrorMessage);
            }
            // 一度取得したユーザ情報を辞書に登録(辞書を使う場合だけ)
            if (p_isUseDictionary・TwitterAPIアクセス回数削減のために辞書を使うか == true)
            {
                if (_user != null)
                {
                    // それぞれの辞書に、登録されていなければ、新しくユーザ情報を取得
                    // こちらは確実に登録されていない
                    p_Users_KeyTwitterID・今まで取得したユーザの辞書.Add(_twitterID, _user);
                    // こちらは登録されているかわからないので確認
                    decimal _decimalId = _user.Id;
                    if (p_Users_KeyDecimalId・今まで取得したユーザの辞書.ContainsKey(_decimalId) == false)
                    {
                        p_Users_KeyDecimalId・今まで取得したユーザの辞書.Add(_decimalId, _user);
                    }

                }
            }
            return _user;

        }
        /// <summary>
        /// TwitterUser.Idのユーザ識別子(Decimal型のユーザ識別子)からユーザ情報TwitterUser型のインスタンスを取得します。失敗した場合は、nullが入ります。
        /// 
        /// ※このクラス内では、twitterAPI実行数を節約するため、getUser・ユーザ情報を取得(..)の代わりに呼びだしています。
        /// </summary>
        /// <returns></returns>
        public static TwitterUser getUser・ユーザ情報を取得(decimal _TwitterUser_Id_decimal)
        {
            if (p_isUseDictionary・TwitterAPIアクセス回数削減のために辞書を使うか == true)
            {
                // APIを呼びだす前に、既に辞書に格納されていないかを確認
                if (p_Users_KeyDecimalId・今まで取得したユーザの辞書.ContainsKey(_TwitterUser_Id_decimal) == true)
                {
                    // これでAPI呼びだす回数を節約できる。
                    return p_Users_KeyDecimalId・今まで取得したユーザの辞書[_TwitterUser_Id_decimal];
                }
            }
            // 普通にAPIを呼びだして取得
            TwitterUser _user = null;
            TwitterResponse<TwitterUser> _res = TwitterUser.Show(_TwitterUser_Id_decimal);
            if (_res.Result == RequestResult.Success)
            {
                _user = _res.ResponseObject;
                return _user;
            }
            else if (_res.Result == RequestResult.Unknown)
            {
                checkError・エラー処理("Decimal型のユーザ識別子「" + _TwitterUser_Id_decimal + "」のユーザが見つかりませんでした。", _res.ErrorMessage);
            }
            else
            {
                checkError・エラー処理("ユーザ識別子からユーザ識別子取得時のエラーです、", _res.ErrorMessage);
            }
            // 一度取得したユーザ情報を辞書に登録(辞書を使う場合だけ)
            if (p_isUseDictionary・TwitterAPIアクセス回数削減のために辞書を使うか == true)
            {
                if (_user != null)
                {
                    // それぞれの辞書に、登録されていなければ、新しくユーザ情報を取得
                    // こちらは確実に登録されていない
                    p_Users_KeyDecimalId・今まで取得したユーザの辞書.Add(_TwitterUser_Id_decimal, _user);
                    // こちらは登録されているかわからないので確認
                    string _twitterID = _user.Name;
                    if (p_Users_KeyTwitterID・今まで取得したユーザの辞書.ContainsKey(_twitterID) == false)
                    {
                        p_Users_KeyTwitterID・今まで取得したユーザの辞書.Add(_twitterID, _user);
                    }

                }
            }
            return null;

        }
        #endregion


        /// <summary>
        /// このクラスのエラー処理を共通して管理するメソッドです。
        /// 
        /// TwitterResponse.Result != Success だったとき、一貫してこのメソッドを呼び出してください。
        /// そうすると、エラー処理の個所をひとつにまとめられます。
        /// </summary>
        /// <param name="_ShownMessage・表示したいメッセージ"></param>
        /// <param name="_details・詳細エラーメッセージ_TwitterResponse_ErrorMessage"></param>
        /// <returns></returns>
        public static bool checkError・エラー処理(string _ShownMessage・表示したいメッセージ, string _details・詳細エラーメッセージ_TwitterResponse_ErrorMessage)
        {
            MessageBox.Show("【エラー】" + _ShownMessage・表示したいメッセージ + "\n\n詳細:" + _details・詳細エラーメッセージ_TwitterResponse_ErrorMessage, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);

            // 以下、エラーの内容によって、よくはまる事例をまとめてくれているサイト。感謝。 http://oresta.blog66.fc2.com/blog-entry-22.html

            // よくある、"Status is a duplicate."エラーは重複。Twitterでは同じ発言を繰り返し投稿できません。少なくとも10ツイートはあける必要があります。 http://www26.atwiki.jp/easybotter_wiki/pages/21.html 

            return true;
        }

以上です。



■使用例
このメソッドを使えば、例えば、同じユーザ情報を150回取得しても、
TwitterAPIアクセスが止められることはありません。
普通のTwitterUser.Show()を呼びだした時と比べてみます。

        public static void HELP・このクラスの使い方のヘルプ()
        {
            string _message = "テストのつぶやき";
            string _info = "";

            MyTwitterTools.tweet・つぶやく(_message);
            System.Console.WriteLine(_info);

            // 同じユーザ情報を150回ずつ取得
            string _twitterID = "merusaia"; // テストする時は、5000以下のフォロワーの人にしようね
            TwitterUser _user;
            // (a)節約する場合
            for (int i = 0; i < 151; i++) // 問題無く、ポケモンゲットだぜ!
            {
                 _user = MyTwitterTools.getUser・ユーザ情報を取得(_twitterID);
                 _info = i + "回目のMyTwitterTools.getUser・ユーザ情報を取得(_twitterID)";
                 System.Console.WriteLine(_info);
            }
            // ただし、p_isUseDictionaryをtrueにしてたらだからね。falseにしてたら(b)と一緒だよ

            // (b)節約しない場合
            var _res = new TwitterResponse<TwitterUser>();
            for (int i = 0; i < 151; i++) // 問題ありあり、アクセス禁止ゲットだぜ!
            {
                _res = TwitterUser.Show(_twitterID);
                _info = i + "回目のTwitterUser.Show(_twitterID)";
                System.Console.WriteLine(_info);
                // どっかで止まる。
            }
            // TwitterAPIの制限(1時間に150回)の1回には、TwitterUser.Showが含まれている
            // Rate limit exceed.が出たら1時間、実験できないからね。実行する時は気を付けて。

            System.Windows.Forms.Application.Exit();

        }

※はてなダイアリーでは、ソースの色付き引用は、コピペしたソースを「>|cs|」〜「||<」で囲むだけでいいんですね。いやぁ、便利な機能ですね。


(a)節約する場合では、1時間に何回実行してもポケモンゲッ…ユーザ情報をゲットできますが、
(B)節約しない場合では、必ずアクセス上限(Rate limit exceed.から始まるメッセ―ジ) が表示されます。


この他にも、例えばフォロワーを取得するTwitterFriendShip.Show()を呼び出す時に、内部にgetUserを使えば、二回目以降にフォロワーを取る場合は、使い回しができたりします。
フォロワーを意識したbotを作りたい人は、試してみるといいかもです。
(ただし、リアルタイムなフォロワーの更新や通知はできませんが。Stream系クラスを使えばできるらしいです。)


ツイッタークライアント等を作っているもっと詳しい方には、「そんなのStreamを使えばいいじゃん」とか言われそうですが、
Streaming系のクラスは結構使い方がややこしく、またAPI仕様変更も頻繁だと聞くので、
(仕様変更が怖くて)あまり手を出せていません^^;。


できれば、開発者にもTwitterAPIにも優しい、地球にやさしいプログラムを作っていきたいものですね。


■MyTwitterTools.cs
なお、これを意識して、twitterizerを日本語風にわかりやすく(?)ラッピングしたクラス「MyTwitterTools.cs」、だいぶ変わりました。
ソース全文をダウンロードしたい方は以下からお気軽にどうぞ。
■dropbox.com/home/Public/minRPG_みんつくプロジェクト 
https://www.dropbox.com/home/Public/minRPG_%E3%81%BF%E3%82%93%E3%81%A4%E3%81%8F%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88
(※VisualStudio2010用なので、文字コードはUTF-8です)。
上記URL → ソースコード → 最新日付 → 「***.zip」をダウンロード&解凍
 → その中の「minRPG/_NET4_0Program_Twitterアプリ/NET4_0_Toolsプログラミングツール系」
   というフォルダの中に、「MyTwitterTools.cs」が入っています。(.NET Framework4.0が必要)
   なお、動かしたい場合は「MyTools.cs」というファイルも必要です。(このファイルのメソッドを使っているため)


あとはプロジェクトに、twitterizer***.dllをNuGetを使って、自動的に参照するだけですね。

「twitterizerてなによ?」「NuGetって何?」て方は、この記事などを参考にしてください。
(NuGet、便利ですよね。。私も最近知りました…><)
http://mimumimu.net/blog/2011/11/22/nuget-%E3%81%A7%E7%B0%A1%E5%8D%98%E3%81%AB%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%82%92%E5%B0%8E%E5%85%A5%E3%81%99%E3%82%8B%E3%80%82/


※ここの内容は、全てパブリックドメインとし、著作権を放棄します。ご自由にコピー・改変してお使いください。