ミッションたぶんPossible

どこにでもいるシステムエンジニアのなんでもない日記です。たぶん。

人生3万日しかない の最高齢と最年少は誰だ!!? #30thou

はじめに

 最近Twitterでは深夜0時くらいになると「人生3万日しかない」というツイートがTLを埋め尽くすというちょっと変わった現象が起こっています。



こういうのとか



こういうのとか。



 これは同名の日記サービスみたいなもんで、TwitterほかいくつかのWebサービスと連動しているみたいです。どうも結構流行ってるらしくて、深夜になるとそこかしこでハッシュタグ#30thou」を見かけるようになります。ちょっと前の「なるほど4時じゃねーの」みたいなもんですね。


Loading....





たてゆれさんの疑問

 ところで、オレのフォロワーの一人がこんな発言をしてました。



 …なるほどその発想は無かった。調べてみたら案外面白いかもなぁ。どうせだったらちょっと幅を持たせて調べてみましょうかい。と言う訳でまどマギリスト以来Twitter4jにご登場頂こうとしたんですが、ちょっと問題が。




「人生3万日しかない」関連ツイートの特徴

 上記のように「人生3万日しかない」関連ツイートにはサービスから吐かれる2種類のツイートと、それ以外の合わせて3種類に分類出来ます。

  1. 登録者が日記(一言コメント?)を登録し忘れて日付を自動で呟く
  2. 登録者が日記を登録してその内容を呟く
  3. 誰かが呟いた「人生3万日しかない」関連ツイートを非公式RT / QTする

 今回調べたいのは残り日数だけですので、2 と 3のツイートはハッキシ言って不要です。これらを除外したいんですが、Twitterの公式APIはpageが100しか無い上、日本語検索が非常に弱いときている。欲しいデータに辿り着けない、とまではいかないまでも、十分なデータが集められないかもしれません。できるだけ沢山のデータから導き出し、なるべく適正な結果が欲しいところです。




 そんな風に調べてたらこんなサービスを見つけました。


http://yats-data.com/yats/


 ツイートを独自にキャッシュして検索出来るサービスのようで、なんと公式よりも日本語検索に強いのが特徴。しかもJSONAPIを公開してくれています。これだ!とばかりに早速活用させてもらいました。





調査結果発表!

 それでは、「人生3万日しかない」の最年少トップ50と、最年長トップ50をどうぞご覧下さい。


最年少ランキング

最年少 : 6日(2011年6月24日生まれ)

年少第2位 : 3107日(2002年12月27日生まれ)

年少第3位 : 3107日(2002年12月27日生まれ)

年少第4位 : 3107日(2002年12月27日生まれ)

年少第5位 : 3131日(2002年12月3日生まれ)

年少第6位 : 3134日(2002年11月30日生まれ)

年少第7位 : 3134日(2002年11月30日生まれ)

年少第8位 : 3169日(2002年10月26日生まれ)

年少第9位 : 3207日(2002年9月18日生まれ)

年少第10位 : 3208日(2002年9月17日生まれ)

年少第11位 : 3272日(2002年7月15日生まれ)

年少第12位 : 3285日(2002年7月2日生まれ)

年少第13位 : 3285日(2002年7月2日生まれ)

年少第14位 : 3320日(2002年5月28日生まれ)

年少第15位 : 3344日(2002年5月4日生まれ)

年少第16位 : 3437日(2002年1月31日生まれ)

年少第17位 : 3467日(2002年1月1日生まれ)

年少第18位 : 3470日(2001年12月29日生まれ)

年少第19位 : 3470日(2001年12月29日生まれ)

年少第20位 : 3470日(2001年12月29日生まれ)

年少第21位 : 3470日(2001年12月29日生まれ)

年少第22位 : 3470日(2001年12月29日生まれ)

年少第23位 : 3470日(2001年12月29日生まれ)

年少第24位 : 3470日(2001年12月29日生まれ)

年少第25位 : 3470日(2001年12月29日生まれ)

年少第26位 : 3470日(2001年12月29日生まれ)

年少第27位 : 3470日(2001年12月29日生まれ)

年少第28位 : 3470日(2001年12月29日生まれ)

年少第29位 : 3470日(2001年12月29日生まれ)

年少第30位 : 3470日(2001年12月29日生まれ)

年少第31位 : 3470日(2001年12月29日生まれ)

年少第32位 : 3470日(2001年12月29日生まれ)

年少第33位 : 3470日(2001年12月29日生まれ)

年少第34位 : 3470日(2001年12月29日生まれ)

年少第35位 : 3470日(2001年12月29日生まれ)

年少第36位 : 3470日(2001年12月29日生まれ)

年少第37位 : 3470日(2001年12月29日生まれ)

年少第38位 : 3470日(2001年12月29日生まれ)

年少第39位 : 3470日(2001年12月29日生まれ)

年少第40位 : 3470日(2001年12月29日生まれ)

年少第41位 : 3470日(2001年12月29日生まれ)

年少第42位 : 3470日(2001年12月29日生まれ)

年少第43位 : 3470日(2001年12月29日生まれ)

年少第44位 : 3470日(2001年12月29日生まれ)

年少第45位 : 3470日(2001年12月29日生まれ)

年少第46位 : 3470日(2001年12月29日生まれ)

年少第47位 : 3470日(2001年12月29日生まれ)

年少第48位 : 3470日(2001年12月29日生まれ)

年少第49位 : 3470日(2001年12月29日生まれ)

年少第50位 : 3470日(2001年12月29日生まれ)

最年長ランキング

最年長 : 29999日(1929年5月12日生まれ)

年長第2位 : 29987日(1929年5月24日生まれ)

年長第3位 : 29986日(1929年5月25日生まれ)

年長第4位 : 29964日(1929年6月16日生まれ)

年長第5位 : 29958日(1929年6月22日生まれ)

年長第6位 : 29954日(1929年6月26日生まれ)

年長第7位 : 29954日(1929年6月26日生まれ)

年長第8位 : 29947日(1929年7月3日生まれ)

年長第9位 : 29887日(1929年9月1日生まれ)

年長第10位 : 29873日(1929年9月15日生まれ)

年長第11位 : 29847日(1929年10月11日生まれ)

年長第12位 : 29809日(1929年11月18日生まれ)

年長第13位 : 29808日(1929年11月19日生まれ)

年長第14位 : 29808日(1929年11月19日生まれ)

年長第15位 : 29794日(1929年12月3日生まれ)

年長第16位 : 29794日(1929年12月3日生まれ)

年長第17位 : 29793日(1929年12月4日生まれ)

年長第18位 : 29786日(1929年12月11日生まれ)

年長第19位 : 29783日(1929年12月14日生まれ)

年長第20位 : 29769日(1929年12月28日生まれ)

年長第21位 : 29630日(1930年5月16日生まれ)

年長第22位 : 29536日(1930年8月18日生まれ)

年長第23位 : 29517日(1930年9月6日生まれ)

年長第24位 : 29517日(1930年9月6日生まれ)

年長第25位 : 29468日(1930年10月25日生まれ)

年長第26位 : 29422日(1930年12月10日生まれ)

年長第27位 : 29349日(1931年2月21日生まれ)

年長第28位 : 29349日(1931年2月21日生まれ)

年長第29位 : 29227日(1931年6月23日生まれ)

年長第30位 : 29227日(1931年6月23日生まれ)

年長第31位 : 29174日(1931年8月15日生まれ)

年長第32位 : 29174日(1931年8月15日生まれ)

年長第33位 : 29110日(1931年10月18日生まれ)

年長第34位 : 28963日(1932年3月13日生まれ)

年長第35位 : 28893日(1932年5月22日生まれ)

年長第36位 : 28893日(1932年5月22日生まれ)

年長第37位 : 28673日(1932年12月28日生まれ)

年長第38位 : 26783日(1938年3月2日生まれ)

年長第39位 : 26783日(1938年3月2日生まれ)

年長第40位 : 25977日(1940年5月16日生まれ)

年長第41位 : 25755日(1940年12月24日生まれ)

年長第42位 : 25755日(1940年12月24日生まれ)

年長第43位 : 25655日(1941年4月3日生まれ)

年長第44位 : 25655日(1941年4月3日生まれ)

年長第45位 : 22152日(1950年11月5日生まれ)

年長第46位 : 22040日(1951年2月25日生まれ)

年長第47位 : 21868日(1951年8月16日生まれ)

年長第48位 : 21348日(1953年1月17日生まれ)

年長第49位 : 21311日(1953年2月23日生まれ)

年長第50位 : 20933日(1954年3月8日生まれ)

ソースコード公開

 この結果を出力する為のJavaソースコードはこちら。


Only30000days.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Calendar;
import java.util.TreeMap;
import java.util.Vector;

import net.arnx.jsonic.JSON;

public class Only30000days {
	/** 問い合わせURL & キーワード */
	public static final String QUERY_STRING = "http://yats-data.com/yats/search?query=%E4%BB%8A%E6%97%A5%E3%81%AF%E6%9C%89%E6%84%8F%E7%BE%A9%E3%81%A0%E3%81%A3%E3%81%9F%E3%81%8B+30thou+-RT+-QT+-%E5%8F%A3%E3%81%90%E3%81%9B%E5%88%86%E6%9E%90&json";
	/** リクエストページ数 */
	public static final int REQUEST_COUNT = 300;
	/** 問い合わせ間隔(ミリ秒) */
	public static final int WAIT_TIME = 20000;
	/**
	 * メインメソッド。Twitter検索(http://yats-data.com/yats/)からリスト情報を取得する。
	 * @param args 使用しない
	 */
	public static void main(String[] args) {

		int page = 1;
		int count = 0;
		TreeMap<String, TweetData> map = new TreeMap<String, TweetData>();
		Vector<String> users = new Vector<String>();
		Vector<TweetData> tweets = new Vector<TweetData>();
		try {
			while(count < REQUEST_COUNT) {
				count++;
				String query = "";
				// 初回以外はページ番号を指定する
				if(page > 0) {
					query = QUERY_STRING + "&page=" + count;
				}
				// HTTP GET情報をセット
				URL url = new URL(query);
				HttpURLConnection http = (HttpURLConnection)url.openConnection();
				http.setRequestMethod("GET");
				http.setInstanceFollowRedirects(false);
				http.connect();
				// 接続
				http.connect();
				// Server内部エラーが発生している場合、ここで高い確率でIOExceptionが発生する
				BufferedReader reader = new BufferedReader(new InputStreamReader(http.getInputStream()));

				// JSONICでデコードできる様に改行を排除
				String line = "";
				StringBuffer buf = new StringBuffer();
				while((line = reader.readLine()) != null) {
					buf.append(line);
				}
				// ツイート情報(JSON形式)をでコード
				TweetData[] datas = JSON.decode(buf.toString(), TweetData[].class);
				for(TweetData data: datas) {
					// 同じIDの情報が既にないか、チェック
					TweetData before = map.get(data.user);
					if(before == null || before.user.length() != 0) {
						// 事前データが無い場合にはデータ保持
						map.put(data.user, data);
						users.add(data.user);
					} else if(data.time.compareTo(before.time) < 0) {
						// 事前に同じIDのツイートデータがあり、新規ツイートデータの方が新しい場合、メモリ保持データを新規ツイートデータに更新
						map.put(data.user, data);
					}
				}
				// サーバ側負担を減らすため、待機
				Thread.sleep(WAIT_TIME);
				System.out.println("server access time:" + count);
			}

		} catch(IOException ex) {
			ex.printStackTrace();
		} catch(Exception ex) {
			ex.printStackTrace();
		} finally {
			// かなり頻繁にサーバ内部エラーが起こるので、取得したツイート情報の処理はほとんどfinallyで実施する
			try {
				for(int i = 0; i < users.size(); i++) {
					TweetData data = map.get(users.get(i));
					boolean addflg = true;
					try {
						// 「○日生きた」の日数を取得
						data.days = Integer.parseInt(data.content.substring(data.content.indexOf(" は")+2, data.content.indexOf("日生きた。人生あと")));
					} catch(NumberFormatException nfe) {
						// NumberFormatExceptionが発生した場合、改変されている(非公式RT / QT)の可能性があるため除外
						addflg = false;
					}
					if(addflg) tweets.add(data);
				}
				// 日数でソート
				tweets = sort(tweets);

				// 年少上位50位を出力
				System.out.println("--------------------- 年 少 --------------------------");
				for(int i = 0; i < 50; i++) {
					output(i + 1, tweets.get(i), "年少");
				}
				// 年長上位50位を出力
				System.out.println("--------------------- 年 長 --------------------------");
				for(int i = 1; i < 51; i++) {
					output(i, tweets.get(tweets.size() - i), "年長");
				}
			} catch(Exception ex2) {
				ex2.printStackTrace();
			}
		}
	}
	/*
	 * ソート処理
	 * @param tweets ツイート情報の可変配列
	 * @return 若い順に整列されたツイート情報
	 */
	private static Vector<TweetData> sort(Vector<TweetData> tweets) {
		// 可変配列が空の場合には処理終了
		if(tweets == null || tweets.size() == 0) return new Vector<TweetData>();

		boolean flg = true;
		Vector<TweetData> vec_sort = null;
		while(flg){
			vec_sort = new Vector<TweetData>();
			TweetData tweet = tweets.get(0).copy();
			flg = false;
			for(int i = 1; i < tweets.size(); i++) {
				if(tweet.days <= tweets.get(i).days) {
					vec_sort.add(tweet);
					tweet = tweets.get(i).copy();
				} else {
					flg = true;
					vec_sort.add(tweets.get(i).copy());
				}
			}
			vec_sort.add(tweet);
			tweets = (Vector<TweetData>)vec_sort.clone();
		}
		return tweets;
	}
	/*
	 * 出力処理
	 * @param rank 順位
	 * @param data ツイート情報
	 * @param op 出力情報のジャンルを表す(年長 or 年少)
	 */
	private static void output(int rank, TweetData data, String op) {
		// カレンダー情報から生年月日を算出
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.DATE, - data.days);

		StringBuffer buf = new StringBuffer();
		// 1位の時だけ「最年少」「最年長」と順位番号を入れずに表示
		if(rank == 1) {
			buf.append("最");
			buf.append(op);
		} else {
			buf.append(op);
			buf.append("第");
			buf.append(rank);
			buf.append("位");
		}
		buf.append(" : <!-- @");
		buf.append(data.user);
		buf.append(" :URL=");
		buf.append(data.url);
		buf.append(" -->");
		buf.append(data.days);
		buf.append("日(");
		buf.append(cal.get(Calendar.YEAR));
		buf.append("年");
		buf.append(cal.get(Calendar.MONTH) + 1);
		buf.append("月");
		buf.append(cal.get(Calendar.DATE));
		buf.append("日生まれ)");

		// 出力
		System.out.println(buf.toString());
	}
}

TweetData.java

import java.util.Date;


public class TweetData {
	public String url;
	public String image;
	public String time2;
	public String content;
	public String user;
	public Date time;
	public String id;
	public int days;
	
	public TweetData copy() {
		TweetData nTweet = new TweetData();
		nTweet.url = url;
		nTweet.image = image;
		nTweet.time2 = time2;
		nTweet.content = content;
		nTweet.user = user;
		nTweet.time = time;
		nTweet.id = id;
		nTweet.days = days;
		
		return nTweet;
	}
}

実装の上でのポイント

 このWebサービスですが、元々個人で運用されているサービスなので、あまり頻繁にアクセスするとサーバが落ちてしまいます。おそらくオレのせいで落ちてしまったことも何度か…。本当に申し訳ないです。

 最終的にアクセスを20秒間隔で行うことでなんとか最後まで実行することができました。また、途中で落ちてもいいように、それまで蓄積した結果を出力出来るよう、処理の大半をfinallyで行っています。ちょっと格好悪いけど、効率優先です。


 もし今後このサービスのAPIを試してみたい方、問い合わせ頻度に関しては格別に慎重に利用された方がよろしいと思います。*1


 上記ソースコードに関しては、まぁ大した内容じゃないですが良かったら参考にしてみてください。ちなみに、一応調べる分にはアカウントIDとかツイートな異様とかまで調べることができています。今回はプライバシーとかも考慮して生まれてからの日数のみの公開としました。あしからず。




結果分析

 単純計算で、一回25件ずつで計300回、合計7,500件のデータが取れたはずですが、「人生3万日しかない」の利用者数はおそらく1万は下らないはず…。となるとこの結果もいまいち怪しいです。もっと大量のデータが無いとちゃんとした結果は得られないでしょうから…もしちゃんと集計するつもりなら独自でDB構築してデータ収集しないとダメでしょうね。


 それはそれとして、出力された結果を見ると、下は生後6日、上は3万日到達、とまんまと幅広い結果が得られました。…どう考えたって生後6日とか大嘘だろ。年少2位の9歳程度は若干信憑性が出てきますが、これもツイートの内容を観察する限り大嘘ですね、どう考えても成人してるだろお前。


 一方で最年長の方ですが、最年長はなんと本日3万日目を迎えた人!…こちらも他のツイートを見る限り原宿を跋扈してたりとかなり若者な雰囲気です。最年長2位の方も萌え系アニメにハァハァしてたりとだいぶご盛んな様子。どう考えても嘘吐きです本当に有難うございます。ちなみに18〜50位まで同じ生年月日の人ばかり並んでいますが、アカウントは全て別の人です。2001年は12/29の十月十日前にSEXした夫婦がそんなに多かったのか、それともなんかのアニメキャラクターの誕生日でそれにあやかってるだけなのかはオレには分かりません。




結論

どいつもこいつも大嘘吐きだ!!




 まぁ薄々分かっていたことではありましたが、ちゃんとした結果が出ないとやっぱり残念ですね。ちなみにオレもこのサービス使ってましたが、割と真面目に書き込んでた方だと思います。もう飽きたんで今日明日あたり退会しちゃいますけどw。


 常々思いますが、Twitterは膨大なデータがあるからプログラマが色々試すにはもってこいですね。経験の浅い方ほど色々と試して欲しいと思います。

*1:でも凄いなぁ、オレよりだいぶ若いけど、自力でこんなちゃんとしたサービス作れて。