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

「はてなカウンター」から1年分の「検索キーワード」情報を抽出し,Excelに記録するバッチ

excel WSH/JScript はてなカウンター

自分のブログが,1年間の間,どのような検索ワードでアクセスされたか?

というランキングを自動生成するバッチ。

  • 複数語の組み合わせ
  • 単一ワード

の両方。

以前に作成したバッチを,ちょこちょこっと書き換えるだけですぐに完成してしまった。

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


下記のバッチの設定事項を書き変えて,ダブルクリックするだけ。


検索ワードを収集.bat

@if(0)==(0) ECHO OFF

echo 処理開始時刻: %date% %time% > log.txt

rem WSHコードに,第一引数としてカレントフォルダを渡す
cscript.exe //nologo //E:JScript "%~f0" "%~dp0"

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

GOTO :EOF
@end


/*

	はてなカウンターから,対象年内の検索語を抽出してExcelに記録するバッチ
	
	・該当はてなIDにログイン済みであること
	・バッチ内の設定事項を調整すること

*/



// ---- 設定事項


// 自分のはてな情報
var hatena_id = "XXXXXX"; //"language_and_engineering";
var counter_id = "1";

// 対象年
var target_year = "2013";

// はてなカウンターの基本的なURL。
// 表示情報のページングに関する情報は除外してある
var counter_url_base = "http://counter.hatena.ne.jp/" 
	+ hatena_id
	+ "/report?cid="
	+ counter_id
	+ "&date="
	+ target_year
	+ "-12-01&mode=summary&target=searchword&type=yearly&"
;
	// 実際にはこの後ろに page=4 などが付与される


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

// このバッチでページングを行なう際の最高ページ,リミット
var max_page = 1000;

// 検索された回数の下限として認める範囲
var min_cnt = 1;

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

// ページングの開始ページ
var first_page_num = 1;



function log(s){ WScript.Echo(s); }



// ---- カレントフォルダにExcelを新規生成


var curr_dir = WScript.Arguments.Unnamed(0);
var file_path = curr_dir + xls_filename;
var fso = WScript.CreateObject("Scripting.FileSystemObject");

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



// Excel起動
var excel = null;
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("とりあえずブックを保存しました");

// 先頭のシートを情報の記録場所とする
var sheet = book.Worksheets(1);



// ---- 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
					// http://blog.livedoor.jp/programlog/archives/298228.html
				+ ")"
			);
			
			// どこかに移動中なら,そこへの移動を再試行
			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起動
var ie = WScript.CreateObject("InternetExplorer.Application")
ie.Visible = true;
ie_goto_url( ie, "http://www.google.co.jp/" );
log("ブラウザでのアクセスを開始します。");


// ページが存在する限り抽出を続行
var page_num = first_page_num;
var continue_flag = true;
while( continue_flag )
{
	var target_url = counter_url_base 
		+ "page="
		+ 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");
			
			// 検索語を認識
			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;
	}
}

log("全ページの検索ワード抽出が完了");


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



// ---- 終了


// ブックを保存
excel.DisplayAlerts = false;
book.SaveAs( file_path );
log( "ブックを保存しました。" );


// Excelを閉じて終了
//excel.Quit();
//excel = null;

log("全処理が終了");

シンプル。

前回のバッチと異なる点は,ほんのわずか。

  • アクセスするURLがちょっと異なる
  • 画面上に表示されるtable内で,よけいな画像を含むspanを除去してから文字列だけを情報抽出
  • Excelに記録する際には,ワードの先頭に「'」を付与する。理由は,検索ワードが「=」で開始している場合,そのままでExcelに記録するとエラーになるため。

ほかの部分のコードは,丸ごと流用できた。


※なお,これらのコードを共通化しよう,というつもりはさらさらない。
そんなことをしたら,「はてなカウンターのWebスクレイピングに特化した専用ツールを本格的に開発している」ことになってしまうではないか。…


で,私のブログの2013年のアクセスログを対象とした実行結果は,下記のような感じだった。

台湾語 日常会話	1513
バッチファイル	1319
svn 使い方	920
日常会話 韓国語	889
一覧 日常会話 韓国語	781
ハングル検定	604
jmeter	594
バッチファイル 作り方	516
batファイル	452
ハングル文字	417
台湾語	383
辞書 韓国語	309
レベル 中国語検定	305
tomcat	296
広東語 発音	271
smarty	263
svn	260
あいさつ 台湾語	244
word マクロ	238
jmeter 使い方	230
マレー語	230
vba word	229
java デザインパターン	216
一覧表 単語 韓国語	214
tortoisesvn 使い方	211

・・・・

ビスタのハイパー機能で目次を作るエクセル	1
ビスタのハイパー機能を使ってエクセルを目次機能&#65533;%	1
ピンインを簡体字へ変換	1
ファンデーション試供品印%	1
マクロ ワード	1
マクロを他のファイルに適用する	1
マスターテスト計画書	1
マレー語	1
モジラとoutolook比較	1
リナックス無線lan設定	1
ログインシェルを設定しない	1

検索ワード(複合語の組み合わせ)の個数は,全部で45514件。

911ページ分を解析したという事になる。


このバッチの実行時間:

処理開始時刻: 2014/01/XX 17:11:31.07
処理終了時刻: 2014/01/XX 19:09:32.25

ちょうど2時間。

前回は夜間を狙ってバッチを走らせたのだが,日中のほうが速いのだろうか?

もし夜間にはメンテ作業等のために
はてな内でサーバパフォーマンスが低下しているとしたら,
あえて夜間にこういう処理を走らせる必要はないのかも。

応用

この複合ワード用のバッチは簡単に応用できて,

「検索語(単語)」の抽出用に改変する事もすぐにできる。


改変作業は1分以内に終わるから,あえて掲載する必要もないんだけど

一応ソースコードを載せておく。


単独の検索ワードを抽出.bat

@if(0)==(0) ECHO OFF

echo 処理開始時刻: %date% %time% > log.txt

rem WSHコードに,第一引数としてカレントフォルダを渡す
cscript.exe //nologo //E:JScript "%~f0" "%~dp0"

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

GOTO :EOF
@end


/*

	はてなカウンターから,対象年内の単独検索語(「検索語(単語)」)を
	抽出してExcelに記録するバッチ
	
	・該当はてなIDにログイン済みであること
	・バッチ内の設定事項を調整すること

*/



// ---- 設定事項


// 自分のはてな情報
var hatena_id = "XXXX"; //"language_and_engineering";
var counter_id = "1";

// 対象年
var target_year = "2013";

// はてなカウンターの基本的なURL。
// 表示情報のページングに関する情報は除外してある
var counter_url_base = "http://counter.hatena.ne.jp/" 
	+ hatena_id
	+ "/report?cid="
	+ counter_id
	+ "&date="
	+ target_year
	+ "-12-01&mode=summary&target=searchwordsingle&type=yearly&"
;
	// 実際にはこの後ろに page=4 などが付与される


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

// このバッチでページングを行なう際の最高ページ,リミット
var max_page = 1000;

// 検索された回数の下限として認める範囲
var min_cnt = 1;

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

// ページングの開始ページ
var first_page_num = 1;



function log(s){ WScript.Echo(s); }



// ---- カレントフォルダにExcelを新規生成


var curr_dir = WScript.Arguments.Unnamed(0);
var file_path = curr_dir + xls_filename;
var fso = WScript.CreateObject("Scripting.FileSystemObject");

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



// Excel起動
var excel = null;
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("とりあえずブックを保存しました");

// 先頭のシートを情報の記録場所とする
var sheet = book.Worksheets(1);



// ---- 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起動
var ie = WScript.CreateObject("InternetExplorer.Application")
ie.Visible = true;
ie_goto_url( ie, "http://www.google.co.jp/" );
log("ブラウザでのアクセスを開始します。");


// ページが存在する限り抽出を続行
var page_num = first_page_num;
var continue_flag = true;
while( continue_flag )
{
	var target_url = counter_url_base 
		+ "page="
		+ 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");
			
			// 検索語を認識
			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;
	}
}

log("全ページの単独検索ワード抽出が完了");


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



// ---- 終了


// ブックを保存
excel.DisplayAlerts = false;
book.SaveAs( file_path );
log( "ブックを保存しました。" );


// Excelを閉じて終了
//excel.Quit();
//excel = null;

log("全処理が終了");

当然のことながら,複合語の総数よりも,単独語の総数のほうが少ない。

だから,さきほどのバッチよりも速く処理が終わる。


まず,実行結果はこんな感じ。

バッチファイル	4023
韓国語	3997
vba	3799
日常会話	3493
android	3301
台湾語	3099
マクロ	2628
バッチ	2345
windows	2275
excel	2114
使い方	2051
bat	1826
javascript	1739
svn	1643
コマンド	1564
jcom	1528
エクセル	1488
ハングル検定	1374
java	1289
中国語	1258
広東語	1239
一覧	1174
ie	1111
コマンドプロンプト	1059
グラフ	1043
batファイル	1038
linux	1034
中国語検定	1032

・・・

メモリダンプ	1
メモリ覗き方法	1
モジラとoutolook比較	1
モトローラ	1
ラックギヤ	1
ライセンス	1
ライン検出	1
リスト	1
リターンコード	1
リナックス無線lan設定	1
ルーターからスマホ	1
ログ	1
ログインシェルを設定しない	1
ロジック参照	1
ワークブック	1

全部で,29334個。ページングは587回。

これらの単独語をもとに,組み合わせから,先ほどの45514個の複合ワードが作られているわけだ。


考えてみると,複合ワードの中には,上記の単独ワードも含まれている。


そうすると,全複合ワード45514個のうち,29334個は単独ワードの扱いだから,

その差分である16180個が,キーワードを組み合わせて検索したケース,という事になる。


全体の複合検索ワードの個数のうち,35%にあたる分が,

「スペース区切りによる単独キーワードの組み合わせ」

によって成り立っているというわけだ。

※ただし,検索ワードが文章として打ち込まれていたり,スペース区切りなしで連続して打ち込まれているものは考慮していないが。


このバッチの実行時間:

処理開始時刻: 2014/01/XX 5:03:40.28
処理終了時刻: 2014/01/XX 5:56:35.78

1時間もかからなかった。

29334 / 45514 = 0.64 なので,本当は1時間以上かかる見積もりのはずなのだが,サクサク動いてくれたようだ。

関連する記事:

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


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


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


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