ツイッターの全発言を取得する perl スクリプトを以前書いたので、
指定したTwitterアカウントの全ツイートを取得(perl版)
http://www.moonmile.net/blog/archives/860
今回は、C# を使ってもう少し分かりやすく書き直します。ひとまず、抜粋だけアップして、後でツールをアップということで。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | public class TwiBack { protected string _user; /// <summary> /// アカウント /// </summary> public string User { get { return _user; } set { _user = value; } } protected const string HTTPTWI = "http://twitter.com" ; /// <summary> /// ツイート数 /// </summary> /// <returns></returns> public int GetTweetCount() { string url = string .Format( "{0}/{1}" , HTTPTWI, _user); WebClient web = new WebClient(); StreamReader sr = new StreamReader( web.OpenRead(url)); // <span id="update_count" class="stat_count">2,153</span> Regex rx = new Regex( ">([0-9,]+)<" ); while (sr.EndOfStream == false ) { string line = sr.ReadLine(); if (line.IndexOf( "<span id=\"update_count\"" ) >= 0) { Match mt = rx.Match(line); int count = int .Parse(mt.Groups[1].Value.Replace( "," , "" )); return count; } // Console.WriteLine(line); } return 0; } /// <summary> /// ページ番号でツイートを取得 /// </summary> /// <param name="page"></param> /// <returns></returns> public string GetTweetPage( int page) { string url = string .Format( "{0}/{1}?page={2}" , HTTPTWI, _user, page); WebClient web = new WebClient(); StreamReader sr = new StreamReader(web.OpenRead(url)); // <li class="hentry u-moonmile status" id="status_41673902056935425" List< string > lines = new List< string >(); while (sr.EndOfStream == false ) { string line = sr.ReadLine(); if (line.IndexOf( "<li class=\"hentry" ) >= 0) { lines.Add(line + "\n" ); while (sr.EndOfStream == false ) { line = sr.ReadLine(); lines.Add( line + "\n" ); if ( line.IndexOf( "</li>" ) >= 0 ) { break ; } } } } string ss = "" ; foreach ( string s in lines ) { ss += s ; } return ss ; } /// <summary> /// ツイートXMLをコンバート /// </summary> /// <param name="xml"></param> /// <returns></returns> public string ConvXml( string xml) { EXDoc.EXDocument doc = new EXDocument(); doc.LoadXML(xml); EXDocument dout = new EXDocument(); // ルート要素を作る dout += "tweets" ; // 変換元をループ foreach (EXElement twi in doc * "li" ) { // 変換元から取り出す string id = twi[ "id" ].Replace( "status_" , "" ); string content = ((EXElement)(twi * "span" % "class" == "entry-content" )).Xml; string date = twi * "span" % "class" == "published timestamp" ; Console.WriteLine(id); // 要素を作る EXElement tweet = dout.Root.Append( "tweet" ); tweet.Append( "id" ).Value = id; tweet.Append( "content" ).Value = content; tweet.Append( "date" ).Value = date; } return dout.Xml; } } |
TwiBack クラスの中で、GetTweetCount メソッドと GetTweetPage メソッドはノーマルに Twitter の Web ページから発言数と発言を取得しているところです。perl 版と同様に、ブラウザ経由で取得するので、アクセス数制限はありません。ただ、返信とかしているのは取れないので、いまいちですけどね。このあたりは、OAuth でログインした後にブラウザ経由で取るように修正してきます。
# 当初の目的が人様の発言を全部取ってくるので、バックアップとはちょっとニュアンスが違うのです。
で、取得した HTML タグは、以下の形式で取得できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | < li class = "hentry u-moonmile status latest-status" id = "status_42686164938932224" > < span class = "status-body" > < span class = "status-content" > < span class = "entry-content" >【メモ】 クローラのせいで重くなった MT4i の対策 - Movable Type運営記 < a href = "http://bit.ly/es5CWf" class = "tweet-url web" rel = "nofollow" target = "_blank" >http://bit.ly/es5CWf</ a > -- 以前から気になっていたけど、yeti は韓国系のクローラーなのか。これもブロック。</ span > </ span > < span class = "meta entry-meta" data = '{}' > < a class = "entry-date" rel = "bookmark" href = "http://twitter.com/moonmile/status/42686164938932224" > < span class = "published timestamp" data = "{time:'Tue Mar 01 20:42:29 +0000 2011'}" >12:42 PM Mar 1st</ span > </ a > < span >< a href = "http://moonmile.net/" rel = "nofollow" >TwiNetBot</ a >から</ span > </ span > < ul class = "meta-data clearfix" ></ ul > </ span > </ li > |
このままだと使いづらいので、分かりやすいように整形します。ってところで普通ならば LINQ to XML を使うのでしょうが、ええッそうですッ!!! 自前の EXDoc を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /// <summary> /// ツイートXMLをコンバート /// </summary> /// <param name="xml"></param> /// <returns></returns> public string ConvXml( string xml) { EXDoc.EXDocument doc = new EXDocument(); doc.LoadXML(xml); EXDocument dout = new EXDocument(); // ルート要素を作る dout += "tweets" ; // 変換元をループ foreach (EXElement twi in doc * "li" ) { // 変換元から取り出す string id = twi[ "id" ].Replace( "status_" , "" ); string content = ((EXElement)(twi * "span" % "class" == "entry-content" )).Xml; string date = twi * "span" % "class" == "published timestamp" ; Console.WriteLine(id); // 要素を作る EXElement tweet = dout.Root.Append( "tweet" ); tweet.Append( "id" ).Value = id; tweet.Append( "content" ).Value = content; tweet.Append( "date" ).Value = date; } return dout.Xml; } |
なんか不思議な記号がいっぱいになってきてしまいましたがw、変換元から、
・id
・content(発言)
・date(発言した日時)
を拾ってきて、
1 2 3 4 5 | < tweet > < id >...</ id > < content >...</ content > < date >...</ date > </ tweet > |
な形に整形します。
要素を追加するのに、いろいろ多重定義する演算子を探してみたのですが、結局のところ、Append メソッドに落ち着きました。デリゲートの追加演算子のように += 演算子を使おうとしたのですが(実装はしています)、+= 演算子を使った後に、追加した要素が取れないことになるので、やめました。
C++ だと、
1 2 3 4 | EXElement *tweet = dout.Root += "tweet" ; (tweet += "id" ) = id; (tweet += "content" ) = content; (tweet += "date" ) = date; |
な感じで作れるんですけどね…C# だと左辺に式を置けないので、これができないのです。
あと、cout などで定番の << 演算子も C# ではなぜか数値しか使えないので、【使えない】演算子になっています。このあたりの制限が、C# はかなり変です。
とは言え、メソッドチェーンと配列を使いながら、イメージしやすい形でコーディングができます。特に、XML を作成するときは、Append を続けて子を作っていくというのがイメージしやすいと思います。これが普通の XML の構築だと、CreateNode した後に、AppendChild するので、なんか作成と挿入が遠い感じになってしまいます(まあ、作成した途端に追加すればいいだけなんですけどね)。