• 追加された行はこの色です。
  • 削除された行はこの色です。
#title(Twitter Streaming APIを使う 1)

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

#contents

*Twitter Streaming APIを使う 1 [#y5aa919d]

恥ずかしながら、私はツイッターをやっていません。しかし[[Twitter API Wiki>http://apiwiki.twitter.com/]]を見ていると「[[Streaming API>http://apiwiki.twitter.com/Streaming-API-Documentation]]」というものが目に入り、読んでみるとなかなか面白そうなので、早速試してみることにしました。今回はTwitter Streaming APIについて書かせていただきます。

私はツイッターの知識がほとんどないにもかかわらずこのような記事を書いていますので、ツイッター機能や用語に関してかなり間抜けなことを書いているかもしれないことをお許しください。

なおTwitter APIを使用する場合は、必ずご自分でドキュメントに目を通し、利用規約等をご確認ください。

**Twitter Streaming APIとは? [#ybda66ec]

ツイッターに投稿された発言(ステータス)をいち早くチェックしたい場合、通常のWebサービスを使用すると、定期的にサーバーに接続し、データを送受信する必要があります。しかしStreaming APIを使用した場合は、一度サーバーに接続すれば、必要なときに勝手にサーバーがデータを送ってきてくれます。そのため、Streaming APIを使用すれば、通常のWebサービスを使用する時よりも、よりリアルタイムに近い素早さで、さらにより少ないデータのやり取りで、情報を得ることが可能になります。

**とりあえずやってみる [#na5bdb15]

Twitter Streaming APIの使い方は非常に簡単で、HTTPで通常通りに接続するだけです。Twitterのサーバーに一度接続すると、基本的にはサーバーから切断することはなく((同じクライアントが重複してログインした時や、Twitterのネットワークの保守などにより、切断されることがあります))、繋ぎっぱなしになります。そして、適当なタイミングでデータが送られてきます。

よって、「[[ファイルをダウンロードし表示する>http://dobon.net/vb/dotnet/internet/webclientopenread.html]]」や「[[WebRequest、WebResponseクラスを使ってファイルをダウンロードし表示する>http://dobon.net/vb/dotnet/internet/webrequest.html]]」で紹介している方法が使えます。ただしこれらの例ではReadToEndメソッドを使って読み込んでいますが、サーバーはデータを送り続けていますので、このままではダメです。少しずつデータを読み込む必要があります。
よって、「[[ファイルをダウンロードし表示する>https://dobon.net/vb/dotnet/internet/webclientopenread.html]]」や「[[WebRequest、WebResponseクラスを使ってファイルをダウンロードし表示する>https://dobon.net/vb/dotnet/internet/webrequest.html]]」で紹介している方法が使えます。ただしこれらの例ではReadToEndメソッドを使って読み込んでいますが、サーバーはデータを送り続けていますので、このままではダメです。少しずつデータを読み込む必要があります。

まずは、これ以上ないという位簡単な例を示します。このコンソールアプリケーションでは、"http://stream.twitter.com/1/statuses/sample.json"に接続し、送られてくるデータをStreamReader.ReadLineメソッドで読み込み、コンソールに出力しています。よって表示されるデータは、ほぼ生のデータです。ここではとりあえず10秒間だけデータを受信し、その後接続を閉じています。

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

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

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

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

        '10秒間だけデータを取得する
        Dim startTime As DateTime = DateTime.Now
        While (DateTime.Now - startTime).Seconds < 10
        While (DateTime.Now - startTime).TotalSeconds < 10
            Console.WriteLine(sr.ReadLine())
        End While

        'こちらから接続を閉じる
        req.Abort()
        res.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 req = (HttpWebRequest)WebRequest.Create(
            "http://stream.twitter.com/1/statuses/sample.json");
        //ユーザー名とパスワードを設定する
        NetworkCredential nc = new NetworkCredential("username", "password");
        req.Credentials = nc;

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

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

        //10秒間だけデータを取得する
        DateTime startTime = DateTime.Now;
        while ((DateTime.Now - startTime).Seconds < 10)
        while ((DateTime.Now - startTime).TotalSeconds < 10)
        {
            Console.WriteLine(sr.ReadLine());
        }

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

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

このコードを実行すると、止めどなく、ものすごい勢いで文字が流れていきます。ただし、ASCII以外の文字はエンコードされていますので、何が書いてあるかはほとんど分かりません。

**5つのメソッド [#obb8f175]

上記で使用した"http://stream.twitter.com/1/statuses/sample.json"は、statuses/sampleメソッドを使用するためのURLです。[[Streaming API Documentation>http://apiwiki.twitter.com/Streaming-API-Documentation]]によると、用意されているメソッドは5つあります。しかしそのうち3つ(statuses/firehose、statuses/links、statuses/retweet)は申請が必要なため、普通の人はstatuses/sampleとstatuses/filterしか使えないようです。

statuses/sampleは、すべてのパブリックなステータスの中から幾つかをランダムで返します。statuses/filterは、パブリックなステータスの内、指定されたtrack(キーワード)、follow(投稿者)、locations(位置)にマッチしたものを返します。statuses/filterの使い方は、後述します。

**データを解析する [#u08edf8f]

Twitter Streaming APIは、JSONとXMLの2つのフォーマットをサポートしています。Streaming APIのドキュメントによると、よりコンパクトであるJSONの使用を強く勧めると書かれています。しかし.NETではXMLの方が扱いが楽なので、ここではXMLを使うことにします。

Streaming APIは、1つのステータスを表すオブジェクトをJSONまたはXML形式でエンコードした文字列を連続して送信します。1つ1つのステータスオブジェクトは、キャリッジリターン(\r)で区切られています。JSONの場合は、ステータスオブジェクト内にキャリッジリターンが入ることがないため、キャリッジリターンで分割すれば1つのステータスを簡単に切り出すことができます((ただし、ラインフィード(\n)がステータスオブジェクト内に入ることはあるとされています))。しかしXMLではステータスオブジェクト内にキャリッジリターンもラインフィードも入る可能性があるため、1つのステータスオブジェクトを切り出すのはより難しくなります。

ここでは、受信した文字列をとりあえずバッファに保存しておき、正規表現を使用して1つのステータスオブジェクトを受信できたかをチェックする方法で、切り出すことにします。

1つのステータスオブジェクトとして切り出した文字列は、XmlDocumentクラスのLoadXmlメソッドで読み込み、XMLの解析はXmlDocumentに任せてしまいます。

なお、送られてくるオブジェクトはステータスだけではありません。deleteやlimitといったオブジェクトも送られてきます。deleteは、前に送られてきた(あるいはこれから送られてくる)ステータスを削除をする必要がある時に送られてきます。limitは、Trackストリーム(filterメソッドのtrackのこと?)で送られることがあり、送られなかったステータスの数を示すようです。将来、これら以外のオブジェクトが加わる可能性があるため、柔軟に対応しなければいけないとされています。

また、オブジェクトとオブジェクトの間には空白の改行が送られてくることがありますが、これは接続を維持するためのものなので、無視します。

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

Module TwitterStreamingApiTest
    Sub Main()
        'XMLを切り出すための正規表現
        Dim xmlReg As New Regex( _
            "<\?xml\s[^>]+>\s*<(?<root>[^\s>]+)[^>]*>(?:.+?)</\k<root>>", _
            RegexOptions.Singleline Or RegexOptions.Compiled)

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

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

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

        '一時的にデータを保存しておくバッファ
        Dim buffer As New StringBuilder()

        '10秒間だけデータを取得する
        Dim startTime As DateTime = DateTime.Now
        While (DateTime.Now - startTime).Seconds < 10
        While (DateTime.Now - startTime).TotalSeconds < 10
            '一行取得
            Dim line As String = sr.ReadLine()
            If line Is Nothing Then
                Exit While
            End If
            '空行の時は何もしない
            If line.Length = 0 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のname)を表示する
                    Console.WriteLine("[{1}] {0}", _
                        rootElm("text").InnerText, rootElm("user")("name").InnerText)
                ElseIf rootElm.Name = "delete" Then
                    Console.WriteLine("[DELETE] {0}", _
                        rootElm("status")("id").InnerText)
                Else
                    Console.WriteLine("[?] {0}", rootElm.Name)
                End If
                'バッファをクリア
                buffer.Length = 0
            End If
        End While

        '閉じる
        req.Abort()
        res.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()
    {
        //XMLを切り出すための正規表現
        Regex xmlReg = new Regex(
            @"<\?xml\s[^>]+>\s*<(?<root>[^\s>]+)[^>]*>(?:.+?)</\k<root>>",
            RegexOptions.Singleline | RegexOptions.Compiled);

        //HttpWebRequestの作成
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
            "http://stream.twitter.com/1/statuses/sample.xml");
        //ユーザー名とパスワードを設定する
        NetworkCredential nc = new NetworkCredential("username", "password");
        req.Credentials = nc;

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

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

        //一時的にデータを保存しておくバッファ
        StringBuilder buffer = new StringBuilder();

        //10秒間だけデータを取得する
        DateTime startTime = DateTime.Now;
        while ((DateTime.Now - startTime).Seconds < 10)
        while ((DateTime.Now - startTime).TotalSeconds < 10)
        {
            //一行取得
            string line = sr.ReadLine();
            if (line == null)
                break;
            //空行の時は何もしない
            if (line.Length == 0)
                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のname)を表示する
                    Console.WriteLine("[{1}] {0}",
                        rootElm["text"].InnerText, rootElm["user"]["name"].InnerText);
                }
                else if (rootElm.Name == "delete")
                {
                    Console.WriteLine("[DELETE] {0}", rootElm["status"]["id"].InnerText);
                }
                else
                {
                    Console.WriteLine("[?] {0}", rootElm.Name);
                }
                //バッファをクリア
                buffer.Length = 0;
            }
        }

        //閉じる
        req.Abort();
        res.Close();
        sr.Close();

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

**delimitedパラメータを使う [#pee40e95]

上の例では正規表現を使ってオブジェクトを切り出しましたが、delimitedパラメータを使うという方法もあります。例えばURLを"http://stream.twitter.com/1/statuses/sample.xml?delimited=length"にすると、まずはじめに数字が一行送られてきて、その後数字のバイト数だけオブジェクトが送られてきます。よって、まず数字を受信して、その後その数字分データを受信すれば、きっちり1つ分のオブジェクトを受信できます。

以下にdelimitedパラメータを使った例を示します。ここでは、返される文字列がすべてASCIIであり、バイト数と文字数が同じであると決めつけることで、コードを簡単にしています。

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

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

        Dim res As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebResponse)
        Dim st As Stream = res.GetResponseStream()
        Dim sr As New StreamReader(st)

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

            '数字に変換出来るか調べる
            Dim xmlLen As Integer
            If Integer.TryParse(line, xmlLen) Then
                '指定されたバイト数だけ読み込む
                Dim xmlChars As Char() = New Char(xmlLen - 1) {}
                Dim len As Integer = sr.ReadBlock(xmlChars, 0, xmlChars.Length)

                '取得したXML文字列をXmlDocumentに読み込む
                Dim xmlDoc As New XmlDocument()
                xmlDoc.LoadXml(New String(xmlChars))
                'ルートを取得
                Dim rootElm As XmlElement = xmlDoc.DocumentElement
                '情報の表示
                If rootElm.Name = "status" Then
                    'statusの情報(textとuserのname)を表示する
                    Console.WriteLine("[{1}] {0}", _
                        rootElm("text").InnerText, rootElm("user")("name").InnerText)
                ElseIf rootElm.Name = "delete" Then
                    Console.WriteLine("[DELETE] {0}", _
                        rootElm("status")("id").InnerText)
                End If
            End If
        End While

        '閉じる
        req.Abort()
        res.Close()
        sr.Close()

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

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

class TwitterStreamingApiTest
{
    static void Main()
    {
        //HttpWebRequestの作成
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
            "http://stream.twitter.com/1/statuses/sample.xml?delimited=length");
        NetworkCredential nc = new NetworkCredential("username", "password");
        req.Credentials = nc;

        HttpWebResponse res = (HttpWebResponse)req.GetResponse();
        Stream st = res.GetResponseStream();
        StreamReader sr = new StreamReader(st);

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

            //数字に変換出来るか調べる
            int xmlLen;
            if (int.TryParse(line, out xmlLen))
            {
                //指定されたバイト数だけ読み込む
                char[] xmlChars = new char[xmlLen];
                int len = sr.ReadBlock(xmlChars, 0, xmlChars.Length);

                //取得したXML文字列をXmlDocumentに読み込む
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(new string(xmlChars));
                //ルートを取得
                XmlElement rootElm = xmlDoc.DocumentElement;
                //情報の表示
                if (rootElm.Name == "status")
                {
                    //statusの情報(textとuserのname)を表示する
                    Console.WriteLine("[{1}] {0}",
                        rootElm["text"].InnerText, rootElm["user"]["name"].InnerText);
                }
                else if (rootElm.Name == "delete")
                {
                    Console.WriteLine("[DELETE] {0}", rootElm["status"]["id"].InnerText);
                }
            }
        }

        //閉じる
        req.Abort();
        res.Close();
        sr.Close();

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

**ステータスオブジェクトのフィールドと意味 [#kffb77c9]

上の例ではとりあえずtext(ステータスの本文)とユーザー名のみを表示しましたが、これ以外にもステータスオブジェクトには多くの情報が含まれています。

ステータスオブジェクトのフィールドとしてどのような名前のものが返される可能性があるのかについて、正直なところ、Twitterのドキュメントのどこに記述があるのか分かりませんでした。ただその意味については、「[[Twitter API Wiki / Return Values>http://apiwiki.twitter.com/Return-Values]]」に説明があります。しかしここで紹介されていないフィールドも返されます。

これらについて、「[[Twitter API Wiki / Return Values>http://apiwiki.twitter.com/Return-Values]]」の説明と、私が試してみて多分こうではと想像した結果を、以下にまとめます。

***statusオブジェクト [#r77360d5]

|名前|説明|例((ほとんどが[[Twitter API Wiki / Return Values>http://apiwiki.twitter.com/Return-Values]]からの引用))|型((.NET Frameworkの型に変換する時に適切と思われる型))|h
|created_at|ステータスが作成された日時(UTC)|Sat Jan 24 22:14:29 +0000 2009|DateTime|
|id|ステータスのID|1145445329|Int64 または String|
|text|ステータスの本文|I am eating oatmeal, The first tag is always <html>|String|
|source|ステータスを送ったアプリケーション|web (Default), <a href="http://www.tweetdeck.com/">TweetDeck</a>|String|
|truncated|ステータスが省略されたか|true, false|Boolean|
|in_reply_to_status_id|返信元のステータスのID|empty, 1047468972|Nullable<Int64> または String|
|in_reply_to_user_id|返信元のステータスを書いたユーザーのID|empty, 14597523|Nullable<Int32> または String|
|favorited|認証ユーザーがステータスをお気に入りとしているか|true, false|Boolean|
|in_reply_to_screen_name|返信元のステータスを書いたユーザーの表示名|empty, tweetybird|String|
|retweeted_status|リツイート元のステータス||status|
|user|投稿したユーザーの情報||user|
|geo|投稿された場所||geo|
|coordinates|?|||
|place|?|||
|contributors|ステータスの投稿に関わったユーザーのIDの配列|14198354|List<String>|

***userオブジェクト [#x1d31ba1]

|名前|説明|例((ほとんどが[[Twitter API Wiki / Return Values>http://apiwiki.twitter.com/Return-Values]]からの引用))|型((.NET Frameworkの型に変換する時に適切と思われる型))|h
|id|ユーザーのID|14198354|Int32 または String|
|name|ユーザーの名前|empty (Default), Twitter API Chatter|String|
|screen_name|ユーザーの表示名|tweetybird, dougw|String|
|location|ユーザーの現在地|empty (Default), California OR New York, NY, In The Woods, 27.893621,-82.243706|String|
|description|ユーザーの自己紹介文|empty (Default), I like new shiny things.|String|
|profile_image_url|ユーザーのアイコンのURL|http://static.twitter.com/images/default_profile_normal.png(Default), http://s3.amazonaws.com/twitter_production/profile_images/14198354/sweet_avatar.jpg|String|
|url|ユーザーのWeb(ホームページ)|empty, http://downforeveryoneorjustme.com|String|
|protected|ツイートを非公開にしているか|true, false|Boolean|
|followers_count|ユーザーをフォローしている人の数|0, 4034|Int32|
|profile_background_color|ユーザーの背景色(RGB)|9ae4e8 (Default)|String|
|profile_text_color|ユーザーの前景色(RGB)|000000 (Default)|String|
|profile_link_color|ユーザーのリンクの色(RGB)|0000ff (Default)|String|
|profile_sidebar_fill_color|ユーザーのサイドバーの色(RGB)|e0ff92 (Default)|String|
|profile_sidebar_border_color|ユーザーのサイドバーの輪郭の色(RGB)|87bc44 (Default)|String|
|friends_count|ユーザーがフォローしている人の数|0, 221|Int32|
|created_at|作成日時(UTC)|Sat Jan 24 22:14:29 +0000 2009|DateTime|
|favourites_count|お気に入りの数|0, 451|Int32|
|utc_offset|ユーザーのタイムゾーンとUTCの秒数差|-21600 (Default), 32400, 36000|String|
|time_zone|ユーザーのタイムゾーン|Central Time (US & Canada) (Default), Tokyo, Osaka, Sydney|String|
|profile_background_image_url|ユーザーの背景画像のURL|empty, http://static.twitter.com/images/themes/theme1/bg.gif  (Default), http://s3.amazonaws.com/twitter_production/profile_background_images/2752608/super_sweet.jpg|String|
|profile_background_tile|ユーザーの背景画像がタイルされているか|true, false|Boolean|
|notifications|ユーザーのNotificationsが有効か|true, false|Boolean|
|geo_enabled|ユーザーがツイート位置情報を有効にしているか|true, false|Boolean|
|verified|認証済みアカウントか|true, false|Boolean|
|following|ユーザーがログインしているユーザーをフォローしているか|true, false|Boolean|
|statuses_count|ユーザーが投稿したステータスの数|0, 9423|Int32|
|lang|ユーザーの言語|ja, en, fr|String|
|contributors_enabled|contributors accessが可能か|true, false|Boolean|

**非同期でデータを受信する [#ye337dc5]

「[[WebRequest、WebResponseクラスを使ってファイルをダウンロードし表示する>http://dobon.net/vb/dotnet/internet/webrequest.html]]」で紹介しているのと同じように、非同期でデータを受信してみます。
「[[WebRequest、WebResponseクラスを使ってファイルをダウンロードし表示する>https://dobon.net/vb/dotnet/internet/webrequest.html]]」で紹介しているのと同じように、非同期でデータを受信してみます。

下の例では、エンターキーを押すまで受信をし続けます。ここでも、受信したデータはすべてASCII文字であると決めつけています。

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

Module TwitterStreamingApiTest
    '受信したデータを入れるバッファ
    Dim bufferData As Byte()
    '受信した文字列を入れておくStringBuilder
    Dim bufferString As StringBuilder
    'XMLを切り出すための正規表現
    Dim xmlReg As New Regex( _
        "<\?xml\s[^>]+>\s*<(?<root>[^\s>]+)[^>]*>(?:.+?)</\k<root>>", _
        RegexOptions.Singleline Or RegexOptions.Compiled)

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

        '非同期要求を開始
        '状態オブジェクトとしてHttpWebRequestをわたす
        Dim r As IAsyncResult = DirectCast(req.BeginGetResponse( _
            New AsyncCallback(AddressOf ResponseCallback), req), IAsyncResult)

        '待機する
        Console.ReadLine()

        '閉じる
        req.Abort()

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

    '非同期要求が終了した時に呼び出されるコールバックメソッド
    Sub ResponseCallback(ByVal ar As IAsyncResult)
        '状態オブジェクトとしてわたされたHttpWebRequestを取得
        Dim req As HttpWebRequest = DirectCast(ar.AsyncState, HttpWebRequest)

        '非同期要求を終了
        Dim res As HttpWebResponse = Nothing
        Try
            res = DirectCast(req.EndGetResponse(ar), HttpWebResponse)
        Catch ex As WebException
            req.Abort()
            Return
        End Try

        'Streamを取得
        Dim st As Stream = res.GetResponseStream()

        'データを読み込むための準備をする
        bufferData = New Byte(5119) {}
        bufferString = New StringBuilder()

        '非同期でデータの読み込みを開始
        '状態オブジェクトとしてStreamをわたす
        Dim r As IAsyncResult = DirectCast( _
            st.BeginRead(bufferData, _
                         0, _
                         bufferData.Length, _
                         New AsyncCallback(AddressOf ReadCallback), st),  _
                         IAsyncResult)
    End Sub

    '非同期読み込み完了時に呼び出されるコールバックメソッド
    Sub ReadCallback(ByVal ar As IAsyncResult)
        '状態オブジェクトとしてわたされたStreamを取得
        Dim st As Stream = DirectCast(ar.AsyncState, Stream)

        'データを読み込む
        Dim readSize As Integer = st.EndRead(ar)

        If readSize > 0 Then
            '読み込んだデータを文字列に変換
            Dim str As String = Encoding.ASCII.GetString(bufferData, 0, readSize)
            'バッファに追加する
            bufferString.Append(str)

            '正規表現と一致するか調べる
            Dim mc As MatchCollection = xmlReg.Matches(bufferString.ToString())
            For Each m As Match In mc
                'XmlDocumentに読み込む
                Dim xmlDoc As New XmlDocument()
                xmlDoc.LoadXml(m.Value)
                Dim rootElm As XmlElement = xmlDoc.DocumentElement
                If rootElm.Name = "status" Then
                    Console.WriteLine("[{1}] {0}", _
                        rootElm("text").InnerText, rootElm("user")("name").InnerText)
                End If
            Next
            'バッファを更新
            If mc.Count > 0 Then
                Dim m As Match = mc(mc.Count - 1)
                bufferString.Remove(0, m.Index + m.Length)
            End If

            '再び非同期でデータを読み込む
            Try
                Dim r As IAsyncResult = DirectCast( _
                    st.BeginRead(bufferData, _
                                 0, _
                                 bufferData.Length, _
                                 New AsyncCallback(AddressOf ReadCallback), st),  _
                                 IAsyncResult)
            Catch ex As WebException
                st.Close()
                Return
            End Try
        Else
            '閉じる
            st.Close()
            Console.WriteLine("----- 切断されました -----")
        End If
    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
{
    //受信したデータを入れるバッファ
    private static byte[] bufferData;
    //受信した文字列を入れておくStringBuilder
    private static StringBuilder bufferString;
    //XMLを切り出すための正規表現
    private static Regex xmlReg = new Regex(
        @"<\?xml\s[^>]+>\s*<(?<root>[^\s>]+)[^>]*>(?:.+?)</\k<root>>",
        RegexOptions.Singleline | RegexOptions.Compiled);

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

        //非同期要求を開始
        //状態オブジェクトとしてHttpWebRequestをわたす
        IAsyncResult r = (IAsyncResult)req.BeginGetResponse(
            new AsyncCallback(ResponseCallback), req);

        //待機する
        Console.ReadLine();

        //閉じる
        req.Abort();

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

    //非同期要求が終了した時に呼び出されるコールバックメソッド
    private static void ResponseCallback(IAsyncResult ar)
    {
        //状態オブジェクトとしてわたされたHttpWebRequestを取得
        HttpWebRequest req = (HttpWebRequest)ar.AsyncState;

        //非同期要求を終了
        HttpWebResponse res = null;
        try
        {
            res = (HttpWebResponse)req.EndGetResponse(ar);
        }
        catch (WebException)
        {
            req.Abort();
            return;
        }

        //Streamを取得
        Stream st = res.GetResponseStream();

        //データを読み込むための準備をする
        bufferData = new byte[5120];
        bufferString = new StringBuilder();

        //非同期でデータの読み込みを開始
        //状態オブジェクトとしてStreamをわたす
        IAsyncResult r = (IAsyncResult)st.BeginRead(
            bufferData,
            0,
            bufferData.Length,
            new AsyncCallback(ReadCallback),
            st);
    }

    //非同期読み込み完了時に呼び出されるコールバックメソッド
    private static void ReadCallback(IAsyncResult ar)
    {
        //状態オブジェクトとしてわたされたStreamを取得
        Stream st = (Stream)ar.AsyncState;

        //データを読み込む
        int readSize = st.EndRead(ar);

        if (readSize > 0)
        {
            //読み込んだデータを文字列に変換
            string str = Encoding.ASCII.GetString(bufferData, 0, readSize);
            //バッファに追加する
            bufferString.Append(str);

            //正規表現と一致するか調べる
            MatchCollection mc = xmlReg.Matches(bufferString.ToString());
            foreach (Match m in mc)
            {
                //XmlDocumentに読み込む
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(m.Value);
                XmlElement rootElm = xmlDoc.DocumentElement;
                if (rootElm.Name == "status")
                {
                    Console.WriteLine("[{1}] {0}",
                        rootElm["text"].InnerText, rootElm["user"]["name"].InnerText);
                }
            }
            //バッファを更新
            if (mc.Count > 0)
            {
                Match m = mc[mc.Count - 1];
                bufferString.Remove(0, m.Index + m.Length);
            }

            //再び非同期でデータを読み込む
            try
            {
                IAsyncResult r = (IAsyncResult)st.BeginRead(
                    bufferData,
                    0,
                    bufferData.Length,
                    new AsyncCallback(ReadCallback),
                    st);
            }
            catch (WebException)
            {
                st.Close();
                return;
            }
        }
        else
        {
            //閉じる
            st.Close();
            Console.WriteLine("----- 切断されました -----");
        }
    }
}
}}

**次回予告 [#j94ee3e7]

長くなってしまいましたので、statuses/filterメソッドについては、次回紹介します。

**履歴 [#b23fb4b9]

-2010/6/18 : 10秒の測り方の間違いを修正。

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

//これより下は編集しないでください
#pageinfo([[:Category/.NET]],2010-06-09 (水) 02:53:12,DOBON!,2010-06-09 (水) 02:53:12,DOBON!)
#pageinfo([[:Category/.NET]],2010-06-09 (水) 02:53:12,DOBON!,2010-06-18 (金) 23:57:24,DOBON!)

[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]