TwitterのツイートをHTMLに落とし込むツールが欲しかったのでJavaで書いてみた。

ついったー(Twitter)発言まとめツール がすばらしかったので、HTML構造や CSS の id や class はこれを踏襲。

動作確認環境

* Windows XP SP3
* JDK 1.6
* Twitter4J version 2.1.1

ソースコード


import java.io.*;
import java.text.*;
import java.util.*;
 
import twitter4j.*;
 
public class TwetterSearchPrinter {
 
  public static void main(String[] args) throws Exception {
    
    String searchQuery = "nilab";
    Integer limit = 1000;
    String outfile = "outfile.html";
    PrintStream out = new PrintStream(new File(outfile), "UTF-8");
    // PrintStream out = System.out;
    print(searchQuery, limit, out);
  }
  
  private static String KAIGYO = "\r\n";
  
  /**
   * 検索クエリにマッチしたツイートを出力します。
   * @param searchQuery 検索クエリ
   * @param limit 出力するツイートの最大数
   * @param out 出力先
   * @throws 何かエラー
   */
  private static void print(String searchQuery, Integer limit, PrintStream out) throws Exception {
    
    // APIからツイートを取得
    List<Tweet> tweets = getTweets(searchQuery, limit);
    
    // APIで取得すると新しいほうから古いほうへの並びになっているので、ツイートが古い順になるようソート。
    Collections.reverse(tweets);
    
    // ツイートを出力
    print(tweets, limit, out);
  }
  
  /**
   * 検索クエリにマッチしたツイートを返します。
   * @param searchQuery 検索クエリ
   * @param limit 取得する最大数
   * @return 検索クエリにマッチしたツイート
   * @throws 何かエラー
   */
  private static List<Tweet> getTweets(String searchQuery, Integer limit) throws Exception {
    
    List<Tweet> tweets = new ArrayList<Tweet>(); 
    try{
      // APIにてツイートを取得
      TwitterFactory factory = new TwitterFactory();
      Twitter twitter = factory.getInstance();
      Query query = new Query();
      query.setQuery(searchQuery);
      // rpp: Optional. The number of tweets to return per page, up to a max of 100. 
      query.setRpp(100);
      // page: Optional. The page number (starting at 1) to return, up to a max of roughly 1500 results (based on rpp * page.
      query.setPage(1);
      long max_id = 0;
      while(true){
        // ツイートを取得
        QueryResult qr = twitter.search(query);
        System.out.print("*"); // APIを1回コールしたという表示
        List<Tweet> statusList = qr.getTweets();
        // ツイートが取得できなくなったら終了
        if(statusList.size() == 0){
          break;
        }
        tweets.addAll(statusList);
        // ツイートが欲しい最大数以上になったら終了
        if(limit != null && limit <= tweets.size()){
          break;
        }
        // 次にAPIをたたくときは、いま取得したツイートより昔のを取得するように
        max_id = statusList.get(statusList.size() - 1).getId() - 1;
        query.setMaxId(max_id);
        // 負荷高いと怒られるかもしれないから
        try{Thread.sleep(1000);}catch(Exception e){}
      }
      System.out.println("");
      
    }catch(Exception e){
      // エラーが発生してもスタックトレースを出力するだけで
      // ここまでに取得したツイートは返すことにする
      e.printStackTrace();
    }
    
    return tweets;
  }
  
  /**
   * ツイートを出力します。
   * @param tweets 出力するツイートの最大数
   * @param out 出力先
   */
  private static void print(List<Tweet> tweets, Integer limit, PrintStream out){
    out.print("<table class=\"twitter_matome\">");
    out.print(KAIGYO);
    int num = limit != null ? Math.min(limit, tweets.size()) : tweets.size();
    for(int i=0; i<num; i++){
      Tweet tweet = tweets.get(i);
      out.print(toString(tweet));
    }
    out.print("</table>");
    out.print(KAIGYO);
  }
  
  /**
   * 出力するツイートを文字列化します。
   * @param t 出力するツイート
   * @return 文字列化されたツイート
   */
  private static String toString(Tweet t){
    StringBuffer buf = new StringBuffer();
    buf.append("<tr class=\"twitter_matome_status\" id=\"" + t.getId() + "\">");
    buf.append(KAIGYO);
    buf.append("<td class=\"twitter_matome_image\">");
    buf.append("<a href=\"http://twitter.com/" + escapeHtml(t.getFromUser()) + "\">");
    // プロフィールアイコンにTwitter標準のURLを使うときはこっち↓
    buf.append("<img src=\"" + escapeHtml(t.getProfileImageUrl()) + "\" width=\"48\" />");
    // プロフィールアイコンに http://tweetimag.es/ を使うときはこっち↓
    //buf.append("<img src=\"http://img.tweetimag.es/i/" + escapeHtml(t.getFromUser()) + "_n\" width=\"48\" />");
    buf.append("</a>");
    buf.append("</td>");
    buf.append(KAIGYO);
    buf.append("<td class=\"twitter_matome_name\">");
    buf.append("<a href=\"http://twitter.com/" + escapeHtml(t.getFromUser()) + "\">");
    buf.append(escapeHtml(t.getFromUser()));
    buf.append("</a>");
    buf.append("</td>");
    buf.append(KAIGYO);
    buf.append("<td class=\"twitter_matome_text\">");
    buf.append(normalizeText(t.getText()));
    buf.append("</td>");
    buf.append(KAIGYO);
    buf.append("<td class=\"twitter_matome_date\">");
    buf.append("<a href=\"http://twitter.com/" + escapeHtml(t.getFromUser()) + "/statuses/" + t.getId() + "\">");
    buf.append(normalizeDate(t.getCreatedAt()));
    buf.append("</a>");
    buf.append("</td>");
    buf.append(KAIGYO);
    buf.append("</tr>");
    buf.append(KAIGYO);
    return buf.toString();
  }
  
  private static DateFormat createDateFormat(){
    // 日本人ですからJST、そしてLocaleはJAPANで。
    DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.JAPAN);
    df.setTimeZone(TimeZone.getTimeZone("JST"));
    return df;
  }
  
  public static final DateFormat yyyyMMddHHmmss = createDateFormat();
  
  /**
   * 出力するツイートの日時情報を文字列化します。
   * @param d 日時情報
   * @return 文字列化された日時情報
   */
  private static String normalizeDate(Date d){
    return yyyyMMddHHmmss.format(d);
  }
  
  /**
   * HTML用えすけーぷ。
   * @param s 文字列
   * @return 適切に変換された文字列
   */
  private static String escapeHtml(String s){
    return
      s.replace("&", "&amp;")
       .replace("<", "&lt;")
       .replace(">", "&gt;")
       .replace("'", "&#39;")
       .replace("\"", "&quot;");
  }
  
  /**
   * 出力するツイート内容を適切な文字列に変換します。
   * @param s ツイート内容
   * @return 適切に変換された文字列
   */
  private static String normalizeText(String s){
    s = escapeHtml(s);
    // ツイートに改行が含まれることがあるので何とかする
    s = s.replaceAll("\r\n", "\n");
    s = s.replaceAll("\r", "\n");
    s = s.replaceAll("\n", "<br />");
    return s;
  }
 
}

出力結果の例

出力結果の例(HTMLの中身)


<table class="twitter_matome">
<tr class="twitter_matome_status" id="12815784668">
<td class="twitter_matome_image"><a href="http://twitter.com/nilab"><img src="http://a1.twimg.com/profile_images/25307672/zlashdot_profile_normal.jpg" width="48" /></a></td>
<td class="twitter_matome_name"><a href="http://twitter.com/nilab">nilab</a></td>
<td class="twitter_matome_text">プログラミングちう</td>
<td class="twitter_matome_date"><a href="http://twitter.com/nilab/statuses/12815784668">2010-04-25 19:49:20</a></td>
</tr>
<tr class="twitter_matome_status" id="12815787775">
<td class="twitter_matome_image"><a href="http://twitter.com/nilab"><img src="http://a1.twimg.com/profile_images/25307672/zlashdot_profile_normal.jpg" width="48" /></a></td>
<td class="twitter_matome_name"><a href="http://twitter.com/nilab">nilab</a></td>
<td class="twitter_matome_text">Twitter4J - A Java library for the Twitter API<br />http://twitter4j.org/ja/index.html</td>
<td class="twitter_matome_date"><a href="http://twitter.com/nilab/statuses/12815787775">2010-04-25 19:49:27</a></td>
</tr>
<tr class="twitter_matome_status" id="12815790185">
<td class="twitter_matome_image"><a href="http://twitter.com/nilab"><img src="http://a1.twimg.com/profile_images/25307672/zlashdot_profile_normal.jpg" width="48" /></a></td>
<td class="twitter_matome_name"><a href="http://twitter.com/nilab">nilab</a></td>
<td class="twitter_matome_text">Twitter API Wiki / Twitter Search API Method: search<br />http://apiwiki.twitter.com/Twitter-Search-API-Method%3A-search</td>
<td class="twitter_matome_date"><a href="http://twitter.com/nilab/statuses/12815790185">2010-04-25 19:49:32</a></td>
</tr>
<tr class="twitter_matome_status" id="12815792988">
<td class="twitter_matome_image"><a href="http://twitter.com/nilab"><img src="http://a1.twimg.com/profile_images/25307672/zlashdot_profile_normal.jpg" width="48" /></a></td>
<td class="twitter_matome_name"><a href="http://twitter.com/nilab">nilab</a></td>
<td class="twitter_matome_text"> ∧_∧<br />( ´∀`)</td>
<td class="twitter_matome_date"><a href="http://twitter.com/nilab/statuses/12815792988">2010-04-25 19:49:38</a></td>
</tr>
<tr class="twitter_matome_status" id="12815796149">
<td class="twitter_matome_image"><a href="http://twitter.com/nilab"><img src="http://a1.twimg.com/profile_images/25307672/zlashdot_profile_normal.jpg" width="48" /></a></td>
<td class="twitter_matome_name"><a href="http://twitter.com/nilab">nilab</a></td>
<td class="twitter_matome_text">迷路ネタ。<br />║╠═╦═╦╦═╦═╦╦═╦╦╦══╦═╦═╦═╗<br />║╚╬╦╩╗╔╬╗╚╬╗╠╦╩╔╩╩╦╩╦╣╦╬║<br />╠╩╔╦╝║╩╔╩║╩╬╚╦╬╠║║╬║╠╠╩║╣<br />╚╩╩═╩═╩═╩═╩═╩═══╩══╩═╩╩══</td>
<td class="twitter_matome_date"><a href="http://twitter.com/nilab/statuses/12815796149">2010-04-25 19:49:44</a></td>
</tr>
<tr class="twitter_matome_status" id="12815830857">
<td class="twitter_matome_image"><a href="http://twitter.com/nilab"><img src="http://a1.twimg.com/profile_images/25307672/zlashdot_profile_normal.jpg" width="48" /></a></td>
<td class="twitter_matome_name"><a href="http://twitter.com/nilab">nilab</a></td>
<td class="twitter_matome_text">ぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるぱられるわーーー</td>
<td class="twitter_matome_date"><a href="http://twitter.com/nilab/statuses/12815830857">2010-04-25 19:50:53</a></td>
</tr>
</table>

参考

* Twitter4J - A Java library for the Twitter API
* Twitter API Wiki / Twitter Search API Method: search

ツイートを検索してインタラクティブに編集したい人は
Togetter とか
ついったー(Twitter)発言まとめツール
使ったほうが幸せになれるはず。

tags: zlashdot Java Java Twitter Twitter4J

Posted by NI-Lab. (@nilab)