読書メーターとブクログに登録した本&コメントを同期するプログラムをバージョンアップしました

先日のブクログのリニューアルに対応し、読書メーターブクログを同期するプログラムをバージョンアップしました。
使い方は以前に作成したプログラムと同様ですので、詳しくは読書メーターとブクログに登録した本&コメントを同期するプログラムを作ったよを参照ください。

ソースコード

以下、バージョンアップ後のソースコードです。

DokushoMeterBooklogMatching.java
import java.io.*;
import java.util.*;

/**
 * 読書メーター・ブクログの比較・同期プログラム
 * @author nattou_curry
 */
public class DokushoMeterBooklogMatching {
	
	/**
	 * メインルーチン
	 */
	public static void main( String[] args ) throws Exception {
		
		System.out.println( "■■読書メータとブクログの情報を比較します。■■" );
		System.out.println();
		
		//////////////////////////////////////////////////////////
		// アカウント情報を取得取得する。
		//////////////////////////////////////////////////////////

		String mail_DokushoMeter = "";		// メールアドレス(読書メーター)
		String password_DokushoMeter = "";	// パスワード(読書メーター)
		String userid_Booklog = "";			// ユーザID(ブクログ)
		String password_Booklog = "";		// パスワード(ブクログ)
		
		boolean accountOK = false;		// アカウント情報取得成功フラグ

		// 引数を確認する。
		if ( args.length >= 1 && args[0].startsWith( "-l" ) ) {
			// 「-l」が指定されている:
		
			//////////////////////////////////////////////////////////
			// アカウント情報をアカウントファイルから取得する。
			//////////////////////////////////////////////////////////

			// アカウントファイルの名前を取得する。
			String accountFile = args[0].substring( 2 );
			
			BufferedReader reader = null;
			try {
				// アカウントファイルを開く。
				reader = new BufferedReader( new FileReader( accountFile ) );
				
				// アカウントファイルからアカウント情報を読み取る。
				mail_DokushoMeter = reader.readLine();
				password_DokushoMeter = reader.readLine();
				userid_Booklog = reader.readLine();
				password_Booklog = reader.readLine();

				// アカウント情報取得成功。
				accountOK = true;
				System.out.println( "アカウント情報をファイルから取得しました。ファイル名=[" + accountFile + "]" );
				
			} catch ( IOException e ) {
				System.out.println( "アカウント情報を正常に読み込めませんでした。ファイル名=[" + accountFile + "]"  );
				System.out.println();
			} finally {
				// ファイルを閉じる。
				if ( reader != null ) {
					try {
						reader.close();
					} catch ( IOException e ) {
						/* 無視 */
					}
				}
			}
		}

		if ( ! accountOK ) {
			
			//////////////////////////////////////////////////////////
			// アカウント情報を入力してもらう。
			//////////////////////////////////////////////////////////
			
			System.out.println( "アカウント情報を入力してください。" );
			System.out.println( "■読書メーター" );
			mail_DokushoMeter = getInput( "メールアドレス: " );
			password_DokushoMeter = getInput( "パスワード: " );
			System.out.println( "■ブクログ" );
			userid_Booklog = getInput( "ユーザID: " );
			password_Booklog = getInput( "パスワード: " );
		}
		
		System.out.println();
		
		//////////////////////////////////////////////////////////
		// 読書メータに登録した本の情報を取得する。
		//////////////////////////////////////////////////////////
		
		Map<String, Map<String, String>> dokushoMeter = getDokushoMeterBookInfo( mail_DokushoMeter, password_DokushoMeter );
		
		//////////////////////////////////////////////////////////
		// ブクログに登録した本の情報を取得する。
		//////////////////////////////////////////////////////////
		
		Map<String, Map<String, String>> booklog = getBooklogBookInfo( userid_Booklog, password_Booklog );
		
		//////////////////////////////////////////////////////////
		// 読書メータとブクログの登録内容を比較し、
		// 以下の条件に該当する本の一覧を作成する。
		// [条件]
		// ・読書メータに登録しているが、ブクログには登録していない。
		// ・読書メータとブクログでコメントが異なる。
		//////////////////////////////////////////////////////////
		
		List<String> diffList = new ArrayList<String>();
		
		// 読書メーターに登録した全ての本について、以下を繰り返す。
		for ( String isbn : dokushoMeter.keySet() ) {
			// ブクログに同じ本を登録していることを確認する。
			if ( ! booklog.containsKey( isbn ) ) {
				// ブクログに登録していない:
				diffList.add( isbn );
				
			} else {
			
				// 読書メータとブクログのコメントが一致することを確認する。
				String dokushoComment = dokushoMeter.get( isbn ).get( "comment" );
				String booklogComment = booklog.get( isbn ).get( "comment" );
				if ( ! dokushoComment.equals( booklogComment ) ) {
					// コメントが異なる:
					diffList.add( isbn );
				}
			}
		}
		
		//////////////////////////////////////////////////////////
		// 比較結果を出力
		//////////////////////////////////////////////////////////
		
		// 読書メータとブクログの本の情報に違いがあることを確認する。
		int diffSize = diffList.size();
		if ( diffSize == 0 ) {
			// 違いがない:
			System.out.println( "●違いは見つかりませんでした。" );
			// 処理を終了する。
			return;
			
		}
		
		// 違いがある本のタイトルを出力する。
		System.out.println( "●" + diffSize + "冊の本で違いが見つかりました。" );
		for ( int i = 0; i < diffList.size(); ++i ) {
			String isbn = diffList.get( i );
			String title = dokushoMeter.get( isbn ).get( "title" );
			System.out.println( "・" + title );
		}
		System.out.println();
		
		//////////////////////////////////////////////////////////
		// 読書メータのコメントをブクログに反映する。
		//////////////////////////////////////////////////////////
		
		System.out.println( "■■読書メータのコメントをブクログに反映します。■■" );

		// 処理を継続するかユーザに確認する。
		String result = getInput( "読書メーターのコメントをブクログに反映してよろしいですか?(y or n): " );
		System.out.println();
		result = result.toLowerCase();
		if ( ! result.equals( "y" ) ) {
			// ユーザが処理の中断を選ぶ:
			System.out.println( "処理を中断しました。" );
			return;
		}
		
		// 全ての違いがある本について以下を繰り返し、更新する本の情報の一覧を作成する。
		List<Map<String, String>> updateBooks = new ArrayList<Map<String, String>>();
		for ( int i = 0; i < diffList.size(); ++i ) {
			String isbn = diffList.get( i );
			
			// 読書メーターのコメントを取得する。
			String comment = dokushoMeter.get( isbn ).get( "comment" );
			
			String cate;
			String rank;
			String status;
			String tags;
			String create_on_y;
			String create_on_m;
			String create_on_d;
			String create_on_h;
			String create_on_i;
			String create_on_s;
			// ブクログに本が登録されていることを確認する。
			if ( booklog.containsKey( isbn ) ) {
				// ブクログのカテゴリを取得する。
				cate = booklog.get( isbn ).get( "cate" );
				// ブクログのランクを取得する。
				rank = booklog.get( isbn ).get( "rank" );
				// ブクログの読書状況を取得する。
				status = booklog.get( isbn ).get( "status" );
				// ブクログのタグを取得する。
				tags = booklog.get( isbn ).get( "tags" );
				// ブクログの登録日時を取得する。
				create_on_y = booklog.get( isbn ).get( "create_on_y" );
				create_on_m = booklog.get( isbn ).get( "create_on_m" );
				create_on_d = booklog.get( isbn ).get( "create_on_d" );
				create_on_h = booklog.get( isbn ).get( "create_on_h" );
				create_on_i = booklog.get( isbn ).get( "create_on_i" );
				create_on_s = booklog.get( isbn ).get( "create_on_s" );
			} else {
				cate = "0";
				rank = "0";
				status = "0";
				tags = "";
				create_on_y = "";
				create_on_m = "";
				create_on_d = "";
				create_on_h = "";
				create_on_i = "";
				create_on_s = "";
			}
			
			// 更新する本の情報をマップに格納する。
			Map<String, String> updateInfo = new HashMap<String, String>();
			updateInfo.put( "isbn", isbn );
			updateInfo.put( "comment", comment );
			updateInfo.put( "cate", cate );
			updateInfo.put( "rank", rank );
			updateInfo.put( "status", status );
			updateInfo.put( "tags", tags );
			updateInfo.put( "create_on_y", create_on_y );
			updateInfo.put( "create_on_m", create_on_m );
			updateInfo.put( "create_on_d", create_on_d );
			updateInfo.put( "create_on_h", create_on_h );
			updateInfo.put( "create_on_i", create_on_i );
			updateInfo.put( "create_on_s", create_on_s );
			
			
			// 更新する本の情報を一覧に追加する。
			updateBooks.add( updateInfo );
		}

		// ブクログの更新を行う。
		updateBooklogBooks( userid_Booklog, password_Booklog, updateBooks );

		System.out.println( "ブクログの更新を終了しました。" );
	}
	
	/**
	 * 読書メータに登録した本の情報を取得する。
	 * @param mail メールアドレス
	 * @param password パスワード
	 * @return 本の情報
	 */
	public static Map<String, Map<String, String>> getDokushoMeterBookInfo( String mail, String password ) throws Exception {

		// 読書メーターにログインする。
		String cookie = DokushoMeterAccess.login( mail, password );
		
		// ログイン情報を取得し、ログインに成功したことを確認する。
		Map<String, String> loginInfo = DokushoMeterAccess.getLoginInfo( cookie );
		if ( ! mail.equals( loginInfo.get( "mail" ) ) ) {
			// ログイン失敗:
			throw new Exception( "ログイン失敗" );
		}
		
		// ログイン情報からユーザIDを取得する。
		String userid = loginInfo.get( "userid" );
		
		// 読み終わった本の一覧を取得する。
		String[] bookList = DokushoMeterAccess.getBookList( cookie, userid );
		
		// 全ての本について、以下を繰り返す。
		Map<String, Map<String, String>> bookInfo = new HashMap<String, Map<String, String>>();
		for ( int i = 0; i < bookList.length; ++i ) {
			String isbn = bookList[i];
			// 本のコメントを取得する
			Map<String, String> info = DokushoMeterAccess.getBookInfo( cookie, isbn );
			
			// 本の情報をマップに格納する。
			bookInfo.put( isbn, info );
		}
		
		return bookInfo;
	}
		
	/**
	 * ブクログに登録した本の情報を取得する。
	 * @param userid ユーザID
	 * @param password パスワード
	 * @return 本の情報
	 */
	public static Map<String, Map<String, String>> getBooklogBookInfo( String userid, String password ) throws Exception {
		
		// ブクログからクッキーを取得する。
		String cookie = BooklogAccess.getCookie();
		
		// ブクログにログインする。
		BooklogAccess.login( cookie, userid, password );
		
		// ログイン情報を取得し、ログインに成功したことを確認する。
		Map loginInfo = BooklogAccess.getLoginInfo( cookie );
		if ( ! userid.equals( loginInfo.get( "username" ) ) ) {
			// ログイン失敗:
			throw new Exception( "ログイン失敗" );
		}
		
		// 本一覧を取得する。
		String[] itemList = BooklogAccess.getBookList( cookie );

		// 全ての本について、以下を繰り返す。
		Map<String, Map<String, String>> bookInfo = new HashMap<String, Map<String, String>>();
		for ( int i = 0; i < itemList.length; ++i ) {
			String isbn = itemList[i];
			// 本の情報を取得する
			Map<String, String> info = BooklogAccess.getBookInfo( cookie, isbn );
			// 本の情報をマップに格納する。
			bookInfo.put( isbn, info );
		}
		
		return bookInfo;
	}
		
	/**
	 * ブクログの本の情報を更新する。
	 * @param userid ユーザID
	 * @param password パスワード
	 * @param books 本の情報
	 * @return 本の情報
	 */
	public static void updateBooklogBooks( String userid, String password, List<Map<String, String>> updateBooks ) throws Exception {
		// ブクログからクッキーを取得する。
		String cookie = BooklogAccess.getCookie();
		
		// ブクログにログインする。
		BooklogAccess.login( cookie, userid, password );
		
		// ログイン情報を取得し、ログインに成功したことを確認する。
		Map loginInfo = BooklogAccess.getLoginInfo( cookie );
		if ( ! userid.equals( loginInfo.get( "username" ) ) ) {
			// ログイン失敗:
			throw new Exception( "ログイン失敗" );
		}
		
		// 本の情報を更新する。
		for ( int i = 0; i < updateBooks.size(); ++i ) {
			Map<String, String> book = updateBooks.get( i );
			BooklogAccess.updateBook( cookie, book );
		}
		
	}

	/**
	 * ユーザの入力値を取得する。
	 * @param prompt 表示するプロンプト
	 * @return 入力値
	 */
	private static String getInput( String prompt ) throws Exception {
		System.out.print( prompt );
		BufferedReader reader = new BufferedReader( new InputStreamReader( System.in ) );
		return reader.readLine();
	}

}
DokushoMeterAccess.java
import java.net.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;

/**
 * 読書メーター接続
 * @author nattou_curry
 */
public class DokushoMeterAccess {
	
	// 読書メータのエンコード
	private static final String ENCODING = "UTF-8";
	
	/**
	 * ログインする。
	 * @param mail メールアドレス
	 * @param password パスワード
	 * @return クッキー
	 */
	public static String login( String user, String password ) throws Exception {
		
		// ログイン画面への接続を開く。
		HttpURLConnection conn = openConnection( "/login" );
		
		// ログイン情報を送信する。
		String query = "mail=" + user + "&password=" + password + "&work=";
		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();
		
		// レスポンスヘッダより、クッキーを取得する。
		Map<String, List<String>> header = conn.getHeaderFields();
		List<String> cookies = header.get( "Set-Cookie" );
		if ( cookies == null ) {
			// クッキーが取得できない。
			throw new Exception( "ログイン失敗" );
		}
		String cookie = "";
		for ( int i = 0; i < cookies.size(); ++i ) {
			cookie += cookies.get( i ) + "; ";
		}
		
		// 接続を閉じる。
		closeConnection( conn );
		
		return cookie;
	}
	
	
	/**
	 * ログイン情報を取得する。
	 * @param cookie クッキー
	 * @param ログイン情報のマップ。userid: ユーザID、username: ユーザ名。
	 */
	public static Map<String, String> getLoginInfo( String cookie ) throws Exception {
		
		// トップ画面への接続を開く。
		HttpURLConnection conn = openConnection( "/account", cookie );
	
		// トップ画面のHTMLを取得する。
		String html = getHTML( conn );
		
		// 接続を閉じる。
		closeConnection( conn );
		
		// HTMLからログイン情報を見つける。
		Pattern p = Pattern.compile( "<a href=\"/u/([^\"]*)\">([^<]*)</a>でログイン中" );
		Matcher m = p.matcher( html );
		if ( ! m.find() ) {
			// ログイン情報が見つからない:
			throw new Exception( "ログインしていません。" );
		}
		
		// ログイン情報をマップに設定する。
		Map<String, String> loginInfo = new HashMap<String, String>();
		loginInfo.put( "userid", m.group( 1 ) );
		
		p = Pattern.compile( "<th><label for=\"mail\">メールアドレス</label></th>[\\r\\n\\t ]*<td>([^ ]*) \\[<a href=\"/mail_edit.php\">変更する</a>\\]</td>" );
		m = p.matcher( html );
		if ( ! m.find() ) {
			// メールアドレスが見つからない:
			throw new Exception( "メールアドレスが見つかりません。" );
		}
		loginInfo.put( "mail", m.group( 1 ) );
		
		return loginInfo;
	}

	/**
	 * 読み終わった本の一覧を取得する。
	 * @param cookie クッキー
	 * @param userid ユーザID
	 * @return 本(isbn)の一覧
	 */ 
	public static String[] getBookList( String cookie, String userid ) throws Exception {
		
		List<String> booklist = new ArrayList<String>();	// 本(isbn)の一覧
		int page = 1;	// ページ番号
		
		// 「読み終わった本の一覧画面」の全ページについて、以下を繰り返す。
		while ( true ) {
			// 読み終わった本の一覧画面に接続する。
			HttpURLConnection conn = openConnection(  "/u/" + userid + "/booklist&p=" + page, cookie);

			// 読み終わった本の一覧画面のHTMLを取得する。
			String html = getHTML( conn );

			// 接続を閉じる。
			closeConnection( conn );
		
			// HTMLに本の情報が含まれていることを確認する。
			if ( html.contains( "まだ登録した本はありません。" ) ) {
				// 本の情報が含まれていない:
				
				// 処理を中断する。
				break;
			}
			
			// 本の情報を取得する。
			Pattern p = Pattern.compile( "<div class=\"book\"><a href=\"/b/([^\"]*)\">" );
			Matcher m = p.matcher( html );
			while ( m.find() ) {
				// 本(isbn)を一覧に追加する。
				booklist.add( m.group( 1 ) );
			}
		
			// 次のページに進む。
			++page;
		}
		
		return booklist.toArray( new String[0] );
	}
	
	/**
	 * 本の情報を取得する。
	 * @param cookie クッキー
	 * @param isbn 本のISBN
	 * @return 本の情報のマップ。title: タイトル、comment: コメント。
	 */
	public static Map<String, String> getBookInfo( String cookie, String isbn ) throws Exception {

		// 本の画面に接続する。
		HttpURLConnection conn = openConnection( "/b/" + isbn, cookie );
	
		// 本の画面からHTMLを取得する。
		String html = getHTML( conn );

		// 接続を閉じる。
		closeConnection( conn );

		Pattern p;
		Matcher m;
		
		// HTMLからタイトルを取得する。
		p = Pattern.compile( "<h1 id=\"title\">([^<]*)</h1>" );
		m = p.matcher( html );
		m.find();
		String title = m.group( 1 );
		
		// HTMLからコメントを取得する。
		p = Pattern.compile( "<input type=\"text\" name=\"comment\" value=\"([^\"]*)\" class=\"input_b_comment\">" );
		m = p.matcher( html );
		m.find();
		String comment = m.group( 1 );
		
		// 本の情報をマップに格納する。
		Map<String, String> info = new HashMap<String, String>();
		info.put( "title", title );
		info.put( "comment", comment );
		
		return info;
	}

	/**
	 * 読書メーターへの接続を開く。
	 * @param requestURI リクエストURI
	 */
	public static HttpURLConnection openConnection( String requestURI ) throws Exception {
		return openConnection( requestURI, null );
	}
	
	/**
	 * 読書メーターへの接続を開く。
	 * @param requestURI リクエストURI
	 * @param cookie クッキー
	 */
	public static HttpURLConnection openConnection( String requestURI, String cookie ) throws Exception {
		URL url = new URL( "http://book.akahoshitakuya.com" + requestURI );
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setInstanceFollowRedirects( false );
		conn.setRequestProperty( "Host", "book.akahoshitakuya.com" );
		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" );
		conn.setRequestProperty( "Referer", "http://book.akahoshitakuya.com" );
		if ( cookie != null ) {
			conn.setRequestProperty( "Cookie", cookie );
		}
		
		return conn;
	}
	
	/**
	 * 読書メーターへの接続を閉じる。
	 * @param conn 接続
	 */
	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 {
		BufferedReader in = new BufferedReader(
			new InputStreamReader( conn.getInputStream(), ENCODING ) );
		StringBuffer buf = new StringBuffer();

		int c;
		while ( ( c = in.read() ) != -1 ) {
			buf.append( (char) c );
		}
		in.close();
		
		return buf.toString();
	}
}
BooklogAccess.java
import java.net.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;

/**
 * ブクログ接続
 * @author nattou_curry
 */
public class BooklogAccess {

	// ブクログのエンコード
	private static final String ENCODING = "UTF-8";
	
	/**
	 * クッキーを取得する。
	 * @reurn クッキー
	 */
	public static String getCookie() throws Exception {
		
		// トップ画面への接続を開く。
		HttpURLConnection conn = openConnection( "/" );
		
		// クッキーを取得する。
		String cookie = conn.getHeaderField( "Set-Cookie" );
		
		// 接続を閉じる。
		closeConnection( conn );
		
		return cookie;
	}

	/**
	 * ログインする。
	 * @param userid ユーザID
	 * @param password パスワード
	 */
	public static void login( String cookie, String userid, String password ) throws Exception {
		
		// ログイン画面への接続を開く。
		HttpURLConnection conn = openConnection( "/login", cookie );
		
		// ログイン情報を送信する。
		String query = "account=" + userid + "&password=" + password + "&st=1";
		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();

		// 接続を閉じる。
		closeConnection( conn );
	}
	
	/**
	 * ログイン情報を取得する。
	 * @param cookie クッキー
	 * @param ログイン情報を設定したマップ。username: ユーザ名。
	 * @return ログイン情報
	 */
	public static Map<String, String> getLoginInfo( String cookie ) throws Exception {
		
		// トップ画面への接続を開く。
		HttpURLConnection conn = openConnection( "/", cookie );
	
		// トップ画面のHTMLを取得する。
		String html = getHTML( conn );
		
		// 接続を閉じる。
		closeConnection( conn );
		
		// HTMLからログイン情報を見つける。
		Pattern p = Pattern.compile( "<dd class=\"byebye\">([^<]*)&nbsp;さん" );
		Matcher m = p.matcher( html );
		if ( ! m.find() ) {
			// ログイン情報が見つからない:
			throw new Exception( "ログインしていません。" );
		}
		
		// ログイン情報をマップに設定する。
		Map<String, String> loginInfo = new HashMap<String, String>();
		loginInfo.put( "username", m.group( 1 ) );
		
		return loginInfo;
	}

	/**
	 * 本の一覧を取得する。
	 * @param cookie クッキー
	 * @return 本(isbn)の一覧
	 */ 
	public static String[] getBookList( String cookie ) throws Exception {
		
		List<String> itemList = new ArrayList<String>();	// 本(isbn)の一覧
		int page = 0;	// ページ番号
		
		Pattern p = Pattern.compile( "<a href=\"/edit/([^\"]*)\" title=\"[^\"]*\">" );

		// 「本の編集・削除画面」の全ページについて、以下を繰り返す。
		while ( true ) {
			// 本の編集・削除画面に接続する。
			HttpURLConnection conn = openConnection(  "/list?page=" + page, cookie );

			// 本の編集・削除画面のHTMLを取得する。
			String html = getHTML( conn );

			// 接続を閉じる。
			closeConnection( conn );
		
			// 本の情報を取得する。
			Matcher m = p.matcher( html );
			if ( !m.find() ) {
				break;
			}
			do {
				// 本(isbn)を一覧に追加する。
				itemList.add( m.group( 1 ) );
			} while ( m.find() );
		
			// 次のページに進む。
			++page;
		}
		
		return itemList.toArray( new String[0] );
	}
	
	/**
	 * 本の情報を取得する。
	 * @param cookie クッキー
	 * @param isbn 本のisbn
	 * @return 本の情報を格納したマップ。comment: コメント、cate: カテゴリ、rank: ランク
	 */ 
	public static Map<String, String> getBookInfo( String cookie, String isbn ) throws Exception {

		// 本の画面に接続する。
		HttpURLConnection conn = openConnection( "/edit/" + isbn, cookie );
	
		// 本の画面からHTMLを取得する。
		String html = getHTML( conn );

		// 接続を閉じる。
		closeConnection( conn );
		
		//////////////////////////////////////////////////////////////////
		// HTMLから本の情報を取得する。
		//////////////////////////////////////////////////////////////////
		
		String tmp;
		// コメント
		String comment = findGroup( html, "<textarea name=\"description\" class=\"inputarea\" style=\"height:180px;\">([^<]*)</textarea>" );
		// カテゴリ
		tmp = findGroup( html, "<select id=\"category_edit\" name=\"category_id\" class=\"inputselect\">(.*?)</select>" );
		String cate = findGroup( tmp, "<option value=\"([^\"]*)\" selected=\"selected\">" );
		// ランク
		tmp = findGroup( html, "<select name=\"rank\" class=\"inputselect\">(.*?)</select>" );
		String rank = findGroup( tmp, "<option value=\"([^\"]*)\" selected=\"selected\">" );
		// 読書状況
		tmp = findGroup( html, "<select name=\"status\" class=\"inputselect\">(.*?)</select>" );
		String status = findGroup( tmp, "<option value=\"([^\"]*)\" selected=\"selected\">" );
		// タグ
		String tags = findGroup( html, "<input type=\"text\" name=\"tags\" value=\"([^\"]*)\" class=\"inputtext\" />" );
		// 登録日時
		String create_on_y = findGroup( html, "<input type=\"text\" id=\"create_on_y\" name=\"create_on_y\" value=\"([^\"]*)\" maxlength=\"4\" class=\"inputtext\" style=\"width:40px;\" />" );
		String create_on_m = findGroup( html, "<input type=\"text\" id=\"create_on_m\" name=\"create_on_m\" value=\"([^\"]*)\" maxlength=\"2\" class=\"inputtext\" style=\"width:20px;\" />" );
		String create_on_d = findGroup( html, "<input type=\"text\" id=\"create_on_d\" name=\"create_on_d\" value=\"([^\"]*)\" maxlength=\"2\" class=\"inputtext\" style=\"width:20px;\" />" );
		String create_on_h = findGroup( html, "<input type=\"text\" id=\"create_on_h\" name=\"create_on_h\" value=\"([^\"]*)\" maxlength=\"2\" class=\"inputtext\" style=\"width:20px;\" />" );
		String create_on_i = findGroup( html, "<input type=\"text\" id=\"create_on_i\" name=\"create_on_i\" value=\"([^\"]*)\" maxlength=\"2\" class=\"inputtext\" style=\"width:20px;\" />" );
		String create_on_s = findGroup( html, "<input type=\"text\" id=\"create_on_s\" name=\"create_on_s\" value=\"([^\"]*)\" maxlength=\"2\" class=\"inputtext\" style=\"width:20px;\" />" );

		
		// 本の情報をマップに格納する。
		Map<String, String> info = new HashMap<String, String>();
		info.put( "comment", comment );
		info.put( "cate", cate );
		info.put( "rank", rank );
		info.put( "status", status );
		info.put( "tags", tags );
		info.put( "create_on_y", create_on_y );
		info.put( "create_on_m", create_on_m );
		info.put( "create_on_d", create_on_d );
		info.put( "create_on_h", create_on_h );
		info.put( "create_on_i", create_on_i );
		info.put( "create_on_s", create_on_s );

		
		return info;
	}
	
	/**
	 * 本を更新する。
	 * @param cookie クッキー
	 * @book 本の情報
	 * @comment コメント
	 */
	public static void updateBook( String cookie, Map<String, String> book ) throws Exception {
		
		String isbn = book.get( "isbn" );
		String cate = book.get( "cate" );
		String rank = book.get( "rank" );
		String comment = book.get( "comment" );
		String status = book.get( "status" );
		String tags = book.get( "tags" );
		String create_on_y = book.get( "create_on_y" );
		String create_on_m = book.get( "create_on_m" );
		String create_on_d = book.get( "create_on_d" );
		String create_on_h = book.get( "create_on_h" );
		String create_on_i = book.get( "create_on_i" );
		String create_on_s = book.get( "create_on_s" );

		// 本の編集画面に接続する。
		HttpURLConnection conn = openConnection( "/edit/" + isbn, cookie );

		// 本の追加情報を送信する。
		String query = "_method=add";
		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();

		// 接続を閉じる。
		closeConnection( conn );

		// 本の編集画面に接続する。
		conn = openConnection( "/edit/" + isbn, cookie );

		// 本の変更情報を送信する。
		query = "_method=edit&category_id=" + cate + "&rank=" + rank + "&status=" + status + "&description=" + comment
						+ "&tags=" + tags + "&create_on_y=" + create_on_y + "&create_on_m=" + create_on_m + "&create_on_d=" + create_on_d
						+ "&create_on_h=" + create_on_h + "&create_on_i=" + create_on_i + "&create_on_s=" + create_on_s;
		conn.setDoOutput( true );
		conn.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded" );
		conn.setRequestProperty( "Content-Length", query.length() + "" );
		out = new OutputStreamWriter( conn.getOutputStream(), ENCODING );
		out.write( query );
		out.flush();
		out.close();

		// 接続を閉じる。
		closeConnection( conn );
	}

	/**
	 * ブクログへの接続を開く。
	 * @param requestURI リクエストURI
	 * @return 接続
	 */
	public static HttpURLConnection openConnection( String requestURI ) throws Exception {
		return openConnection( requestURI, null );
	}
	
	/**
	 * ブクログへの接続を開く。
	 * @param requestURI リクエストURI
	 * @param cookie クッキー
	 */
	public static HttpURLConnection openConnection( String requestURI, String cookie ) throws Exception {
		URL url = new URL( "http://booklog.jp" + requestURI );
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setInstanceFollowRedirects( false );
		conn.setRequestProperty( "Host", "booklog.jp" );
		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" );
		conn.setRequestProperty( "Referer", "http://booklog.jp" );
		if ( cookie != null ) {
			conn.setRequestProperty( "Cookie", cookie );
		}
		
		return conn;
	}
	
	/**
	 * ブクログへの接続を切断する。
	 * @param conn 接続
	 */
	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 {
		BufferedReader in = new BufferedReader(
			new InputStreamReader( conn.getInputStream(), ENCODING ) );
		StringBuffer buf = new StringBuffer();

		int c;
		while ( ( c = in.read() ) != -1 ) {
			buf.append( (char) c );
		}
		in.close();
		
		return buf.toString();
	}

	/**
	 * 入力文字列中で、正規表現の一番目の括弧(グループ)にマッチする部分文字列を返す。
	 * @param regex 正規表現
	 * @param input 入力文字列
	 * @return マッチする部分文字列。マッチしない場合、null。
	 */
	public static String findGroup( String input, String regex ) {
		// 正規表現によるマッチングを行う。
		Pattern p = Pattern.compile( regex, Pattern.DOTALL );
		Matcher m = p.matcher( input );
		if ( ! m.find() ) {
			// マッチしない:
			return null;
		}
		
		// 一番目の括弧(グループ)にマッチする部分文字列を取得する。
		return m.group( 1 );
	}
}