はてなカウンターのアクセス解析を整理して,「人気記事のランキング」を自動生成するバッチ
「はてなダイアリー」のアクセス解析ツール「はてなカウンター」を開くと,
「ある期間に,どのページのPVが多かったか?」という集計の画面がある。
↓こんな感じ。
アクセスされたURLと,期間内でのアクセス数が並んでいる。
(これだと,URLだけなので,記事のタイトルが分からない。)
もし,この画面で集計された内容をもとに
- 「ある期間内で,もっとも閲覧されていた記事のランキング・リスト」
を自動生成できたら,とても便利だろう。
例えば,対象期間が「2013年の1年間」だとすれば,
「1年間で最も人気があった記事のアクセス数ランキング」を自動生成できる。
そういうバッチを作ってみた。
以下はソースコードと使い方。
そして,このブログ内の記事に対して,2013年のアクセス・ランキングも生成してみる。
必要なファイルは,1個だけ。下記のバッチファイルである。
アクセスランキングを生成.bat
@if(0)==(0) ECHO OFF cscript.exe //nologo //E:JScript "%~f0" %* > %0\..\log.txt echo 終了しました。 @pause GOTO :EOF @end function log(s){ WScript.Echo(s); } // ---- 引数取得 // 引数があるか if( WScript.Arguments.length == 0 ) { // http://language-and-engineering.hatenablog.jp/entry/20110921/p1 log("同一フォルダ上のExcelファイルをドロップしてください。"); WScript.Quit(); } // ファイルパスを構築 var filename = WScript.Arguments.Unnamed(0); //var ws = WScript.CreateObject("WScript.Shell"); //var cwd = ws.CurrentDirectory; //var filepath = cwd + "\\" + filename; var filepath = filename; var fso = WScript.CreateObject("Scripting.FileSystemObject"); var filedir = fso.GetParentFolderName( filepath ); // http://jeanne.wankuma.com/tips/vb6/path/getdirectoryname.html // ファイルが存在するか if( ! fso.FileExists( filepath ) ) { // http://wsh.style-mods.net/ref_filesystemobject/fileexists.htm log( filepath + " は無効なファイルパスです。"); log("同一フォルダ上のExcelファイルをドロップしてください。"); WScript.Quit(); } else { log( filepath + " は有効なファイルです。"); } // ---- Excel起動 var excel = null; try { // Excel(Kingsoft Spreadsheet)を立ち上げる excel = WScript.CreateObject("ET.Application"); //http://language-and-engineering.hatenablog.jp/entry/20121218/p1 } catch(e) { // Excel(MS Office)を立ち上げる excel = WScript.CreateObject("Excel.Application"); //http://language-and-engineering.hatenablog.jp/entry/20090717/p1 } // 例外設計 // http://language-and-engineering.hatenablog.jp/entry/20101029/p1 excel.Visible = true; // 対象ブックを開く excel.Workbooks.Open( filepath ); var book = excel.Workbooks( excel.Workbooks.Count ); // 最初のシートにハイパーリンクがコピペで列挙されているとする var sheet_links = book.Worksheets(1); // http://3rd.geocities.jp/kaito_extra/Source/ExcelCtrl.html // 2番目のシートにURLを整理する var sheet_urls = book.Worksheets(2); // ---- URLを正規化して格納するためのオブジェクト 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]+)(§ion=([^/]*))?(/)?$", "i" ) ); // http://d.hatena.ne.jp/---/mobile?guid=on&date=20130101§ion=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; } }; // ---- URLを読み取って整理 var continue_flag = true; var y = 2; while( continue_flag ) { // 該当セルが空でなければ読み取りを継続 if( sheet_links.Cells( y, 1 ).Value ) { log( y + "行目のハイパーリンクを検査" ); // このセルの範囲にハイパーリンクが存在するか // http://hinden.at.webry.info/201104/article_12.html var links = sheet_links.Cells( y, 1 ).Hyperlinks; if( links.Count > 0 ) { // 正規化前のURLを取得 var url_raw = links(1).Address; // http://okwave.jp/qa/q2294383.html // 記録 url_dic.record( url_raw, sheet_links.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_urls.Cells( y, 1 ).Value = info.normal_url; sheet_urls.Cells( y, 2 ).Value = info.cnt; } log("全URLの書き出しが完了"); // シート上で,アクセス数の多い順にソート(URL統合のため順序が変動しているから) var xlDescending = 2; // http://msdn.microsoft.com/en-us/library/office/ff834316.aspx sheet_urls.Range( "A1:B" + url_dic.get_entry_num() ).Sort( sheet_urls.Cells(1, 2), // アクセス数をキーに降順で並び替え xlDescending ); // http://www.excel.studio-kazu.jp/kw/20091227224357.html log("ソートが完了"); // ---- 全URLにタイトルを付与する // IEで全URLを調査する // IEがビジー状態の間待ちます function waitIE( ie ) { while( ( ie.Busy ) || ( ie.readystate != 4 ) ) { WScript.Sleep( 100 ); } WScript.Sleep( 1000 ) } // IE起動 var ie = WScript.CreateObject("InternetExplorer.Application") ie.Navigate( "http://www.google.co.jp/" ); ie.Visible = true; waitIE( ie ); log("ブラウザでのアクセスを開始します。"); // シート掲載順にアクセス for( var i = 0; i < url_dic.get_entry_num(); i ++ ) { var y = i + 1; var target_url = sheet_urls.Cells( y, 1 ).Value; // 開く log("[" + y + "] " + target_url + "を開きます"); ie.Navigate( target_url ); waitIE( ie ); // タイトルを抽出して記録 var tit = ie.Document.title; log( "タイトルは " + tit ); sheet_urls.Cells( y, 3 ).Value = tit; } log("全URLのタイトル抽出が完了"); // IEの制御を破棄 ie.Quit(); ie = null; // ---- 終了 // ブックを保存 excel.DisplayAlerts = false; book.SaveAs( filepath ); // Excelを閉じて終了 //excel.Quit(); //excel = null; log("全処理が終了");
ここまでがソースコード。
使い方としては,まず上記の内容をBATファイルとして保存。
そして,BATと同じフォルダ上に,適当なExcelファイルを新規作成。
そのエクセルの1シート目に,「はてなカウンター」の内容を貼り付ける。
下記の画面キャプチャみたいに。
アクセス解析の画面のテーブル内容を,左上にそのままコピペすればよい。
行数は,いくらでも下のほうまで続けてよい。
で,そのExcelファイルを,先ほどのBATファイルにドロップする。
これで処理が開始〜完了する。
このバッチがやってくれる事は:
- まず,アクセス解析画面のコピペの中から,ハイパーリンクをURLに変換し,
- PC用・スマホ用・ガラケー用などの異なるURLを統合して,1つのURLとしてアクセス数を合計し直し,
- 統合されたURLのそれぞれに対して,ブラウザ(IE)からアクセスして,記事のタイトルを取得し,Excelのシート上に記録する。
すると,Excelファイルの2シート目には,
- 統合されたURL
- PV数
- 記事のタイトル
が並ぶ。
4列目には,下記のようなExcel関数を書いて,下までオートフィルすればよい。
="▶第"&ROW()&"位:"&CHAR(10)&SUBSTITUTE(C1," - 主に言語とシステム開発に関して","")&" <img src=http://b.hatena.ne.jp/entry/image/"&A1&">"&CHAR(10)&A1&CHAR(10)&"-"&B1&" pv"&CHAR(10)&CHAR(10)
これで,ランキングのHTMLが生成できた。
期間や解析対象などを変えつつ,いくらでも使いまわせる。
とりあえず,本ブログの場合の実行結果を丸ごとペーストしてみる。
2013年にアクセス数が多かった記事のランキングとしての出力結果:
▶第1位:
開発に役立つ,BATファイルの書き方・パターン集 (コマンドプロンプトの定石を体系的に学び,バッチ中級者になろう)
http://language-and-engineering.hatenablog.jp/entry/20130502/PatternsOfMSDOSo...
- 86921 pv
▶第2位:
Tortoise SVNの使い方を覚えてもらうためのページ (初級,中級)
http://language-and-engineering.hatenablog.jp/entry/20110114/p1
- 23715 pv
▶第3位:
ネットワーク図の書き方 (物理/論理構成図の作成手順と,Excelで使えるアイコン素材のリンク集)
http://language-and-engineering.hatenablog.jp/entry/20110916/p1
- 16947 pv
▶第4位:
今から3分で jmeter の使い方を身に付ける (負荷テスト入門)
http://language-and-engineering.hatenablog.jp/entry/20081014/1223905380
- 16247 pv
▶第5位:
J:COMでネット利用時に,無線LANルータを導入し,PCとiPadからWi-Fi接続する方法 (ケーブルモデムにつながらない時のための,設定手順のメモ)
http://language-and-engineering.hatenablog.jp/entry/20111015/p1
- 11496 pv
▶第6位:
Excel VBAで,グラフを自動で描画しよう(データ範囲を動的に変える) + ソフトウェアの品質保証について
http://language-and-engineering.hatenablog.jp/entry/20090516/p1
- 10367 pv
▶第7位:
GoFの23のデザインパターンを,Javaで活用するための一覧表 (パターンごとの要約コメント付き)
http://language-and-engineering.hatenablog.jp/entry/20120330/p1
- 9186 pv
▶第8位:
JavaScriptの動かないコード (中級編) clickイベントを強制的に発生させたい (fireEvent/createEventの使い方)
http://language-and-engineering.hatenablog.jp/entry/20090907/p1
- 8929 pv
▶第9位:
Excel VBAのマクロを,複数のブックから利用する方法 (標準モジュールをブックの外部で管理して,共通ライブラリとして読み込み)
http://language-and-engineering.hatenablog.jp/entry/20090731/p1
- 8905 pv
▶第10位:
Androidアプリで,HTTP通信のPOSTリクエストをする汎用クラス (文字化け無し+非同期タスク)
http://language-and-engineering.hatenablog.jp/entry/20111121/p1
- 8783 pv
▶第11位:
コマンドラインからプロセスを起動・終了する方法 (環境変数とレジストリについて)
http://language-and-engineering.hatenablog.jp/entry/20081028/1225160338
- 8131 pv
▶第12位:
Androidアプリをマーケットに公開する方法の作業手順メモ (リリース時とアップグレード時のチェックリスト)
http://language-and-engineering.hatenablog.jp/entry/20120310/AndroidMarketRel...
- 7803 pv
▶第13位:
Android SDKプログラミング APIリファレンスのリンク集
http://language-and-engineering.hatenablog.jp/entry/20130302/AndroidSDKProgra...
- 7679 pv
▶第14位:
Linux上でシェルが実行される仕組みを,体系的に理解しよう (bash 中級者への道)
http://language-and-engineering.hatenablog.jp/entry/20110617/p1
- 7675 pv
▶第15位:
コマンドプロンプトで,暗記するべき10の必須コマンド (前半) ファイル処理系
http://language-and-engineering.hatenablog.jp/entry/20081001/1222857265
- 7035 pv
▶第16位:
今から3分で,Smartyの使い方を覚えよう (PHPテンプレートエンジンの入門)
http://language-and-engineering.hatenablog.jp/entry/20130107/SmartyPHPTemplat...
- 6728 pv
▶第17位:
韓国語の文字(ハングル)の覚え方。母音・子音ごとに図解で暗記
http://language-and-engineering.hatenablog.jp/entry/20130708/KoreanAlphabetsV...
- 6697 pv
▶第18位:
なんとなくCSSを使っている人が,CSS中級者になるために (「崩れないレイアウト」のセオリー)
http://language-and-engineering.hatenablog.jp/entry/20110216/p1
- 6665 pv
▶第19位:
Excel VBAのマクロで,IEを自動操作しよう (DOMセレクタ関数をVBAで自作)
http://language-and-engineering.hatenablog.jp/entry/20090710/p1
- 6504 pv
▶第20位:
逆コンパイル + 逆アセンブル のための5つの無料ツール
http://language-and-engineering.hatenablog.jp/entry/20081008/1223384382
- 6367 pv
▶第21位:
Word VBA の入門用リンク集 (ワードマクロの,サンプルコードやリファレンス)
http://language-and-engineering.hatenablog.jp/entry/20100314/p1
- 6365 pv
▶第22位:
台湾語のあいさつなど日常会話フレーズ集 (ピンインと声調,カタカナ付き)
http://language-and-engineering.hatenablog.jp/entry/20130308/BasicGreetingsIn...
- 6347 pv
▶第23位:
ハングル検定5級に独学で合格するための対策リンク集 (過去問や単語など)
http://language-and-engineering.hatenablog.jp/entry/20130610/KoreanLicenseLev...
- 6191 pv
▶第24位:
Linux上にSambaで共有フォルダを作り,Windowsから開発環境として利用しよう (環境構成のわかりやすい図解付き)
http://language-and-engineering.hatenablog.jp/entry/20110820/p1
- 6047 pv
▶第25位:
JScript / VBScript (WSH)で,IEを自動操作しよう
http://language-and-engineering.hatenablog.jp/entry/20090713/p1
- 5904 pv
▶第26位:
韓国語(ハングル)で会話するための,最初歩の必須フレーズ
http://language-and-engineering.hatenablog.jp/entry/20130707/KoreanFirstStepP...
- 5698 pv
▶第27位:
今から1時間で,Androidアプリの開発環境を構築し,Windows上でサンプルを動作させる手順
http://language-and-engineering.hatenablog.jp/entry/20110724/p1
- 5590 pv
▶第28位:
メモリの中身を読んでみよう (プロセスをダンプ+解析する方法)
http://language-and-engineering.hatenablog.jp/entry/20081019/1224341559
- 5293 pv
▶第29位:
今から1時間で,64ビットWindows 7上にAndroid開発環境を構築し,サンプルを動作させる手順 (※4.x系のSDKを使用)
http://language-and-engineering.hatenablog.jp/entry/20121017/AdnroidDevelopme...
- 5175 pv
▶第30位:
コマンドプロンプトで,暗記するべき10の必須コマンド (後半)ネットワーク系
http://language-and-engineering.hatenablog.jp/entry/20081002/1222908187
- 5062 pv
↑こういう記事ランキング表が手軽に作れるわけだ。
※なお,本記事は「ランキング生成のためのバッチ」についての記事だから,
本ブログの2013年の人気記事については,別のエントリで改めて言及する。
このバッチの面倒な所は,たまにブラウザが止まってしまうところ。何せIEだから・・・。
その場合,F5を押してリロードすれば処理が再開する。
バッチのソースコードの参考情報:
BATとWSHのコードを1ファイルに混在させるためのshebang記法
http://computer-technology.hateblo.jp/entry/20131025/p1
JScript / VBScript (WSH)で,IEを自動操作しよう
http://language-and-engineering.hatenablog.jp/entry/20090713/p1
URLの末尾のfb_locale=ja_jpはなに?
http://detail.chiebukuro.yahoo.co.jp/...
- 最近、どのブログサービスでもURLの末尾にfb_locale=ja_jpが付いている
Internationalization : Facebook開発者向けドキュメントの日本語訳とTips
http://facebook-docs.oklahome.net/arc...
- Open GraphオブジェクトのクローラーはURLにfb_localeパラメータを付加(例:?locale=en_US)し、X-Facebook- Localeヘッダも渡します。
PROBLEM retrieve fb_locale parameter in the URL OR the X-Facebook-Locale header | GEEK'S BLOG
http://www.raleche.com/content/proble...
- retrieve fb_locale parameter
Kennt jemand das Url anhängsel ?fb_locale=de_DE - ABAKUS
http://www.abakus-internet-marketing....
JavaScriptの関数オブジェクト(1/2) | JavaScriptのQ&A【OKWave】
http://okwave.jp/qa/q7261969.html
正規表現を使いこなす
http://www.web3d-club.net/jikken/Java...
- グループでマッチングした文字列は検索実行後も記憶されており,参照するにはJavaScriptの場合「RegExp.$1」で取り出す
$1...$9 プロパティ (RegExp) (JavaScript)
http://msdn.microsoft.com/ja-jp/libra...
- $1...$9 のプロパティの値は、かっこで囲まれたパターンの検索が成功するたびに変更されます
- 正規表現パターン内に指定できるかっこで囲んだ部分の数に制限はありませんが、保存されるのは最後に見つかった 9 つだけ
文字列結合時に改行を入れたい−CHAR関数:Excel(エクセル)の関数・数式の使い方-文字列
http://www.relief.jp/itnote/archives/...
- Excelのセル内の改行コードは CHAR(10)
言葉を簡単に入れ替えるには?SUBSTITUTE関数で文字を置き換えてみよう - FMVサポート : 富士通
http://azby.fmworld.net/usage/excel-f...
- =SUBSTITUTE(文字列,検索文字列,置換文字列,対象)
関連する記事:
2012年のトップ記事のまとめ
http://language-and-engineering.hatenablog.jp/entry/20130103/p1
はてなダイアリーに執筆した記事一覧を,表形式に整理するブックマークレット (アーカイブページを,Excelに貼り付けやすく整形加工)
http://language-and-engineering.hatenablog.jp/entry/20140102/p1
はてブのマイページから,情報を一括して整形・抽出するブックマークレット
http://language-and-engineering.hatenablog.jp/entry/20131229/p1
2008年の記事 TOP 5
http://language-and-engineering.hatenablog.jp/entry/20081231/1230726519