「はてなカウンター」から1年分の「検索キーワード」情報を抽出し,Excelに記録するバッチ
自分のブログが,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 ビスタのハイパー機能を使ってエクセルを目次機能�% 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