読者です 読者をやめる 読者になる 読者になる
スポンサーリンク

「はてなカウンター」から,今月の情報を一括して抽出しExcelに保存するバッチ

WSH/JScript excel はてなカウンター IEの自動操作

「はてなカウンター」から,特定の一カ月分の情報を抜き出して保存するバッチ。

ある月の

  • アクセスされたページの順位とページタイトル
  • リンク元URLの中から,検索エンジンなどを除去したリストのURLとページタイトル
  • 単一の検索語
  • 組み合わせの検索語

を,1つのExcelファイルにまとめて保存してくれる。

はてなIDや対象月などの設定項目は,書き変えやすいように工夫されている。


要は,下記の3つのエントリーを統合し,1つのバッチに整理して,使いやすくしたもの。

「はてなカウンター」から1年分の「検索キーワード」情報を抽出し,Excelに記録するバッチ
http://language-and-engineering.hatenablog.jp/entry/20140122/p1


「はてなカウンター」から1年分の「リンク元」情報を抽出し,URLをExcelに記録するバッチ (URLのフィルタリング機能付き)
http://language-and-engineering.hatenablog.jp/entry/20140120/p1


はてなカウンターのアクセス解析を整理して,「人気記事のランキング」を自動生成するバッチ
http://language-and-engineering.hatenablog.jp/entry/20140112/GenerateAccessRa...


下記はソースコード。ファイルが2つある。


月間レポートを作成.bat

@echo off

rem はてなカウンターから,対象月の情報を収集する


rem ---- 設定事項 ----

rem カウンターを特定する情報
SET HATENA_ID=(自分のはてなID)
SET COUNTER_ID=(はてなカウンターのID)

rem 対象月
SET YYYY=2014
SET MM=01

rem 集計の設定値

rem アクセスURLを集計する際の,アクセス回数の最低値
SET MIN_CNT_ACCESS_PAGE_URL=80

rem リンク元URLを集計する際の,アクセス回数の最低値
SET MIN_CNT_LINK_PAGE_URL=2

rem 単独語を集計する際の,出現回数の最低値
SET MIN_CNT_SINGLE_SEARCH_WORD=5

rem 複合語を集計する際の,出現回数の最低値
SET MIN_CNT_MIXED_SEARCH_WORD=2


rem ---- 処理 ----

echo 処理開始時刻: %date% %time% > log.txt
SET CURRENT_DIR="%~dp0"

cscript.exe //nologo //E:JScript counter-monthly-report.js ^
  "%CURRENT_DIR%" "%HATENA_ID%" "%COUNTER_ID%" ^
  %YYYY% %MM% ^
  %MIN_CNT_ACCESS_PAGE_URL% ^
  %MIN_CNT_LINK_PAGE_URL% ^
  %MIN_CNT_SINGLE_SEARCH_WORD% ^
  %MIN_CNT_MIXED_SEARCH_WORD%


echo 終了しました。
echo 処理終了時刻: %date% %time% >> log.txt

pause


counter-monthly-report.js

/*

	はてなカウンターから,対象月のレポートを作成してExcelに記録するバッチ
	
	・該当はてなIDにログイン済みであること
	・呼び出し側のバッチ内の設定事項を調整すること

*/


// 引数を取得
var curr_dir     = WScript.Arguments.Unnamed(0);
var hatena_id    = WScript.Arguments.Unnamed(1);
var counter_id   = WScript.Arguments.Unnamed(2);
var target_year  = WScript.Arguments.Unnamed(3);
var target_month = WScript.Arguments.Unnamed(4);


// アクセスURLを集計する際の,アクセス回数の最低値
var min_cnt_access_page_url    = WScript.Arguments.Unnamed(5);

// リンク元URLを集計する際の,アクセス回数の最低値
var min_cnt_link_page_url      = WScript.Arguments.Unnamed(6);

// 単独語を集計する際の,出現回数の最低値
var min_cnt_single_search_word = WScript.Arguments.Unnamed(7);

// 複合語を集計する際の,出現回数の最低値
var min_cnt_mixed_search_word  = WScript.Arguments.Unnamed(8);



// ---- 設定事項


// 情報を記録するExcelファイル名
var xls_filename = "monthly_report_"
	+ hatena_id
	+ "_"
	+ counter_id
	+ "_"
	+ target_year
	+ "_"
	+ target_month
	+ ".xls"
;


// URLフィルタリングのホワイトリスト
var reg_patterns_white = [
	/^http/
];


// URLフィルタリングのブラックリスト
//(検索エンジンやマッシュアップ,URL加工系のWebサービスなど)
var reg_patterns_black = [
	// 自分のブログ内のリンク
	new RegExp( "^http:\/\/d\.hatena\.ne\.jp\/" + hatena_id + ".*\/" ),
	
	// 検索エンジン
	/^http(s)?:\/\/www\.google\./,
	/^http:\/\/(nl\.)?search\.yahoo\./,
	/^http:\/\/www\.bing\.com\/search/,
	/^http:\/\/jp\.ask\.com\//,
	/^http:\/\/jp\.hao123\.com\//,
	/^http:\/\/websearch\./,
	/^http:\/\/cgi\.search\./,
	/^http:\/\/nortonsafe\.search\.ask\.com\//,
	/^http:\/\/notify\.bluecoat\.com\/notify\-/,
	/^http:\/\/(s)?(ksearch)?\.luna\.tv/,
	/^http:\/\/wsearch\.ocn\.ne\.jp\//,
	/^http:\/\/www\.search\./,
	/^http:\/\/geo\-cafe\.starthome\.jp\//,
	/^http:\/\/green\.search\./,
	/^http:\/\/image\.search\./,
	/^http:\/\/realtime\.search\./,
	/^http:\/\/kids\.goo\.ne\.jp\/search/,
	/^http:\/\/office\.microsoft\.com\/ja\-jp\/results/,
	/^http:\/\/sp\-search\.auone\.jp/,
	/^http:\/\/webcache\.googleusercontent\.com/,
	/^http:\/\/www\.aolsearch\./,
	/^http:\/\/www\.hatena\.ne\.jp\/o\/search/,
	/^http:\/\/www\.metasearch\./,
	/^http:\/\/www\.pointtown\.com\/ptu\/search/,
	/^http:\/\/www\.so\-net\.ne\.jp\/search/,
	/^http:\/\/so\-net\.ne\.jp\/search/,
	/^http:\/\/ysearch\./,
	/^http:\/\/isearch/,
	/^http:\/\/hatenatunnel\.appspot\.com\/language_and_engineering\/searchdiary/,
	/^http:\/\/www\.amazon\.(co\.jp)?(com)?\/gp\/bit\/apps\/web\/SERP\/search/,
	/^http(s)?:\/\/encrypted\.google\.com/,
	/^http:\/\/www(.+)\.delta\-search\.com/,
	/^http:\/\/www\.searchgol\.com/,
	/^http:\/\/www\.mysearchresults\.com/,
	/^http:\/\/pex\.jp\/search/,
	/^http:\/\/livedoor\-search/,
	/^http:\/\/kaikatsu\.jword\.jp/,
	/^http:\/\/eonet\.excite\.co\.jp\/search/,
	/^http:\/\/www\.benri\.com\/kensaku/,

	// URL加工系
	/^http:\/\/d\.hatena\.ne\.jp\/notify\-/,
	/^http:\/\/t\.co\//,
	/^http:\/\/search\./,
	/^http:\/\/r\.duckduckgo\.com/,
	
	// Webクリップ系,その他サイト
	/^http:\/\/b\.hatena\.ne\.jp\//,
	/^http:\/\/hatebu\.net\/entry\/d\.hatena\.ne\.jp\/language_and_engineering/,
	/^http:\/\/ceron\.jp\//,
	/^http:\/\/getpocket\.com\//,
	/^http:\/\/reader\.livedoor\.com/,
	/^http:\/\/tophatenar\.com/,
	/^http:\/\/k\.hatena\.ne\.jp\/keywordblog/,
	/^http:\/\/hatebu\-graph\.com/,
	/^http:\/\/tweetbuzz\.jp/,
	/^http:\/\/translate\.google\./,
	/^http(s)?:\/\/m\.facebook/,
	/^http(s)?:\/\/www\.facebook/,
	
	// ほか,はじきたいもの
	/^http(s)?:\/\/[^/]+\/$/ // ドメインのトップページからリンクされている
];



// ---- 前処理


// グローバル変数を定義
var excel = null;

// 1ページあたりに表示されるリンクの上限
var links_num_in_page = 50;


// 新規ブック生成
var file_path = curr_dir + xls_filename;
var book = createNewBookHere( file_path );

// IE起動
var ie = getIE();


// ---- 記録


var sheet;

sheet = getNewSheet( book, "アクセスURL" );
recordAccessPageURLCountersInSheet( sheet, min_cnt_access_page_url );

sheet = getNewSheet( book, "リンク元URL" );
recordLinkPageURLCountersInSheet( sheet, min_cnt_link_page_url )

sheet = getNewSheet( book, "単独の検索語" );
recordSingleSearchWordCountersInSheet( sheet, min_cnt_single_search_word );

sheet = getNewSheet( book, "組み合わせの検索語" );
recordMixedSearchWordCountersInSheet( sheet, min_cnt_mixed_search_word );



// ---- 終了


quitIE( ie );
saveBook( book );
log("全処理が終了");






// --------------- 以下はユーティリティ関数 ---------------



// ログ出力
function log(s){ WScript.Echo(s); }


// はてなカウンターの基本的なURLのトップを返す。
// ページング情報は含まない
function getCounterBaseURL( target_type )
{
	// 表示情報のページングに関する情報は除外してある
	var counter_url_base = "http://counter.hatena.ne.jp/" 
		+ hatena_id
		+ "/report?cid="
		+ counter_id
		+ "&date="
		+ target_year
		+ "-"
		+ target_month
		+ "-01&mode=summary&target="
		+ target_type
		+ "&type=monthly&"
	;
	
	return counter_url_base;
}


// はてなカウンターのページング情報付きのURLを返す。
function getCounterURLWithPage( target_type, page_num )
{
	var counter_url = getCounterBaseURL( target_type )
		+ "page="
		+ page_num
	;
	
	return counter_url;
}


// カレントフォルダにExcelを新規生成し,ブックを返す。
function createNewBookHere( file_path )
{
	var fso = WScript.CreateObject("Scripting.FileSystemObject");

	// ファイルが存在するか
	if( fso.FileExists( file_path ) )
	{
		log( "既にファイルが存在します。実行停止");
		WScript.Quit();
	}
	else
	{
		log( "記録対象:" + file_path );
	}

	// Excel起動
	try
	{
		excel = WScript.CreateObject("ET.Application");
	}
	catch(e)
	{
		excel = WScript.CreateObject("Excel.Application");
	}
	excel.Visible = true;

	// 新規ブック
	excel.Workbooks.Add();
	var book = excel.Workbooks( excel.Workbooks.Count );

	// 新規ブックを保存
	excel.DisplayAlerts = false;
	book.SaveAs( file_path );
	log("とりあえずブックを保存しました");

	return book;
}


// 新しいシートを追加して返す
function getNewSheet( book, new_name )
{
	var sheet = book.Worksheets.Add();
		// http://officetanaka.net/excel/vba/sheet/sheet03.htm
	sheet.Name = new_name;
	
	return sheet;
}


// ブックを保存
function saveBook( book )
{
	excel.DisplayAlerts = false;
	book.SaveAs( file_path );

	log( "ブックを保存しました。" );
}


// IE起動
function getIE()
{
	var ie = WScript.CreateObject("InternetExplorer.Application")
	ie.Visible = true;
	ie_goto_url( ie, "http://www.google.co.jp/" );
	log("ブラウザでのアクセスを開始します。");
	
	return ie;
}


// IEがビジー状態の間待ちます
function ie_wait_while_busy( ie, _url )
{   
	var timeout_ms      = 45 * 1000;
	var step_ms         = 100;
	var total_waited_ms = 0;
	
	while( ( ie.Busy ) || ( ie.readystate != 4 ) )
	{
		WScript.Sleep( step_ms );
		
		// タイムアウトか?
		total_waited_ms += step_ms;
		if( total_waited_ms >= timeout_ms )
		{
			log(
				"警告:タイムアウトのため,リロードします。("
				+ ie.LocationURL
				+ ")"
			);
			
			// どこかに移動中なら,そこへの移動を再試行
			if( _url )
			{
				log( _url + "への遷移を再試行");
				ie_goto_url( ie, _url );
			}
			else
			{
				log( "リロード中");
				
				// 移動先が明示されていなければリロード
				ie.document.location.reload( true );
				ie_wait_while_busy( ie );
			}
			
			break;
		}
	}

	WScript.Sleep( 1000 )
}


// ページを移動
function ie_goto_url( ie, url ){
	ie.Navigate( url );
	ie_wait_while_busy( ie, url );
}


// IEの制御を破棄
function quitIE( ie )
{
	ie.Quit();
	ie = null;
}



// ----- 検索キーワードを集計する関数 -----


// http://language-and-engineering.hatenablog.jp/entry/20140122/p1


// 単独語を集計してシートに記録
function recordSingleSearchWordCountersInSheet( sheet, min_cnt_single_search_word )
{
	var max_page = 1000;
	var min_cnt = min_cnt_single_search_word;

	recordSearchWordCountersInSheet_Common( sheet, "searchwordsingle", min_cnt, max_page );
}


// 複合語を集計してシートに記録
function recordMixedSearchWordCountersInSheet( sheet, min_cnt_mixed_search_word )
{
	var max_page = 1000;
	var min_cnt = min_cnt_mixed_search_word;

	recordSearchWordCountersInSheet_Common( sheet, "searchword", min_cnt, max_page );
}



// 単独語または複合語を集計してシートに記録する共通関数
function recordSearchWordCountersInSheet_Common( sheet, counter_type, min_cnt, max_page )
{
	
	// ページが存在する限り抽出を続行
	var page_num = 1;
	var continue_flag = true;
	while( continue_flag )
	{
		var target_url = getCounterURLWithPage( counter_type, page_num );

		// IEで開く
		log("[" + page_num + " ページ目] " + target_url + " を開きます");
		ie_goto_url( ie, target_url );
		
		// tableを取得
		var table = ie.document
			.getElementById("hourlyreport")
			.getElementsByTagName("table")[0]
		;
		var trs = table.getElementsByTagName("tr");
		
		// trが51行あるので情報抽出。先頭のタイトル行はスキップ
		for( var i = 1; i < links_num_in_page + 1; i ++ )
		{
			// 行があるか?
			var tr = trs[ i ];
			if( tr )
			{
				var y = ( page_num - 1 ) * links_num_in_page + i;
				log( y + "番目の情報を抽出");
			
				var tds = tr.getElementsByTagName("td");
				
				// 値を認識
				if(
					( counter_type == "searchword" )
					||
					( counter_type == "searchwordsingle" )
				)
				{
					// アイコンなどを除去
					var marks_sp = tds[0].getElementsByTagName("span")[0];
					marks_sp.parentNode.removeChild( marks_sp );
				}
				var sw_txt = tds[0].innerText;
				
				// 回数
				var cnt = parseInt( tds[1].innerText.replace( /,/g, "" ), 10);
				if( cnt < min_cnt )
				{
					continue_flag = false;
					log( "回数が下限に達したので抽出を終了" );
				}
				else
				{
					log( i + " 行目から情報を抽出:「" + sw_txt + "」, " + cnt );
					
					// 書き込み
					sheet.Cells( y, 1 ).Value = "'" + sw_txt; // 検索語が = で始まる場合があった
					sheet.Cells( y, 2 ).Value = cnt;
				}
			}
			else
			{
				// 行が途切れたらそこで終わり
				continue_flag = false;
				
				// ページが終わる場合もtable自体と先頭行は表示され,
				// 下部に「アクセスが記録されておりませんでした。」と出る。
			}
		}
		
		
		// 次のページへ
		page_num ++;
		if( page_num > max_page )
		{
			continue_flag = false;
		}
	}
	
	// ワードの列の幅を自動調整
	sheet.Rows(1).EntireColumn.AutoFit();
		// http://www.happy2-island.com/excelsmile/smile03/capter00607.shtml

	log("全ページから情報の抽出が完了");
}



// ---- URLを正規化して格納するためのオブジェクト


// http://language-and-engineering.hatenablog.jp/entry/20140112/GenerateAccessRankingTableHTMLFromHatenaCounter


// 新規インスタンスを返す
function getNewUrlDic()
{
	var url_dic = {
		
		// 辞書配列の本体
		_arr : []
		,
		
		blog_id : ""
		,
		
		// 総記事数
		get_entry_num : function(){
			return this._arr.length;
		}
		,
		
		// 位置に基づいて記事情報を返す
		get_entry_by_index : function( ind )
		{
			return this._arr[ind];
		}
		,

		// 位置に基づいて,正規化された記事URL情報を返す
		get_normal_url_info_by_index : function( ind )
		{
			var info = this.get_entry_by_index( ind );
			var _normal_url = "http://d.hatena.ne.jp/"
				+ this.blog_id 
				+ "/" 
				+ info.entry_date 
				+ "/" 
				+ ( info.entry_id ? info.entry_id : "" )
			;
			
			return {
				normal_url : _normal_url,
				cnt        : info.cnt
			};
		}
		,

		// URLを受け付けて記録
		record : function( url_raw, cnt )
		{
			var url_info = this._normalize_url( url_raw );
			var entry_date = url_info[1];
			var entry_id   = url_info[2];
			
			// 無効なURLははじく
			if(
				( ! url_info[0] )
				||
				( ! url_info[1] )
			){
				log( "スキップ:" + url_raw );
				return;
			}
			
			this.blog_id   = url_info[0];
			
			// 既に登録済みのURLの別形態URLであれば
			if( this._has_entry( entry_date, entry_id ) )
			{
				this._update_entry_info( entry_date, entry_id, cnt );
			}
			else
			{
				this._add_entry_info( entry_date, entry_id, cnt );
			}
		}
		,
		
		// URLを正規化
		_normalize_url : function( url_raw )
		{
			// 注:「不明」などの文字列や,googleなど他ドメインのサイトのURLが渡ってくる場合もある
		
			if(
				url_raw.match( new RegExp("/mobile\\?", "i" ) )
			)
			{
				// ガラケーからアクセスされた場合
				log("ガラケーからのアクセスを検出");

				// URLからコアな情報だけを抽出
				url_raw.match( 
					new RegExp( 
						"^http://d\\.hatena\\.ne\\.jp/([^/]+)/mobile\\?([^&]*&)?date=([0-9]+)(&section=([^/]*))?(/)?$",
						"i"
					)
				);
					// http://d.hatena.ne.jp/---/mobile?guid=on&date=20130101&section=p1
					// guid=on& は無い場合もある
					
					// http://ratememo.blog17.fc2.com/blog-entry-907.html
					
				log(
					"1:" + RegExp.$1 + "," +
					"2:" + RegExp.$2 + "," +
					"3:" + RegExp.$3 + "," +
					"4:" + RegExp.$4 + "," +
					"5:" + RegExp.$5
				);
				
				var blog_id    = RegExp.$1;
				var entry_date = RegExp.$3;
				var entry_id   = RegExp.$5; // 空の場合もある。日付だけでアクセスされた場合
				
			}
			else
			{
				// PCまたはタッチデバイスからアクセスされた場合
			
				// URLからコアな情報だけを抽出
				url_raw.match( 
					new RegExp( 
						"^http://d\\.hatena\\.ne\\.jp/([^/]+)/(touch/)?([0-9]+)(/)?([^/]*)(/)?$",
						"i"
					)
				);
				
				var blog_id    = RegExp.$1;
				var entry_date = RegExp.$3;
				var entry_id   = RegExp.$5; // 空の場合もある。日付だけでアクセスされた場合
					// 201301 のように,月だけで指定された場合は,アクセス集計に数えないものとする。
				
				if( entry_id )
				{
					// URL末尾の?以降のパラメータを除去
					// http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13118651288
					
					entry_id.match( /^([^\?]+)(\?.*)?$/ );
					entry_id = RegExp.$1;
				}
			}
			
			// 重要な情報だけを返す
			return [ blog_id, entry_date, entry_id ];
		}
		,

		// エントリが辞書に登録済みか
		_has_entry : function( _entry_date, _entry_id )
		{
			for( var i = 0; i < this._arr.length; i ++ )
			{
				var info = this.get_entry_by_index(i);
				if(
					( info.entry_date == _entry_date )
					&&
					( info.entry_id   == _entry_id )
				)
				{
					// もうある
					return true;
				}
			}
			
			// まだない
			return false;
		}
		,

		// エントリ情報を追加
		_add_entry_info : function( _entry_date, _entry_id, _cnt )
		{
			if( 
				( ! _entry_date )
				||
				( ! _cnt )
			)
			{
				log( 
					"無効なデータを追加できません。" 
					+ "entry_date=" + _entry_date
					+ ", cnt=" + _cnt
				);
				WScript.Quit();
			}
		
			this._arr.push(
				{
					entry_date : _entry_date,
					entry_id   : _entry_id,
					cnt        : _cnt
				}
			);
		}
		,

		// エントリ情報を更新
		_update_entry_info : function( _entry_date, _entry_id, _cnt )
		{
			// 同一エントリ情報を探して数字だけ更新
			for( var i = 0; i < this._arr.length; i ++ )
			{
				var info = this.get_entry_by_index(i);
				if(
					( info.entry_date == _entry_date )
					&&
					( info.entry_id   == _entry_id )
				)
				{
					// 加算
					info.cnt += _cnt;
					
					log( 
						"重複:" 
						+ _entry_date + "/" + _entry_id + "のカウントを" 
						+ _cnt + "から" + info.cnt + "に調整"
					)
				}
			}
		}
		,
		
		// 全記事情報がそろった時点で,登録情報を最終チェックする
		finalize_data : function()
		{
			// 記事IDを持っていない記事情報について,
			// 可能なら記事IDを付与する。
			for( var i = 0; i < this._arr.length; i ++ )
			{
				var info = this.get_entry_by_index(i);
				if( ! info.entry_id )
				{
					var entry_date = info.entry_date;
					var entries = this._find_entries_by_entry_date( entry_date );
					
					// 適切な記事IDを探す
					var ids = [];
					for( var j = 0; j < entries.length; j ++ )
					{
						// 有効な記事IDを持っていれば候補として考慮
						if( entries[ j ].entry_id )
						{
							ids.push( entries[ j ].entry_id );
						}
					}
					// 記事IDの候補があれば
					if( ids.length > 0 )
					{
						// デフォルトでは,日付内で下から順にp1, p2, p3... と記事IDが付与される。
						// 日付URLを開くと最初に見えるのは一番上の記事だから,最後の記事IDを採用する。
						ids.sort();
						var new_entry_id = ids[ ids.length - 1 ];
						
						// 新しい記事IDを基に数値を加算
						this._update_entry_info( entry_date, new_entry_id, info.cnt );
						info.delete_flag = true; // 削除予約
						
						log(entry_date + "の日付の記事に,記事IDとして" + new_entry_id + "を付与");
					}
					else
					{
						// 記事IDの候補がなければしょうがないので,日付URLのまま放置する。
						log(entry_date + "の日付の記事には記事IDを付与せず");
					}
				}
			}
			// 上記で削除予約した情報を削除
			for( var i = this._arr.length - 1; i >= 0; i -- )
			{
				if( this.get_entry_by_index(i).delete_flag )
				{
					// 配列から要素を削除
					this._arr.splice( i, 1 );
						// http://javascript-memo.seesaa.net/article/24832361.html
				}
			}
			
		}
		,
		
		// 日付をキーにして記事情報を検索する
		_find_entries_by_entry_date : function( entry_date )
		{
			var ret = [];
			for( var i = 0; i < this._arr.length; i ++ )
			{
				var info = this.get_entry_by_index(i);
				if( info.entry_date == entry_date )
				{
					ret.push( info )
				}
			}
			
			return ret;
		}

	};

	return url_dic;
}



// ---- アクセスURLを読み取って整理する関数



// あるシート上のURLを正規化して,別のシートに記録
function normalizeURLCountersInSheet( sheet_from, sheet_to )
{
	var url_dic = getNewUrlDic();

	var continue_flag = true;
	var y = 2;
	while( continue_flag )
	{

		// 該当セルが空でなければ読み取りを継続
		if( sheet_from.Cells( y, 1 ).Value )
		{
			log( y + "行目を検査" );
		
			// 正規化前のURLを取得
			var url_raw = sheet_from.Cells( y, 1 ).Value;

			// 記録
			url_dic.record( url_raw, sheet_from.Cells( y, 2 ) );
			log( "URLを記録 [" + url_dic.get_entry_num() + "] " + url_raw );
			
			// 次の行へ
			y ++;
		}
		else
		{
			// 空セルに到達したので読み取りを中断
			continue_flag = false;
		}
	}
	log("全URLの記録が完了");


	// 全記事情報を整理
	url_dic.finalize_data();
	log("記事情報の整理が完了");


	// ---- 整理済みのURLをシートに書きだし


	// 登録順に書きだし
	for( var i = 0; i < url_dic.get_entry_num(); i ++ )
	{
		var info = url_dic.get_normal_url_info_by_index( i );

		var y = i + 1;
		sheet_to.Cells( y, 1 ).Value = info.normal_url;
		sheet_to.Cells( y, 2 ).Value = info.cnt;
	}
	log("全URLの書き出しが完了");


	// シート上で,アクセス数の多い順にソート(URL統合のため順序が変動しているから)
	var xlDescending = 2;
		// http://msdn.microsoft.com/en-us/library/office/ff834316.aspx
	sheet_to.Range( 
		"A1:B" + url_dic.get_entry_num() 
	).Sort(
		sheet_to.Cells(1, 2), // アクセス数をキーに降順で並び替え
		xlDescending 
	);
		// http://www.excel.studio-kazu.jp/kw/20091227224357.html
	log("ソートが完了");
	
}


// あるシート上のURLをフィルタリングして,別のシートに記録
function filterLinkURLInSheet( sheet_from, sheet_to )
{
	var continue_flag = true;
	var y_raw   = 1;
	var y_write = 1;
	
	while( continue_flag )
	{
		// 該当セルが空でなければ読み取りを継続
		if( sheet_from.Cells( y_raw, 1 ).Value )
		{
			log( y_raw + "行目のURLを検査" );
			var url = sheet_from.Cells( y_raw, 1 ).Value + "";
			var cnt = sheet_from.Cells( y_raw, 2 ).Value + 0;
			var ok_flag = true;
			
			// ホワイトリストに通る?
			for( var i = 0; i < reg_patterns_white.length; i ++ )
			{
				if( ! url.match( reg_patterns_white[i] ) )
				{
					ok_flag = false;
				}
			}
			
			// ブラックリストにひっかからない?
			for( var i = 0; i < reg_patterns_black.length; i ++ )
			{
				if( url.match( reg_patterns_black[i] ) )
				{
					ok_flag = false;
				}
			}
			
			// フィルタを通過した?
			if( ok_flag )
			{
				log("OK");
				sheet_to.Cells( y_write, 1 ).Value = url;
				sheet_to.Cells( y_write, 2 ).Value = cnt;
				
				y_write ++;
			}
			else
			{
				log("NG");
			}
			
			// 次の行へ
			y_raw ++;
		}
		else
		{
			// 空セルに到達したので読み取りを中断
			continue_flag = false;
		}
	}
	log("全URLのフィルタリングが完了");
	
}


// シートの特定の列の全URLにページタイトルを付与
function getTitlesForAllURLInSheet( sheet, url_row_num, write_row_num )
{
	// シート掲載順にアクセス
	var y = 1;
	var continue_flag = true;
	while( continue_flag )
	{
		var target_url = sheet.Cells( y, url_row_num ).Value;

		if( ! target_url )
		{
			continue_flag = false;
		}
		else
		if( target_url.match( /^http/ ) ) // 「不明」などの場合もある
		{
			// 開く
			log("[" + y + "] " + target_url + "を開きます");
			ie_goto_url( ie, target_url );
			
			// タイトルを抽出して記録
			var tit = ie.Document.title;
			log( "タイトルは " + tit );
			sheet.Cells( y, write_row_num ).Value = tit;
			
		}
		
		y ++;
	}
	log("全URLのタイトル抽出が完了");
	
}



// アクセスURLを集計してシートに記録
function recordAccessPageURLCountersInSheet( sheet, min_cnt )
{
	var sheet_to = sheet;

	// 全URL
	var tmp_sheet_name = "アクセスURL(正規化前)";
	var sheet_from = getNewSheet( book, tmp_sheet_name );
	recordAccessOrLinkPageURLCountersInSheet_Common( sheet_from, min_cnt, "url" );
	
	// URLの統合処理を実行
	normalizeURLCountersInSheet( sheet_from, sheet_to );
	
	// 作業用のシートを削除しないでとっておく

	// 全URLにページタイトルを付与
	getTitlesForAllURLInSheet( sheet_to, 1, 3 );
}


// リンク元URLを集計してシートに記録
function recordLinkPageURLCountersInSheet( sheet, min_cnt )
{
	var sheet_to = sheet;

	// 全URL
	var tmp_sheet_name = "リンク元URL(正規化前)";
	var sheet_from = getNewSheet( book, tmp_sheet_name );
	recordAccessOrLinkPageURLCountersInSheet_Common( sheet_from, min_cnt, "link" );
	
	// URLのフィルタリングを実行
	filterLinkURLInSheet( sheet_from, sheet_to );
	
	// 作業用のシートを削除しないでとっておく

	// 全URLにページタイトルを付与
	getTitlesForAllURLInSheet( sheet_to, 1, 3 );
}


// アクセスURLとリンク元URLの共通関数
function recordAccessOrLinkPageURLCountersInSheet_Common( sheet, min_cnt, counter_type )
{
	var max_page = 1000;
	
	// ページが存在する限り抽出を続行
	var page_num = 1;
	var continue_flag = true;
	while( continue_flag )
	{
		var target_url = getCounterURLWithPage( counter_type, page_num );

		// IEで開く
		log("[" + page_num + " ページ目] " + target_url + " を開きます");
		ie_goto_url( ie, target_url );
		
		// tableを取得
		var table = ie.document
			.getElementById("hourlyreport")
			.getElementsByTagName("table")[0]
		;
		var trs = table.getElementsByTagName("tr");
		
		// trが51行あるので情報抽出。先頭のタイトル行はスキップ
		for( var i = 1; i < links_num_in_page + 1; i ++ )
		{
			// 行があるか?
			var tr = trs[ i ];
			if( tr )
			{
				var y = ( page_num - 1 ) * links_num_in_page + i;
				log( y + "番目の情報を抽出");
			
				var tds = tr.getElementsByTagName("td");
				
				// URLを認識
				var elem_as = tds[0].getElementsByTagName("a");
				if( elem_as.length > 0 )
				{
					var elem_a = elem_as[0];
					
					//var link_url = elem_a.getAttribute("href"); // エラーになる場合がある
					//var link_url = elem_a.href; // エラーになる場合がある
					
					var link_url = elem_a.getAttribute("href", 2);
						// http://might1976.doorblog.jp/archives/51159843.html
				}
				else
				{
					// 「不明」などの文言の場合もある
					var link_url = tds[0].innerText;
				}

				// アクセス回数
				var cnt = parseInt( tds[1].innerText.replace( /,/g, "" ), 10);
				if( cnt < min_cnt )
				{
					continue_flag = false;
					log( "アクセス回数が下限に達したので抽出を終了" );
				}
				else
				{
					log( i + " 行目からリンクを抽出:「" + link_url + "」, " + cnt );
					
					// 書き込み
					sheet.Cells( y, 1 ).Value = link_url;
					sheet.Cells( y, 2 ).Value = cnt;
				}
			}
			else
			{
				// 行が途切れたらそこで終わり
				continue_flag = false;
				
				// ページが終わる場合もtable自体と先頭行は表示され,
				// 下部に「アクセスが記録されておりませんでした。」と出る。
			}
		}
		
		
		// 次のページへ
		page_num ++;
		if( page_num > max_page )
		{
			continue_flag = false;
		}
	}

	log("全ページのリンク抽出が完了");
}

「はてなカウンターを操作するためのユーティリティ関数」が,だいぶそろってきた。

関連する記事:

はてなダイアリーに執筆した記事一覧を,表形式に整理するブックマークレット (アーカイブページを,Excelに貼り付けやすく整形加工)
http://language-and-engineering.hatenablog.jp/entry/20140102/p1


はてブのマイページから,情報を一括して整形・抽出するブックマークレット
http://language-and-engineering.hatenablog.jp/entry/20131229/p1


2013年の人気記事ランキング (アクセス数やブクマ数の多かったエントリ)
http://language-and-engineering.hatenablog.jp/entry/20140201/p1