はてなカウンターのアクセス数ランキングを作成するプログラム

概要

はてなカウンターのアクセス数ランキングを作成するプログラムを書きました。
このプログラムは、はてなカウンターに接続してアクセス数情報を取得し、はてなダイアリーに入力可能な形式でアクセス数ランキングを出力します。
これまで本ブログではアクセス数ランキングを毎月手作業で作成しておりましたが、このプログラムによりアクセス数ランキングを自動的に作成することが可能になりました。

起動方法

このプログラムを起動するには、javaコマンドでSummarizeHatenaCounterクラスを実行します。

java SummarizeHatenaCounter
すると、アクセス数ランキングの作成に必要な項目の入力を求められるので、順に入力していきます。
はてなIDまたはメールアドレスを入力してください: nattou_curry_2
パスワードを入力してください: ●●●●●●●

カウンターID: タイトル
1: 何かしらの言語による記述を解析する日記
2: テスト日記
カウンターIDを選択してください: 1

年/月を入力してください(yyyy/MM): 2009/11

タイトルから除去する文字列を入力してください:  - 何かしらの言語による記述を解析する日記

出力先ファイルのパス名を入力してください: c:\test.txt
以下が表示されると、アクセス数ランキングの作成処理は完了です。
結果を「c:\test.txt」に出力しました。
指定した出力先ファイルに、はてなダイアリーに入力可能な形式でアクセス数ランキングが出力されます。

ソースコード

このプログラムは、以下の二つのソースコードからなります。
ソースコード 概要
SummarizeHatenaCounter.java はてなカウンターのアクセス数ランキング作成
HatenaCounterAccess.java はてなカウンター接続
SummarizeHatenaCounter.java
import java.io.*;
import java.util.*;
import java.text.*;

/**
 * はてなカウンターのアクセス数ランキング作成
 * @nattou_curry
 */
public class SummarizeHatenaCounter {
	
	public static void main( String[] args ) throws Exception {
		
		BufferedReader reader = new BufferedReader( new InputStreamReader( System.in ) );

		//////////////////////////////////////////////////////////////////
		// 画面入力からはてなIDまたはメールアドレスとパスワードを取得する。
		//////////////////////////////////////////////////////////////////

		System.out.print( "はてなIDまたはメールアドレスを入力してください: " );
		String name_in = reader.readLine();

		System.out.print( "パスワードを入力してください: " );
		String password_in = reader.readLine();

		System.out.println();

		//////////////////////////////////////////////////////////////////
		// はてなカウンターにログインする。
		//////////////////////////////////////////////////////////////////

		// ログインする(SSL)。
		String cookie = HatenaCounterAccess.loginWithSSL( name_in, password_in );
		
		// ログイン情報を取得する。
		Map<String, String> loginInfo = HatenaCounterAccess.getLoginInfo( cookie );
		
		// ログイン情報からはてなIDを取得する。
		String name = loginInfo.get( "name" );
		
		//////////////////////////////////////////////////////////////////
		// カウンターの一覧を取得する。
		//////////////////////////////////////////////////////////////////

		// カウンターの一覧を取得する。
		List<Map<String, String>> counterList = HatenaCounterAccess.getCounterList( cookie, name );
		
		//////////////////////////////////////////////////////////////////

		boolean inputOK;

		//////////////////////////////////////////////////////////////////
		// 画面入力により、カウンターIDを選択する。
		//////////////////////////////////////////////////////////////////

		String cid_in = null;

		// カウンターの一覧を表示する。
		System.out.println( "カウンターID: タイトル" );
		for ( int i = 0; i < counterList.size(); ++i ) {
			Map<String, String> counterInfo = counterList.get( i );
			
			String title = counterInfo.get( "title" );
			String cid = counterInfo.get( "cid" );
			
			System.out.println( cid + ": " + title );
		}

		// 適切な入力があるまで、処理をくりかえす。
		inputOK = false;
		while ( ! inputOK ) {
			// 入力プロンプトを表示する。
			System.out.print( "カウンターIDを選択してください: " );
			
			// 画面入力を取得する。
			cid_in = reader.readLine();
			
			// 適切なカウンターIDが入力されたことを確認する。
			for ( int i = 0; i < counterList.size(); ++i ) {
				Map<String, String> counterInfo = counterList.get( i );
				
				String cid = counterInfo.get( "cid" );
				
				if ( cid_in.equals( cid ) ) {
					// 適切なカウンターIDが入力された:
					inputOK = true;
					break;
				}
			}
		}
		
		System.out.println();

		//////////////////////////////////////////////////////////////////
		// 画面入力により、年/月を選択する。
		//////////////////////////////////////////////////////////////////
		
		String year = "";
		String month = "";
		
		SimpleDateFormat sdf_yyyyMM = new SimpleDateFormat( "yyyy/MM" );
		SimpleDateFormat sdf_yyyy = new SimpleDateFormat( "yyyy" );
		SimpleDateFormat sdf_MM = new SimpleDateFormat( "MM" );

		// 適切な入力があるまで、処理をくりかえす。
		inputOK = false;
		while ( ! inputOK ) {
			// 入力プロンプトを表示する。
			System.out.print( "年/月を入力してください(yyyy/MM): " );
			
			String yearmonth = reader.readLine();
			
			try {
				Date dt_yearmonth = sdf_yyyyMM.parse( yearmonth );
				
				year = sdf_yyyy.format( dt_yearmonth );
				month = sdf_MM.format( dt_yearmonth );
				
				inputOK = true;
			} catch ( ParseException e ) {
				/* inputNG */
			}
		}
		
		System.out.println();

		//////////////////////////////////////////////////////////////////
		// 1ヶ月前の年/月を決定する。
		//////////////////////////////////////////////////////////////////

		Calendar cal_prev = Calendar.getInstance();
		cal_prev.set( Calendar.YEAR, Integer.parseInt( year ) );
		cal_prev.set( Calendar.MONTH, Integer.parseInt( month ) - 1 );
		cal_prev.add( Calendar.MONTH, -1 );
		Date dt_prev = cal_prev.getTime();
		
		String prevYear = sdf_yyyy.format( dt_prev );
		String prevMonth = sdf_MM.format( dt_prev );;
		
		//////////////////////////////////////////////////////////////////
		// 画面入力により、タイトルから除去する文字列を入力する。
		//////////////////////////////////////////////////////////////////
		
		// 入力プロンプトを表示する。
		System.out.print( "タイトルから除去する文字列を入力してください: " );

		String exclude_from_title = reader.readLine();

		System.out.println();

		//////////////////////////////////////////////////////////////////
		// 画面入力により、出力先ファイルのパス名を入力する。
		//////////////////////////////////////////////////////////////////
		
		// 入力プロンプトを表示する。
		System.out.print( "出力先ファイルのパス名を入力してください: " );

		String fileName = reader.readLine();

		System.out.println();

		//////////////////////////////////////////////////////////////////
		// はてなカウンターにアクセスし、各種情報を取得する。
		//////////////////////////////////////////////////////////////////

		// URLの一覧を取得する。
		List<Map<String, String>> urlList = HatenaCounterAccess.getMonthlyURLList( cookie, name, cid_in, year, month );
		
		// 各URLのタイトルを取得する。
		for ( int i = 0; i < urlList.size() && i < 10; ++i ) {
			Map<String, String> searchWordInfo = urlList.get( i );
			
			String url = searchWordInfo.get( "url" );
			String title = HatenaCounterAccess.getTitle( url );
			
			// タイトルから指定された文字列を除去する。
			title = title.replaceFirst( exclude_from_title, "" );
			
			
			searchWordInfo.put( "title", title );
		}

		// 1ヵ月前のURLの一覧を取得する。
		List<Map<String, String>> prevUrlList = HatenaCounterAccess.getMonthlyURLList( cookie, name, cid_in, prevYear, prevMonth );
		
		// 検索語の一覧を取得する。
		List<Map<String, String>> searchWordList = HatenaCounterAccess.getMonthlySearchWordList( cookie, name, cid_in, year, month );

		// 検索語(単語)の一覧を取得する。
		List<Map<String, String>> searchWordSingleList = HatenaCounterAccess.getMonthlySearchWordSingleList( cookie, name, cid_in, year, month );
		
		//////////////////////////////////////////////////////////////////
		// 各URLのアクセスランクの1ヶ月前からの遷移を決定する。
		//////////////////////////////////////////////////////////////////
		
		for ( int i = 0; i < urlList.size() && i < 10; ++i ) {
			Map<String, String> urlInfo = urlList.get( i );
			
			String date = urlInfo.get( "date" );
			String url = urlInfo.get( "url" );
			
			String diff = "";
			if ( date.substring( 0, 7 ).equals( year + "/" + month ) ) {
				diff = "新";
			} else {
				
				diff = "↑";
				for ( int j = 0; j < prevUrlList.size() && j < 10; ++j ) {
					Map<String, String> prevUrlInfo = prevUrlList.get( j );
					String prevUrl = prevUrlInfo.get( "url" );
					
					if ( url.equals( prevUrl ) ) {
						if ( i < j ) {
							diff = "↑";
						} else if ( i == j ) {
							diff = "→";
						} else {
							diff = "↓";
						}
						break;
					}
				}
			}
			
			urlInfo.put( "diff", diff );
		}

		//////////////////////////////////////////////////////////////////
		
		StringBuffer buffer = new StringBuffer();
		buffer.append( "*[アクセス数]" + year + "年" + month + "月のアクセス数ランキング\r\n" );
		
		//////////////////////////////////////////////////////////////////
		// URLの一覧を元に、アクセス数上位10記事を作成する。
		//////////////////////////////////////////////////////////////////

		buffer.append( "**" + year + "年" + month + "月のアクセス数上位10記事\r\n" );
		buffer.append( "|*順位|*作成日|*記事|*回数|*比率|\r\n" );
		
		for ( int i = 0; i < urlList.size() && i < 10; ++i ) {
			Map<String, String> urlInfo = urlList.get( i );
			
			int no = i + 1;
			String diff = urlInfo.get( "diff" );
			String date = urlInfo.get( "date" );
			String url = urlInfo.get( "url" );
			String title = urlInfo.get( "title" );
			String count = urlInfo.get( "count" );
			String ratio = urlInfo.get( "ratio" );
			String b_entry_url = "http://b.hatena.ne.jp/entry/" + url;
			String b_image_url = "http://b.hatena.ne.jp/entry/image/" + url;
			
			// アクセスランクの遷移を色付けする。
			if ( diff.equals( "新" ) ) {
				diff = "<span style='color:#00BB00;font-weight:bold;'>新</span>";
			} else if ( diff.equals( "→" ) ) {
				diff = "→";
			} else if ( diff.equals( "↑" ) ) {
				diff = "<span style='color:#FF0000;'>↑</span>";
			} else if ( diff.equals( "↓" ) ) {
				diff = "<span style='color:#0000FF;'>↓</span>";
			}
			
			buffer.append( "|" + diff + " " + no + "|" + date +  "|" + "<a href='" + url + "'>" + title + "</a><a href='" + b_entry_url + "'><img src='" + b_image_url + "'>|" + count + "|" + ratio + "|\r\n" );
		}
		
		//////////////////////////////////////////////////////////////////
		// 検索語(単語)の一覧を元に、上位10検索語(単語)を作成する。
		//////////////////////////////////////////////////////////////////

		buffer.append( "**" + year + "年" + month + "月の上位10検索語(単語)\r\n" );
		buffer.append( "|*順位|*内容|*回数|*比率|\r\n" );

		for ( int i = 0; i < searchWordSingleList.size() && i < 10; ++i ) {
			Map<String, String> searchWordSingleInfo = searchWordSingleList.get( i );
			
			int no = i + 1;
			String search_word = searchWordSingleInfo.get( "search_word" );
			String count = searchWordSingleInfo.get( "count" );
			String ratio = searchWordSingleInfo.get( "ratio" );
			String searchdiary_link = "http://d.hatena.ne.jp/" + name + "/searchdiary?word=" + search_word;
			
			buffer.append( "|" + no + "|<a href='" + searchdiary_link + "'>" + search_word + "</a>|" + count + "|" + ratio + "|\r\n" );
		}

		//////////////////////////////////////////////////////////////////
		// 検索語の一覧を元に、上位10検索語を作成する。
		//////////////////////////////////////////////////////////////////

		buffer.append( "**" + year + "年" + month + "月の上位10検索語\r\n" );
		buffer.append( "|*順位|*内容|*回数|*比率|\r\n" );

		for ( int i = 0; i < searchWordList.size() && i < 10; ++i ) {
			Map<String, String> searchWordInfo = searchWordList.get( i );
			
			int no = i + 1;
			String search_word = searchWordInfo.get( "search_word" );
			String count = searchWordInfo.get( "count" );
			String ratio = searchWordInfo.get( "ratio" );
			String searchdiary_link = "http://d.hatena.ne.jp/" + name + "/searchdiary?word=" + search_word;
			
			buffer.append( "|" + no + "|<a href='" + searchdiary_link + "'>" + search_word + "</a>|" + count + "|" + ratio + "|\r\n" );
		}
		
		//////////////////////////////////////////////////////////////////

		buffer.append( "**おまけ\r\n" );

		//////////////////////////////////////////////////////////////////
		// 検索語(単語)の一覧を元に、上位10検索語(単語)に関連するリンクを作成する。
		//////////////////////////////////////////////////////////////////

		buffer.append( "上位10検索語(単語)に関連するリンクです。\r\n" );
		buffer.append( "|*順位|*はてなキーワード|*人気ブログ|\r\n" );
		for ( int i = 0; i < searchWordSingleList.size() && i < 10; ++i ) {
			Map<String, String> searchWordSingleInfo = searchWordSingleList.get( i );
			
			int no = i + 1;
			String search_word = searchWordSingleInfo.get( "search_word" );
			String keyword_link = "http://d.hatena.ne.jp/keyword/" + search_word;
			String hotblog_link = "http://k.hatena.ne.jp/hotblog/" + search_word;
			
			buffer.append( "|" + no + "|<a href='" + keyword_link + "'>" + search_word + "</a>|<a href='" + hotblog_link + "'>" + search_word + "</a>|\r\n" );
		}
		
		//////////////////////////////////////////////////////////////////
		// 検索語の一覧を元に、上位10検索語に関連するリンクを作成する。
		//////////////////////////////////////////////////////////////////

		buffer.append( "上位10検索語に関連するリンクです。\r\n" );
		buffer.append( "|*順位|*google検索|\r\n" );
			
		for ( int i = 0; i < searchWordList.size() && i < 10; ++i ) {
			Map<String, String> searchWordInfo = searchWordList.get( i );
			
			int no = i + 1;
			String search_word = searchWordInfo.get( "search_word" );
			String google_link = searchWordInfo.get( "google_link" );
			
			buffer.append( "|" + no + "|<a href='" + google_link + "'>" + search_word + "</a>|\r\n" );
		}
		
				
		//////////////////////////////////////////////////////////////////
		// 作成した内容をファイルに出力する。
		//////////////////////////////////////////////////////////////////

		BufferedWriter writer = null;
		try {
			writer = new BufferedWriter( new FileWriter( fileName ) );
			writer.write( buffer.toString() );
		} finally {
			if ( writer != null ) {
				writer.close();
			}
		}
		
		System.out.println( "結果を「" + fileName + "」に出力しました。" );
	}
}
HatenaCounterAccess
import java.net.*;
import javax.net.ssl.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
import java.util.zip.*;
import java.text.*;

/**
 * はてなカウンター接続
 * @author nattou_curry
 */
public class HatenaCounterAccess {
	
	// はてなカウンターのエンコーディング
	private static String ENCODING = "UTF-8";
	
	/**
	 * ログインする(SSL)。
	 * @param name はてなIDまたはメールアドレス
	 * @param password パスワード
	 * @return クッキー
	 */
	public static String loginWithSSL( String name, String password ) throws Exception {
		
		// クッキー
		String cookie = null;
		// クッキー編集用のマップ
		Map<String, String> cookiesMap = new HashMap<String, String>();
		
		// リダイレクト先URL
		String redirectURL;
		
		//////////////////////////////////////////////////////////////////
		// hatenaのログイン画面に接続し、Cookieを取得する。
		//////////////////////////////////////////////////////////////////
		
		HttpURLConnection conn = null;
		try {
			conn = openConnection( "https://www.hatena.ne.jp/login");
			
			// ログイン情報を送信する。
			String query = "name=" + URLEncoder.encode( name, ENCODING )
				+ "&password=" + URLEncoder.encode( password, ENCODING )
				+ "&location=" + URLEncoder.encode( "http://d.hatena.ne.jp/", ENCODING );
			conn.setDoOutput( true );
			conn.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded" );
			conn.setRequestProperty( "Content-Length", query.length() + "" );
			OutputStreamWriter out = new OutputStreamWriter( conn.getOutputStream(), ENCODING );
			out.write( query );
			out.flush();
			out.close();

			// クッキーを取得し、編集用のマップに設定する。
			putCookiesToMap( conn, cookiesMap );

			// 編集用のマップからクッキー文字列を作成する。
			cookie = cookiesMapToString( cookiesMap );
			
			// リダイレクト先URLを取得する。
			redirectURL = conn.getHeaderField( "Location" );
		} finally {
			// 接続を閉じる。
			closeConnection( conn );
		}
		
		return cookie;
	}
	
	/**
	 * レスポンスヘッダよりクッキーを取得し、編集用のマップに設定する。
	 * @param conn HTTP接続
	 * @param cookieMap クッキー編集用のマップ
	 */
	private static void putCookiesToMap( HttpURLConnection conn, Map<String, String> cookiesMap ) {

		List<String> cookies = null;

		// レスポンスヘッダより、クッキーを取得する。
		Map<String, List<String>> header = conn.getHeaderFields();
		for ( String key : header.keySet() ) {
			if ( key != null && key.compareToIgnoreCase( "set-cookie" ) == 0 ) {
				cookies = header.get( key );
				break;
			}
		}
		
		if ( cookies == null ) {
			// クッキーが取得できない場合:
			return;
		}
		
		// クッキーを分解し、編集用のマップに設定する。
		for ( int i = 0; i < cookies.size(); ++i ) {
			String cookieLine = cookies.get( i );
			String[] keyValuePairs = cookieLine.split( "; *" );
			
			for ( int j = 0; j < keyValuePairs.length; ++j ) {
				String keyValuePair = keyValuePairs[j];
				String[] keyValue = keyValuePair.split( "=", 2 );
				String key = keyValue[0];
				String value = null;
				
				if ( keyValue.length >= 2 ) {
					value = keyValue[1];
				}
				
				if ( "Domain".equals( key ) ) continue;
				if ( "HttpOnly".equals( key ) ) continue;
				if ( "Expires".equals( key ) ) continue;
				if ( "Secure".equals( key ) ) continue;
				if ( "Path".equals( key ) ) continue;

				if ( "EXPIRED".equals( value ) )continue;
				
				cookiesMap.put( key, value );
			}
		}
	}
	
	/**
	 * 編集用のマップからクッキー文字列を作成する。
	 * @param cookieMap クッキー編集用のマップ
	 * @return クッキー文字列
	 */
	private static String cookiesMapToString( Map<String, String> cookiesMap ) {
		
		StringBuffer cookie = new StringBuffer();
		for ( String key: cookiesMap.keySet() ) {
			String value = cookiesMap.get( key );
			
			if ( value != null ) {
				cookie.append( key + "=" + value + "; " );
			} else {
				cookie.append( key + "; " );
			}
		}
		
		return cookie.toString();
	}

	/**
	 * ログイン情報を取得する。
	 * @param cookie クッキー
	 * @param ログイン情報のマップ。name: はてなID。
	 */
	public static Map<String, String> getLoginInfo( String cookie ) throws Exception {

		String html;
		
		//////////////////////////////////////////////////////////////////
		// トップ画面に接続する。
		//////////////////////////////////////////////////////////////////

		HttpURLConnection conn = null;
		try {
			conn = openConnection( "http://counter.hatena.ne.jp/", cookie );
	
			// トップ画面のHTMLを取得する。
			html = getHTML( conn );
		} finally {
			// 接続を閉じる。
			closeConnection( conn );
		}
		
		//////////////////////////////////////////////////////////////////
		// トップ画面のHTMLより、ログイン情報を取得する。
		//////////////////////////////////////////////////////////////////

		// はてなIDを取得する。
		Pattern p = Pattern.compile( "ようこそ<a href=\".*?\" style=\".*?\"><font color=\".*?\">(.*?)</font></a>さん" );
		Matcher m = p.matcher( html );
		if ( ! m.find() ) {
			// はてなIDが見つからない:
			throw new Exception( "はてなIDが見つかりません。" );
		}
		String name = m.group( 1 );

		Map<String, String> loginInfo = new HashMap<String, String>();
		loginInfo.put( "name", name );
		
		return loginInfo;
	}
	
	/**
	 * カウンターの一覧を取得する。
	 * @param cookie クッキー
	 * @param name はてなID
	 * @param カウンターの一覧。title: カウンターのタイトル、cid: カウンターのID。
	 */
	public static List<Map<String, String>> getCounterList( String cookie, String name ) throws Exception {

		String html;
		
		//////////////////////////////////////////////////////////////////
		// カウンターの一覧画面に接続する。
		//////////////////////////////////////////////////////////////////

		HttpURLConnection conn = null;
		try {
			conn = openConnection( "http://counter.hatena.ne.jp/" + name + "/", cookie );
	
			// トップ画面のHTMLを取得する。
			html = getHTML( conn );
		} finally {
			// 接続を閉じる。
			closeConnection( conn );
		}
		
		//////////////////////////////////////////////////////////////////
		// カウンターの一覧画面のHTMLより、カウンター情報を取得する。
		//////////////////////////////////////////////////////////////////

		List<Map<String, String>> counterList = new ArrayList<Map<String, String>>();
		
		// はてなIDを取得する。
		Pattern p = Pattern.compile( "<td><a href=\"\\./\\?cid=(.*?)\">(.*?)</a></td>" );
		Matcher m = p.matcher( html );
		while ( m.find() ) {
			Map<String, String> counterInfo = new HashMap<String, String>();
			counterInfo.put( "cid", m.group( 1 ) );
			counterInfo.put( "title", m.group( 2 ) );
			
			counterList.add( counterInfo );
		}
		
		return counterList;
	}
	
	/**
	 * 月次のURLの一覧をアクセス回数の多い順に取得する。
	 * @param cookie クッキー
	 * @param name はてなID
	 * @param cid カウンターID
	 * @param year 年
	 * @param month 月
	 * @return 月次のURLの一覧。url:URL、count:アクセス回数、ratio:比率、date:アクセス日付
	 */
	public static List<Map<String, String>> getMonthlyURLList( String cookie, String name, String cid, String year, String month ) throws Exception {

		//////////////////////////////////////////////////////////////////
		// 年/月をフォーマットする。
		//////////////////////////////////////////////////////////////////

		int num_year = Integer.parseInt( year );
		int num_month = Integer.parseInt( month );

		Calendar cal_targetMonth = Calendar.getInstance();
		cal_targetMonth.set( Calendar.YEAR, num_year );
		cal_targetMonth.set( Calendar.MONTH, num_month - 1 );
		cal_targetMonth.set( Calendar.DAY_OF_MONTH, 1 );
		
		Date dt_targetMonth = cal_targetMonth.getTime();

		SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd" );
		String targetMonth = sdf.format( dt_targetMonth );
	
		//////////////////////////////////////////////////////////////////
		// URL画面に接続し、検索語情報を取得する。
		//////////////////////////////////////////////////////////////////

		String html;
		
		HttpURLConnection conn = null;
		try {
			String url = "http://counter.hatena.ne.jp/" + name + "/report?cid=" + cid + "&date=" + targetMonth + "&mode=summary&target=url&type=monthly";
			conn = openConnection( url, cookie );
			
			// URL画面のHTMLを取得する。
			html = getHTML( conn );
		} finally {
			// 接続を閉じる。
			closeConnection( conn );
		}
		
		List<Map<String, String>> urlList = new ArrayList<Map<String, String>>();
		
		Pattern p = Pattern.compile( "<td class=\"maincell\" nowrap><a href=\"([^\"]*?([0-9]{4})([0-9]{2})([0-9]{2})[^\"]*?)\">.*?</a></td>.*?<td style=\"text-align: right\">(.*?)</td>.*?<td style=\"text-align: right\">(.*?)</td>", Pattern.DOTALL );
		
		Matcher m = p.matcher( html );
		while ( m.find() ) {
			String url = m.group( 1 );
			String url_year = m.group( 2 );
			String url_month = m.group( 3 );
			String url_day = m.group( 4 );
			String count = m.group( 5 );
			String ratio = m.group( 6 );
			
			String date = url_year + "/" + url_month + "/" + url_day;
			
			Map<String, String> urlInfo = new HashMap<String, String>();
			urlInfo.put( "url", url );
			urlInfo.put( "count", count );
			urlInfo.put( "ratio", ratio );
			urlInfo.put( "date", date );
			
			urlList.add( urlInfo );
		}
		
		return urlList;
	}
	
	/**
	 * 指定したURLのページのタイトルを取得する。
	 * @param url URL
	 * @return タイトル
	 */
	public static String getTitle( String url ) throws Exception {

		String html;
		
		HttpURLConnection conn = null;
		try {
			conn = openConnection( url );
			
			// URL画面のHTMLを取得する。
			html = getHTML( conn );
		} finally {
			// 接続を閉じる。
			closeConnection( conn );
		}
		
		Pattern p = Pattern.compile( "<title>(.*?)</title>", Pattern.CASE_INSENSITIVE );
		Matcher m = p.matcher( html );
		
		String title = null;
		if ( m.find() ) {
			title = m.group( 1 );
		}
		
		return title;
	}
	
	/**
	 * 月次の検索語(単語)の一覧を検索回数の多い順に取得する。
	 * @param cookie クッキー
	 * @param name はてなID
	 * @param cid カウンターID
	 * @param year 年
	 * @param month 月
	 * @return 月次の検索語(単語)の一覧。google_link: googleの検索リンク、hatena_link: はてなの検索リンク、search_word: 検索語、count: 検索回数、ratio: 比率。
	 */
	public static List<Map<String, String>> getMonthlySearchWordSingleList( String cookie, String name, String cid, String year, String month ) throws Exception {

		//////////////////////////////////////////////////////////////////
		// 年/月をフォーマットする。
		//////////////////////////////////////////////////////////////////
		
		int num_year = Integer.parseInt( year );
		int num_month = Integer.parseInt( month );

		Calendar cal_targetMonth = Calendar.getInstance();
		cal_targetMonth.set( Calendar.YEAR, num_year );
		cal_targetMonth.set( Calendar.MONTH, num_month - 1 );
		cal_targetMonth.set( Calendar.DAY_OF_MONTH, 1 );
		
		Date dt_targetMonth = cal_targetMonth.getTime();

		SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd" );
		String targetMonth = sdf.format( dt_targetMonth );
	
		//////////////////////////////////////////////////////////////////
		// 検索語画面に接続し、検索語一覧を取得する。
		//////////////////////////////////////////////////////////////////

		String html;
		
		HttpURLConnection conn = null;
		try {
			String url = "http://counter.hatena.ne.jp/" + name + "/report?cid=" + cid + "&date=" + targetMonth + "&mode=summary&target=searchwordsingle&type=monthly";
			conn = openConnection( url, cookie );
			
			// 検索語画面のHTMLを取得する。
			html = getHTML( conn );
		} finally {
			// 接続を閉じる。
			closeConnection( conn );
		}
		
		List<Map<String, String>> searchWordList = new ArrayList<Map<String, String>>();
		
		Pattern p = Pattern.compile( "<td class=\"maincell\" nowrap><span class=\"search_link\"><a href=\"(.*?)\" target=\"_blank\"><img src=\"/images/google_search.gif\" alt=\"\\[G\\]\"></a><a href=\"(.*?)\" target=\"_blank\"><img src=\"/images/hatena_search.gif\" alt=\"\\[\\?\\]\"></a></span>(.*?)</td>.*?<td style=\"text-align: right\">(.*?)</td>.*?<td style=\"text-align: right\">(.*?)</td>", Pattern.DOTALL );
		Matcher m = p.matcher( html );
		while ( m.find() ) {
			String google_link = m.group( 1 );
			String hatena_link = m.group( 2 );
			String search_word = m.group( 3 );
			String count = m.group( 4 );
			String ratio = m.group( 5 );
			
			Map<String, String> searchWordInfo = new HashMap<String, String>();
			searchWordInfo.put( "google_link", google_link);
			searchWordInfo.put( "hatena_link", hatena_link );
			searchWordInfo.put( "search_word", search_word );
			searchWordInfo.put( "count", count );
			searchWordInfo.put( "ratio", ratio );
			
			searchWordList.add( searchWordInfo );
		}
		
		return searchWordList;
	}

	/**
	 * 月次の検索語の一覧を検索回数の多い順に取得する。
	 * @param cookie クッキー
	 * @param name はてなID
	 * @param cid カウンターID
	 * @param year 年
	 * @param month 月
	 * @return 月次の検索語の一覧。google_link: googleの検索リンク、hatena_link: はてなの検索リンク、search_word: 検索語、count: 検索回数、ratio: 比率。
	 */
	public static List<Map<String, String>> getMonthlySearchWordList( String cookie, String name, String cid, String year, String month ) throws Exception {

		//////////////////////////////////////////////////////////////////
		// 年/月をフォーマットする。
		//////////////////////////////////////////////////////////////////

		int num_year = Integer.parseInt( year );
		int num_month = Integer.parseInt( month );

		Calendar cal_targetMonth = Calendar.getInstance();
		cal_targetMonth.set( Calendar.YEAR, num_year );
		cal_targetMonth.set( Calendar.MONTH, num_month - 1 );
		cal_targetMonth.set( Calendar.DAY_OF_MONTH, 1 );
		
		Date dt_targetMonth = cal_targetMonth.getTime();

		SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd" );
		String targetMonth = sdf.format( dt_targetMonth );
	
		//////////////////////////////////////////////////////////////////
		// 検索語画面に接続し、検索語一覧を取得する。
		//////////////////////////////////////////////////////////////////

		String html;
		
		HttpURLConnection conn = null;
		try {
			String url = "http://counter.hatena.ne.jp/" + name + "/report?cid=" + cid + "&date=" + targetMonth + "&mode=summary&target=searchword&type=monthly";
			conn = openConnection( url, cookie );
			
			// 検索語画面のHTMLを取得する。
			html = getHTML( conn );
		} finally {
			// 接続を閉じる。
			closeConnection( conn );
		}
		
		List<Map<String, String>> searchWordList = new ArrayList<Map<String, String>>();
		
		Pattern p = Pattern.compile( "<td class=\"maincell\" nowrap><span class=\"search_link\"><a href=\"(.*?)\" target=\"_blank\"><img src=\"/images/google_search.gif\" alt=\"\\[G\\]\"></a><a href=\"(.*?)\" target=\"_blank\"><img src=\"/images/hatena_search.gif\" alt=\"\\[\\?\\]\"></a></span>(.*?)</td>.*?<td style=\"text-align: right\">(.*?)</td>.*?<td style=\"text-align: right\">(.*?)</td>", Pattern.DOTALL );
		Matcher m = p.matcher( html );
		while ( m.find() ) {
			String google_link = m.group( 1 );
			String hatena_link = m.group( 2 );
			String search_word = m.group( 3 );
			String count = m.group( 4 );
			String ratio = m.group( 5 );
			
			Map<String, String> searchWordInfo = new HashMap<String, String>();
			searchWordInfo.put( "google_link", google_link);
			searchWordInfo.put( "hatena_link", hatena_link );
			searchWordInfo.put( "search_word", search_word );
			searchWordInfo.put( "count", count );
			searchWordInfo.put( "ratio", ratio );
			
			searchWordList.add( searchWordInfo );
		}
		
		return searchWordList;
	}
	
	/**
	 * HTTP接続を開く。
	 * @param requestURL リクエストURL
	 * @return HTTP接続
	 */
	public static HttpURLConnection openConnection( String requestURL ) throws Exception {
		return openConnection( requestURL, null );
	}
	
	/**
	 * HTTP接続を開く。
	 * @param requestURL リクエストURL
	 * @param cookie クッキー
	 * @return HTTP接続
	 */
	public static HttpURLConnection openConnection( String requestURL, String cookie ) throws Exception {
		URL url = new URL( requestURL );
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setInstanceFollowRedirects( false );
		conn.setRequestProperty( "User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6" );
		conn.setRequestProperty( "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" );
		conn.setRequestProperty( "Accept-Language", "ja,en-us;q=0.7,en;q=0.3" );
		conn.setRequestProperty( "Accept-Encoding", "gzip,deflate" );
		conn.setRequestProperty( "Accept-Charset", "Shift_JIS,utf-8;q=0.7,*;q=0.7" );
		conn.setRequestProperty( "Keep-Alive", "300" );
		conn.setRequestProperty( "Connection", "keep-alive" );
		if ( cookie != null ) {
			conn.setRequestProperty( "Cookie", cookie );
		}
		
		return conn;
	}
	
	/**
	 * HTTP接続を閉じる。
	 * @param conn HTTP接続
	 */
	public static void closeConnection( HttpURLConnection conn ) {
		try {
			OutputStream out = conn.getOutputStream();
			out.close();
		} catch ( Exception e ) {}

		try {
			InputStream in = conn.getInputStream();
			in.close();
		} catch ( Exception e ) {}
	}
	/**
	 * 接続先のHTMLを取得する。
	 * @param conn 接続
	 * @return HTML
	 */
	public static String getHTML( HttpURLConnection conn ) throws Exception {
		
		//////////////////////////////////////////////////////////////////
		// レスポンスヘッダより、エンコーディングを取得する。
		//////////////////////////////////////////////////////////////////
		
		String encoding = "";
		
		String contentType = conn.getHeaderField( "Content-Type" );
		if ( contentType != null ) {	
			Pattern p = Pattern.compile( ".*; charset=(.*)" );
			Matcher m = p.matcher( contentType );
			
			if ( m.find() ) {
				encoding = m.group( 1 );
			}
		}

		//////////////////////////////////////////////////////////////////
		// レスポンスヘッダより、GZIPを使用しているか判定する。
		//////////////////////////////////////////////////////////////////

		boolean usingGZIP = false;
		
		List<String> contentEncodings = conn.getHeaderFields().get( "Content-Encoding" );
		if ( contentEncodings != null ) {
			for ( int i = 0; i < contentEncodings.size(); ++i ) {
				String contentEncoding = contentEncodings.get( i );
				if ( contentEncoding.equals( "gzip" ) ) {
					usingGZIP = true;
					break;
				}
			}
		}
			
		//////////////////////////////////////////////////////////////////
		// レスポンスデータを取得する。
		//////////////////////////////////////////////////////////////////

		StringBuffer buf = new StringBuffer();

		BufferedReader in = null;
		try {
			// GZIPを使用しているかどうかにより、レスポンスの入力方法を変える。
			if ( usingGZIP ) {
				// GZIPを使用している:
				in = new BufferedReader(
					new InputStreamReader(
						new GZIPInputStream( conn.getInputStream() )
						, encoding ) );
			} else {
				// GZIP を使用していない。
				in = new BufferedReader(
					new InputStreamReader( conn.getInputStream(), encoding ) );
			}
				
			int c;
			while ( ( c = in.read() ) != -1 ) {
				buf.append( (char) c );
			}
			
		} finally { 
			if ( in != null ) {
				in.close();
			}
		}
		
		return buf.toString();
	}
	
	/**
	 * 【デバッグ用】レスポンスヘッダを表示する。
	 * @param conn HTTP接続
	 */
	private static void printHeader( HttpURLConnection conn ) {
		Map<String, List<String>> header = conn.getHeaderFields();
		for ( String key : header.keySet() ) {
			List<String> list = header.get( key );
			System.out.println( "[" + key + "]" );
			for ( int i = 0; i < list.size(); ++i ) {
				System.out.println( list.get( i ) );
			}
		}
	}
	
}