スポンサーリンク

「はてなカウンター」から1年分の「リンク元」情報を抽出し,URLをExcelに記録するバッチ (URLのフィルタリング機能付き)

アクセス解析の作業において,下記の2つを自動化するバッチ。

  • (1)アクセス元URLの抽出と,Excelファイルへの記録。
  • (2)アクセス元URLの一括フィルタリング。検索エンジンなどの除外。

まず一つ目から。


アクセス解析ツール「はてなカウンター」には,「リンク元」という項目がある。

当ブログが,Web上のどこのURLからリンクされ,何回アクセスされたか?

を記録・集計してくれる機能だ。


この「リンク元」ページは,1画面内に50リンク表示される。

しかし,「1年間分のリンク元をぜんぶ集計したい」という場合,

ページ数が数百ページ以上にも及び,Excelに手動で貼り付けるのは面倒すぎる。


そして2番目。

これらの「リンク元」URLのうち,大半はGoogleやYahooなどの

検索エンジンからのもので,情報としてあまり価値のないものだ。


なぜなら,はてなカウンターには「検索語」という機能もあって,

どのような検索ワードでアクセスされたのかが解析できるから。


「リンク元」を見る場合,検索エンジンなどのサイトは,リンク元から除外したいのだ。

だから,URLを自動的にフィルタリングしたい。というわけ。


以下では,このようなバッチのソースコードを掲載する。

そして,当ブログに対して実行した結果も合わせて掲載する。

(1)リンク元をExcelに記録するバッチ


はてなに関する自分の設定事項や,対象期間をバッチ内に記入して,

あとはダブルクリックするだけ。

すると,IEが起動して,はてなカウンターからリンク元情報を抽出してくれる。

はてなにログインしておくのをお忘れなく。


リンク元のURLを収集.bat

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

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

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

rem 環境変数から「パス名」(フォルダ名)を取り出すには %~pX とする。
rem http://orangeclover.hatenablog.com/entry/20101004/1286120668


echo 終了しました。

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

GOTO :EOF
@end


/*

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

*/



// ---- 設定事項


// 自分のはてな情報
var hatena_id = "XXXXXXXX"; // "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=link&type=yearly&"
;
	// 実際にはこの後ろに page=4 などが付与される


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

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

// リンク元からアクセスされた回数の下限として認める範囲
var min_cnt = 2;

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

// ページングの開始ページ
var first_page_num = 1; // 通常は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 );
	// http://language-and-engineering.hatenablog.jp/entry/20090717/p1

// 新規ブックを保存
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 )
}
	// http://language-and-engineering.hatenablog.jp/entry/20100310/p1
	// http://language-and-engineering.hatenablog.jp/entry/20100403/p1


// ページを移動
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");
			
			// 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("全ページのリンク抽出が完了");


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



// ---- 終了


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


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

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

これを実行すると,バッチと同じフォルダ上にExcelファイルが生成され,

その中にリンク元URLとアクセス回数が記録される。

↓こんな感じで。

https://www.google.co.jp/	129672
ブックマーク他	89545
https://www.google.com/	7097
http://language-and-engineering.hatenablog.jp/entry/20130502/	5427
http://language-and-engineering.hatenablog.jp/entry/20130502/PatternsOfMSDOSorBAT	4247
https://www.google.co.jp	3545
http://language-and-engineering.hatenablog.jp/entry/20110904/p1	2194
http://language-and-engineering.hatenablog.jp/entry/20110717/p1	1864
http://d.hatena.ne.jp/language_and_engineering/	1561
http://www.google.co.jp/	1154
http://language-and-engineering.hatenablog.jp/entry/20110114/p1	1106
http://language-and-engineering.hatenablog.jp/entry/20081014/1223905380	980
http://language-and-engineering.hatenablog.jp/entry/20080912/1221297779	836
http://language-and-engineering.hatenablog.jp/entry/20081001/1222857265	703
http://www2u.biglobe.ne.jp/~motida/xmlhunsenki.html	656
http://language-and-engineering.hatenablog.jp/entry/20120330/p1	554
・・・

アクセス数が2以上あったURLを,1年間分抽出したら,22569個あった。

上記は,その最上位の部分のコピペだ。


これだけのボリュームを記録するのは大変だよ。

一度に画面に表示されるのは50個までだから。

452回のページングと毎回のExcelへのコピペを,バッチが自動的に処理してくれた,ということ。


このバッチ処理にかかった時間は,log.txtに記録される。

処理開始時刻: 2014/01/xx 1:36:06.60
処理終了時刻: 2014/01/xx 4:57:49.92

3時間20分。 その間,ずーっとIEが動きっぱなし。

つまり200分間かけて,400以上のページをスクレイピングしたので,

1分あたり平均して2〜3ページのオープンしかしてないから,

Webサイト側には負荷はかけてない。迷惑なし。


でも,60ページとか70ページ目あたりから,ブラウザのタイムアウトが時折起こるようになる。

その辺の情報になると,DBからのクエリがはてなのキャッシュサーバ内に保管されていないんだろう。


でも,たびたびブラウザがタイムアウトで動かなくなっても,

このバッチではちゃんとリトライ処理が働いて,

ページのリロードがかかるから,処理が中断されないですむ。


参考:

ブラウザの自動操作の最大の問題,「タイムアウト」を克服するには
http://language-and-engineering.hatenablog.jp/entry/20100403/p1


プロジェクト専用のDSLで,効率的にIEを自動操作する(WSH/JScript)
http://language-and-engineering.hatenablog.jp/entry/20100310/p1


ブラウザのビジー状態を判定するための,より良い方法 (WSHでIEを自動操作する際,COMのアプリケーションイベントを利用する)
http://language-and-engineering.hatenablog.jp/entry/20100410/p1


上の抽出結果を見ると,私の場合,サイト内からサイト内に遷移しているケースが非常に多い。

なぜなら,私が執筆する情報は,どれも互いに関連しており,

過去の参考情報に対してリンクをまめに挿入しているから。

そうすれば,書く側も,読む側も,労力が削減される。


…のはいいんだけど,「リンク元」としては,あまり価値がない。

外部サイトから,どのようなリンクを受けているか,という要因を知りたいから。

理由:

Google検索エンジンのアルゴリズム(hummingbird)を把握し,SEOのキーワード分析手法に役立てよう
http://computer-technology.hateblo.jp/entry/20140118/p2

  • サイトが高評価を得るためには,被リンクの「質」に注目。リンクがキーワードを含み,なおかつ多数の分散したIPアドレスからリンクされているか


SEO時,アクセス解析ツールの利用目的は,ユーザについての仮説を検証すること
http://computer-technology.hateblo.jp/entry/20140118/p1

  • なんとなく上位ランキングを眺めているだけではだめ。時間の浪費

だから,次のバッチが必要になる。

(2)リンク元URLをフィルタリングするバッチ

自分のサイトとかを除外した結果を得たいので,URLのリストをフィルタリングにかけよう。


前述のバッチで生成されたエクセルファイルを,下記のバッチにドラッグ・ドロップすれば良い。

ただし,2シート目が存在することを事前に確認しておくこと。


URLパターンの絞り込み.bat

@if(0)==(0) ECHO OFF
cscript.exe //nologo //E:JScript "%~f0" %* 
echo 終了しました。
@pause
GOTO :EOF
@end


// ---- 設定事項


// ホワイトリスト
var reg_patterns_white = [
	/^http/
];

// ブラックリスト(検索エンジンやマッシュアップ,URL加工系のWebサービスなど)
var reg_patterns_black = [
	// 自分のブログ内のリンク
	/^http:\/\/d\.hatena\.ne\.jp\/language_and_engineering\//,
	
	// 検索エンジン
	/^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:\/\/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:\/\/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/,

	// 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)?:\/\/[^/]+\/$/ // ドメインのトップページからリンクされている
];




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


// ---- 引数取得


// 引数があるか
if( WScript.Arguments.length == 0 )
{
	log("同一フォルダ上のExcelファイルをドロップしてください。");
	WScript.Quit();
}

// ファイルパスを構築
var filename = WScript.Arguments.Unnamed(0);
var filepath = filename;
var fso = WScript.CreateObject("Scripting.FileSystemObject");
var filedir  = fso.GetParentFolderName( filepath );

// ファイルが存在するか
if( ! fso.FileExists( filepath ) )
{
	log( filepath + " は無効なファイルパスです。");
	log("同一フォルダ上のExcelファイルをドロップしてください。");
	WScript.Quit();
}
else
{
	log( filepath + " は有効なファイルです。");
}



// ---- Excel起動


var excel = null;
try
{
	excel = WScript.CreateObject("ET.Application");
}
catch(e)
{
	excel = WScript.CreateObject("Excel.Application");
}
excel.Visible = true;


// 対象ブックを開く
excel.Workbooks.Open( filepath );
var book = excel.Workbooks( excel.Workbooks.Count );


// 最初のシートにURLの生データが列挙されているとする
var sheet_raw = book.Worksheets(1);

// 2番目のシートにURLを整理する
var sheet_urls = book.Worksheets(2);



// ---- URLの生データをフィルタリング


var continue_flag = true;
var y_raw   = 1;
var y_write = 1;
while( continue_flag )
{
	// 該当セルが空でなければ読み取りを継続
	if( sheet_raw.Cells( y_raw, 1 ).Value )
	{
		log( y_raw + "行目のURLを検査" );
		var url = sheet_raw.Cells( y_raw, 1 ).Value + "";
		var cnt = sheet_raw.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_urls.Cells( y_write, 1 ).Value = url;
			sheet_urls.Cells( y_write, 2 ).Value = cnt;
			
			y_write ++;
		}
		else
		{
			log("NG");
		}
		
		// 次の行へ
		y_raw ++;
	}
	else
	{
		// 空セルに到達したので読み取りを中断
		continue_flag = false;
	}
}
log("全URLのフィルタリングが完了");



// ---- 終了


// ブックを保存
excel.DisplayAlerts = false;
book.SaveAs( filepath );


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

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

ホワイトリストと,ブラックリストの両方を正規表現で保持しておいて,

安全なURLだけを残せばよいのだ。


このバッチを実行すると,そんなに時間はかからず,数分で完了した。

フィルタリング後の結果は,2番目のシートに記録されている。


↓こんな感じ。

http://www2u.biglobe.ne.jp/~motida/xmlhunsenki.html	656
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1040371430	252
http://alohayou.com/2008/10/hawaiian_olelo/	184
http://archive.1topi.jp/1/d.hatena.ne.jp/language_and_engineering/20110916/p1	183
http://u2k772.blog95.fc2.com/blog-entry-246.html	168
http://d.hatena.ne.jp/bluerabbit/20090801/1249080708	157
http://commte.net/blog/archives/3605	151
http://blog.sen-i.jp/vba/excel-vba-common-module-file.html	135
http://wiki.clockahead.com/index.php?Coding%2fVBA%2fExcel%2f%a5%bd%a1%bc%a5%b9%a5%b3%a1%bc%a5%c9%a4%ce%b4%c9%cd%fd	135
http://makoto-watanabe.main.jp/htmlDOM.html	113
http://d.hatena.ne.jp/replication/20130622/1371883575	109
http://d.hatena.ne.jp/elwoodblues/20120404/1333474343	107
http://blog.goo.ne.jp/xmldtp/e/169a579bc8004099188976f4b2e4cc0d	87
http://mspec.jp/blog/archives/197	84
http://d.hatena.ne.jp/kk_Ataka/20130828/1377693786	76
http://3rd.geocities.jp/kaito_extra/Source/MouseCtrl.html	75
http://naonao.ktkr.net/?p=1703	67
http://gigazine.net/news/20121210-headline/	63
http://programmerbox.com/2013-06-14_web_site_load_testing_tools/	63
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q14106051914	60
・・・


先ほどはURLの個数が22000以上もあったが,このフィルタリングのおかげで,508個まで絞られた。

有益な情報だけを残してくれるので,なかなか便利だと思う。



本エントリの目的は,こういうバッチの作り方と,ソースコードを実際に掲載すること。

だから,ここで得られた実行結果についてコメントするのは,別の機会に譲ろう。

関連する記事:

はてなカウンターのアクセス解析を整理して,「人気記事のランキング」を自動生成するバッチ
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