#title(Twitter Streaming APIを使う 2)

#navi(.NETプログラミング研究)

#contents

*Twitter Streaming APIを使う 2 [#sb640823]

[[前回>../96]]に続いて、Twitter Streaming APIです。前回も書きましたが、私はツイッターについての知識がほとんどないということをご了承ください。

**statuses/filterメソッドを使う [#kd0c99e7]

statuses/filterメソッドを使用すると、指定したキーワードを含むパブリックステータスだけを送ってもらうことができます。キーワードだけでなく、ユーザーや場所を指定することもできます。

「[[Streaming API>http://apiwiki.twitter.com/Streaming-API-Documentation]]」によると、statuses/filterメソッドはPOSTでデータを送信することになっていますが、GETでも大丈夫のようです(詳しく調べたわけではありませんが)。

***trackパラメータ [#sae4aba1]

キーワードを指定するには、trackパラメータを使います。キーワードは大文字小文字を区別しません。また、キーワードは1〜60バイトである必要があります。スペースの入ったキーワードは指定できませんし、UTF-8のキーワードもうまくいかないそうです。

例えば「Twitter」をtrackパラメータに指定したときは、textフィールド内の「TWITTER」、「twitter」、「"Twitter"」、「twitter.」、「#twitter」、「@twitter」などにマッチし、「TwitterTracker」や「http://www.twitter.com」にはマッチしないということです。

trackパラメータに複数のキーワードを指定する場合は、それぞれのキーワードをコンマ(,)区切りにします。複数のキーワードが指定された場合は、どれか1つでも含まれていればマッチしたことになります。例えば、パラメータを「track=basketball,football,baseball」とすると、「basketball」、「football」、「baseball」のいずれかのキーワードを含むステータスが送られてきます。キーワードは最大で200個指定できます。

***followパラメータ [#x97c71a5]

ユーザーを指定するには、followパラメータを使います。followパラメータには、ユーザーのIDを指定します。マッチするステータスは、そのユーザーが作成したか、そのユーザーがReTweetしたか、そのユーザーが作成したものを誰かがReTweetしたか、そのユーザーが作成したステータスへの返信(in-reply-to)かのどれかです。ただし、ReTweetボタンや返信(reply "swoosh")ボタンを押さないで作成されたステータスにはマッチしない(「@user」や「RT @user」などと書かれたステータスでも)ということです。

followパラメータもtrackパラメータと同様に、コンマ区切りで複数のIDを指定可能です。最大で400個指定できます。

***locationsパラメータ [#e133dcf3]

指定した場所から投稿されたステータスだけを送ってもらうには、locationsパラメータを使います。これは、その場所の範囲を南西と北東の経度緯度をコンマで区切った文字列で指定します(つまり範囲は、矩形(box)っぽくなります)。例えば、サンフランシスコは「locations=-122.75,36.8,-121.75,37.8」となるそうです。

locationsパラメータもコンマ区切りで複数指定可能で、例えばサンフランシスコとニューヨークであれば、「locations=-122.75,36.8,-121.75,37.8,-74,40,-73,41」となります。locationsパラメータの範囲は、1辺が1度以下でなければならず、最大で10個まで指定できます。

trackとlocationsパラメータは、あまりに広い範囲を指定すると、すべてのステータスを返せなくなり、制限された状態になります。制限状態が終了すると、制限メッセージ([[前号>../96]]で紹介したlimitオブジェクトのことのようです)と、マッチしたすべてのステータスがもう一度送られます。

なおtrackとlocationsパラメータの両方を指定すると、どちらかにマッチしたステータスが送られます。

***サンプルコード [#h873005a]

以下の例では、「2010wc」というキーワードを含むステータスを受信しています。

#code(vbnet){{
Imports System.IO
Imports System.Net
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Xml

Module TwitterStreamingApiTest
    Sub Main()
        'HttpWebRequestを作成する
        Dim webreq As HttpWebRequest = DirectCast(System.Net.WebRequest.Create( _
            "http://stream.twitter.com/1/statuses/filter.xml"), HttpWebRequest)
        'ユーザー名とパスワードを設定する
        Dim nc As New NetworkCredential("username", "password")
        webreq.Credentials = nc

        'POSTとしてデータを送信する
        webreq.Method = "POST"
        webreq.ContentType = "application/x-www-form-urlencoded"
        '417 Expectation Failed が返されないように
        webreq.ServicePoint.Expect100Continue = False
        'trackパラメータを指定する
        Dim postStr As String = "track=2010wc"
        Dim postData As Byte() = Encoding.UTF8.GetBytes(postStr)
        webreq.ContentLength = postData.Length
        'データを送信する
        Dim reqStrm As Stream = webreq.GetRequestStream()
        reqStrm.Write(postData, 0, postData.Length)
        reqStrm.Close()

        'サーバーからの応答を受信するためのHttpWebResponseを取得する
        Dim webres As HttpWebResponse = DirectCast(webreq.GetResponse(), HttpWebResponse)

        '応答データを受信するためのStreamを取得する
        Dim st As Stream = webres.GetResponseStream()
        Dim sr As New StreamReader(st)

        'XMLを切り出すための正規表現
        Dim xmlReg As New Regex( _
            "<\?xml\s[^>]+>\s*<(?<root>[^\s>]+)[^>]*>(?:.+?)</\k<root>>", _
            RegexOptions.Singleline Or RegexOptions.Compiled)
        '一時的にデータを保存しておくバッファ
        Dim buffer As New StringBuilder()

        '10秒間だけデータを取得する
        Dim startTime As DateTime = DateTime.Now
        While (DateTime.Now - startTime).TotalSeconds < 10
            Dim line As String = sr.ReadLine()
            If line Is Nothing Then
                Exit While
            End If
            If line = "" Then
                Continue While
            End If

            'バッファに追加
            buffer.AppendLine(line)

            '正規表現と一致するか調べる
            Dim m As Match = xmlReg.Match(buffer.ToString())
            If m.Success Then
                'XmlDocumentに読み込む
                Dim xmlDoc As New XmlDocument()
                xmlDoc.LoadXml(m.Value)
                'ルートを取得
                Dim rootElm As XmlElement = xmlDoc.DocumentElement
                '情報の表示
                If rootElm.Name = "status" Then
                    'statusの情報(textとuserのscreen_name)を表示する
                    Console.WriteLine("[{1}] {0}", _
                        rootElm("text").InnerText, _
                        rootElm("user")("screen_name").InnerText)
                End If
                'バッファをクリア
                buffer.Length = 0
            End If
        End While

        'こちらから接続を閉じる
        webreq.Abort()
        webres.Close()
        sr.Close()

        Console.WriteLine("----- 終了しました! -----")
        Console.ReadLine()
    End Sub
End Module
}}

#code(csharp){{
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

class TwitterStreamingApiTest
{
    static void Main()
    {
        //HttpWebRequestを作成する
        HttpWebRequest webreq = (HttpWebRequest)System.Net.WebRequest.Create(
            "http://stream.twitter.com/1/statuses/filter.xml");
        //ユーザー名とパスワードを設定する
        NetworkCredential nc = new NetworkCredential("username", "password");
        webreq.Credentials = nc;

        //POSTとしてデータを送信する
        webreq.Method = "POST";
        webreq.ContentType = "application/x-www-form-urlencoded";
        //417 Expectation Failed が返されないように
        webreq.ServicePoint.Expect100Continue = false;
        //trackパラメータを指定する
        string postStr = "track=2010wc";
        byte[] postData = Encoding.UTF8.GetBytes(postStr);
        webreq.ContentLength = postData.Length;
        //データを送信する
        Stream reqStrm = webreq.GetRequestStream();
        reqStrm.Write(postData, 0, postData.Length);
        reqStrm.Close();

        //サーバーからの応答を受信するためのHttpWebResponseを取得する
        HttpWebResponse webres = (HttpWebResponse)webreq.GetResponse();

        //応答データを受信するためのStreamを取得する
        Stream st = webres.GetResponseStream();
        StreamReader sr = new StreamReader(st);

        //XMLを切り出すための正規表現
        Regex xmlReg = new Regex(
            @"<\?xml\s[^>]+>\s*<(?<root>[^\s>]+)[^>]*>(?:.+?)</\k<root>>",
            RegexOptions.Singleline | RegexOptions.Compiled);
        //一時的にデータを保存しておくバッファ
        StringBuilder buffer = new StringBuilder();

        //10秒間だけデータを取得する
        DateTime startTime = DateTime.Now;
        while ((DateTime.Now - startTime).TotalSeconds < 10)
        {
            string line = sr.ReadLine();
            if (line == null)
                break;
            if (line == "")
                continue;

            //バッファに追加
            buffer.AppendLine(line);

            //正規表現と一致するか調べる
            Match m = xmlReg.Match(buffer.ToString());
            if (m.Success)
            {
                //XmlDocumentに読み込む
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(m.Value);
                //ルートを取得
                XmlElement rootElm = xmlDoc.DocumentElement;
                //情報の表示
                if (rootElm.Name == "status")
                {
                    //statusの情報(textとuserのscreen_name)を表示する
                    Console.WriteLine("[{1}] {0}",
                        rootElm["text"].InnerText,
                        rootElm["user"]["screen_name"].InnerText);
                }
                //バッファをクリア
                buffer.Length = 0;
            }
        }

        //こちらから接続を閉じる
        webreq.Abort();
        webres.Close();
        sr.Close();

        Console.WriteLine("----- 終了しました! -----");
        Console.ReadLine();
    }
}
}}

**JSONを解析する [#b3cb866c]

[[Twitter Streaming APIのドキュメント>http://apiwiki.twitter.com/Streaming-API-Documentation]]では、JSONの使用をかなり強く勧めています。それにも関わらず、XMLを使うというのはちょっと申し訳ない気がします。そこで、JSONを解析する方法を考えます。

.NET FrameworkでJSONを解析するとなると、.NET Framework 3.5以降であれば、JavaScriptSerializer((.NET 3.5では「古い形式です」という警告がでるが、SP1からはなくなった))(ASP.NET 2.0 AJAX Extensionsでも可)やDataContractJsonSerializerクラスを使うことで比較的簡単にできます。そうでなければ、自分で解析するか、[[JSON>http://json.org/json-ja.html]]で紹介されているようなライブラリを使用すれば良いでしょう。

***JavaScriptSerializerを使う [#l6cce571]

以下に、JavaScriptSerializerを使用した例を示します。このコードを実行するには、System.Web.Extensionsを参照設定に追加してください。ここでは手抜きでデータをDictionaryに変換していますが、専用のクラスを作った方が良いでしょう。また、statusオブジェクトなのかを判断する方法がわかりませんでしたので、とりあえずtextフィールドがあればstatusと判断するようにしました。

#code(vbnet){{
Imports System.IO
Imports System.Net
Imports System.Collections.Generic

Module TwitterStreamingApiTest
    Sub Main()
        'HttpWebRequestの作成
        Dim webreq As HttpWebRequest = DirectCast(System.Net.WebRequest.Create( _
            "http://stream.twitter.com/1/statuses/sample.json"), HttpWebRequest)
        Dim nc As New NetworkCredential("username", "password")
        webreq.Credentials = nc

        'サーバーからの応答を受信するためのHttpWebResponseを取得
        Dim webres As HttpWebResponse = DirectCast(webreq.GetResponse(), HttpWebResponse)

        '応答データを受信するためのStreamを取得
        Dim st As Stream = webres.GetResponseStream()
        Dim sr As New StreamReader(st)

        Dim startTime As DateTime = DateTime.Now
        While (DateTime.Now - startTime).TotalSeconds < 10
            Dim line As String = sr.ReadLine()
            If line Is Nothing Then
                Exit While
            End If
            If line = "" Then
                Continue While
            End If

            'JSONのデータをDictionaryに変換する
            Dim serializer As New System.Web.Script.Serialization.JavaScriptSerializer()
            Dim status As Dictionary(Of String, Object) = _
                serializer.Deserialize(Of Dictionary(Of String, Object))(line)

            If status.ContainsKey("text") Then
                '"text"があればstatusと判断する
                Dim user As Dictionary(Of String, Object) = _
                    DirectCast(status("user"), Dictionary(Of String, Object))
                Console.WriteLine("[{1}] {0}", status("text"), user("name"))
            ElseIf status.ContainsKey("delete") Then
                Console.WriteLine("[DELETE]")
            End If
        End While

        '閉じる
        webreq.Abort()
        webres.Close()
        sr.Close()

        Console.WriteLine("----- 終了しました! -----")
        Console.ReadLine()
    End Sub
End Module
}}

#code(csharp){{
using System;
using System.IO;
using System.Net;
using System.Collections.Generic;

class TwitterStreamingApiTest
{
    static void Main()
    {
        //HttpWebRequestの作成
        HttpWebRequest webreq = (HttpWebRequest)System.Net.WebRequest.Create(
            "http://stream.twitter.com/1/statuses/sample.json");
        NetworkCredential nc = new NetworkCredential("username", "password");
        webreq.Credentials = nc;

        //サーバーからの応答を受信するためのHttpWebResponseを取得
        HttpWebResponse webres = (HttpWebResponse)webreq.GetResponse();

        //応答データを受信するためのStreamを取得
        Stream st = webres.GetResponseStream();
        StreamReader sr = new StreamReader(st);

        DateTime startTime = DateTime.Now;
        while ((DateTime.Now - startTime).TotalSeconds < 10)
        {
            string line = sr.ReadLine();
            if (line == null)
                break;
            if (line == "")
                continue;

            //JSONのデータをDictionaryに変換する
            System.Web.Script.Serialization.JavaScriptSerializer serializer =
                new System.Web.Script.Serialization.JavaScriptSerializer();
            Dictionary<string, object> status =
                serializer.Deserialize<Dictionary<string, object>>(line);

            if (status.ContainsKey("text"))
            {
                //"text"があればstatusと判断する
                Dictionary<string, object> user =
                    (Dictionary<string, object>)status["user"];
                Console.WriteLine("[{1}] {0}", status["text"], user["name"]);
            }
            else if (status.ContainsKey("delete"))
            {
                Console.WriteLine("[DELETE]");
            }
        }

        //閉じる
        webreq.Abort();
        webres.Close();
        sr.Close();

        Console.WriteLine("----- 終了しました! -----");
        Console.ReadLine();
    }
}
}}

***Json.NETを使う [#xc8e0128]

JSONを扱うフリーのライブラリとしては、[[Json.NET>http://json.codeplex.com/]](MITライセンス)が有名です。Json.NETは.NET Framework 2.0以降で使用できますが、Json.NETの機能の1つである「LINQ to JSON」は.NET Framework 3.5以降でなければ使用できません。

以下にJson.NETのLINQ to JSONを使った例を紹介します。なお.NET Framework 2.0以降であれば、JsonConvert.DeserializeXmlNodeメソッドを使ってXmlDocumentに変換するといった方法があります。

#code(vbnet){{
Imports System.IO
Imports System.Net

Module TwitterStreamingApiTest
    Sub Main()
        'HttpWebRequestの作成
        Dim webreq As HttpWebRequest = DirectCast(System.Net.WebRequest.Create( _
            "http://stream.twitter.com/1/statuses/sample.json"), HttpWebRequest)
        Dim nc As New NetworkCredential("username", "password")
        webreq.Credentials = nc

        'サーバーからの応答を受信するためのHttpWebResponseを取得
        Dim webres As HttpWebResponse = DirectCast(webreq.GetResponse(), HttpWebResponse)

        '応答データを受信するためのStreamを取得
        Dim st As Stream = webres.GetResponseStream()
        Dim sr As New StreamReader(st)

        Dim startTime As DateTime = DateTime.Now
        While (DateTime.Now - startTime).TotalSeconds < 10
            Dim line As String = sr.ReadLine()
            If line Is Nothing Then
                Exit While
            End If
            If line = "" Then
                Continue While
            End If

            'JSONをParse
            Dim json As Newtonsoft.Json.Linq.JObject = _
                Newtonsoft.Json.Linq.JObject.Parse(line)
            Dim token As Newtonsoft.Json.Linq.JToken = Nothing
            If json.TryGetValue("text", token) Then
                '"text"があればstatusと判断する
                'Stringにキャストしないと、前後に"が入る
                Console.WriteLine("[{1}] {0}", _
                    CType(json("text"), String), _
                    CType(json("user")("name"), String))
            ElseIf json.TryGetValue("delete", token) Then
                Console.WriteLine("[DELETE] {0}", json("delete")("status")("id"))
            End If
        End While

        '閉じる
        webreq.Abort()
        webres.Close()
        sr.Close()

        Console.WriteLine("----- 終了しました! -----")
        Console.ReadLine()
    End Sub
End Module
}}

#code(csharp){{
using System;
using System.IO;
using System.Net;

class TwitterStreamingApiTest
{
    static void Main()
    {
        //HttpWebRequestの作成
        HttpWebRequest webreq = (HttpWebRequest)System.Net.WebRequest.Create(
            "http://stream.twitter.com/1/statuses/sample.json");
        NetworkCredential nc = new NetworkCredential("username", "password");
        webreq.Credentials = nc;

        //サーバーからの応答を受信するためのHttpWebResponseを取得
        HttpWebResponse webres = (HttpWebResponse)webreq.GetResponse();

        //応答データを受信するためのStreamを取得
        Stream st = webres.GetResponseStream();
        StreamReader sr = new StreamReader(st);

        DateTime startTime = DateTime.Now;
        while ((DateTime.Now - startTime).TotalSeconds < 10)
        {
            string line = sr.ReadLine();
            if (line == null)
                break;
            if (line == "")
                continue;

            //JSONをParse
            Newtonsoft.Json.Linq.JObject json = Newtonsoft.Json.Linq.JObject.Parse(line);
            Newtonsoft.Json.Linq.JToken token;
            if (json.TryGetValue("text", out token))
            {
                //"text"があればstatusと判断する
                //Stringにキャストしないと、前後に"が入る
                Console.WriteLine("[{1}] {0}",
                    (string)json["text"], (string)json["user"]["name"]);
            }
            else if (json.TryGetValue("delete", out token))
            {
                Console.WriteLine("[DELETE] {0}", json["delete"]["status"]["id"]);
            }
        }

        //閉じる
        webreq.Abort();
        webres.Close();
        sr.Close();

        Console.WriteLine("----- 終了しました! -----");
        Console.ReadLine();
    }
}
}}

***補足 : .NET Framework 1.1以前の場合 [#v504b851]

[[JSON>http://json.org/json-ja.html]]で紹介されているC#のクラスの内、.NET Framework 1.1以前に対応しているのは、「[[How do I write my own parser?>http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html]]で紹介されているクラスだけのようです。このクラスを使用してみたところ、コードポイントをUnicode文字に変換する箇所でエラーが発生することがありました。

JSON.csの277行にある

#code(csharp){{
s.Append(Char.ConvertFromUtf32((int)codePoint));
}}



#code(csharp){{
s.Append(Encoding.UTF32.GetString(BitConverter.GetBytes(codePoint)));
}}

に変更することで、このエラーは出なくなりました。

**コメント [#zfa79d0f]
#comment

//これより下は編集しないでください
#pageinfo([[:Category/.NET]],2010-06-21 (月) 01:23:58,DOBON!,2010-06-21 (月) 01:23:58,DOBON!)

[ トップ ]   [ 編集 | 差分 | バックアップ | 添付 | 複製 | 名前変更 | リロード ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]