tweetbackup もどきを作ってみる

ツイッターの全発言を取得する 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 するので、なんか作成と挿入が遠い感じになってしまいます(まあ、作成した途端に追加すればいいだけなんですけどね)。

カテゴリー: C# パーマリンク