概要とアプローチ
ウェブサイトのアクセス解析を行う際、クライアントサイドの統計コードに依存するのではなく、サーバーレスなスクリプトを活用して外部データを抽出する方法があります。本稿では、Node.js 環境を利用して特定のブログプラットフォームから閲覧数を自動的に抽出し、合算するツールの実装プロセスについて解説します。
技術スタック
- ランタイム: Node.js (HTTP 通信・非同期処理)
- DOM パーサー: Cheerio (jQuery 類似ライブラリによる HTML 解析)
- 非同期制御: Promises および async/await構文
- パターンマッチング: 正規表現 (RegEx)
データソースの構造分析
対象サイトの URL 構造を確認すると、ページ遷移はクエリパラメータ page の変更によって実現されています。したがって、カウント処理ではこのパラメータをループさせ、各ページの HTML レスポンスを取得する必要があります。一般的に、閲覧数は HTML メタタグや JavaScript コード内ではなく、特定の DOM クラス名を持つテキストとして格納されていることが多いです。
実装手順
1. 単一ページの抽出ロジック
まず、与えられた URL から該当ページの HTML を取得し、指定されたクラス内のテキストから数字を抽出する関数を作成します。変数名をより意味のあるものに変更し、スコープ管理を明確化します。
const http = require('http');
const cheerio = require('cheerio');
/**
* 特定ページの閲覧数データを取得する Promise
* @param {string} baseUrl - ベース URL
* @param {number} pageNum - ページ番号
* @returns {Promise<number>} 閲覧数の合計値
*/
function fetchPageViewCount(baseUrl, pageNum) {
return new Promise((resolve, reject) => {
const url = baseUrl + `/default.html?page=${pageNum}`;
http.get(url, (res) => {
let htmlContent = '';
// ステータスチェック
if (res.statusCode !== 200) {
resolve(0);
return;
}
res.on('data', (chunk) => {
htmlContent += chunk;
});
res.on('end', () => {
// ページが存在しない(空である)場合は 0 を返す
if (!//.test(htmlContent)) {
resolve(0);
return;
}
const $ = cheerio.load(htmlContent);
const descText = $('.postDesc').text();
if (!descText) {
resolve(0);
return;
}
// 正規表現で閲覧数を抽出:例「阅读(1234)」の場合
const viewPattern = /阅读\((\d+)\)/g;
let match;
let pageCountTotal = 0;
while ((match = viewPattern.exec(descText)) !== null) {
pageCountTotal += Number(match[1]);
}
console.log(`ページ ${pageNum}: ${pageCountTotal} 件`);
resolve(pageCountTotal);
});
}).on('error', reject);
});
}
2. 並列処理と自動終了ロジック
単純に全ページを同時にリクエストする(Promise.all)方式は、サーバー負荷が集中するリスクがあります。また、コンテンツがないページに達しても無限ループになる可能性があります。これを解決するために、async/await を使用し、一つずつ処理を行いながら結果がある間ループを継続させるアプローチを採用します。
const getOnePageView = require('./pageFetcher'); // 上記ロジックを参照
(async function aggregateViews() {
const host = 'http://www.cnblogs.com/';
const authorId = process.argv[2] || 'imgss';
let currentPage = 1;
let grandTotal = 0;
try {
while (true) {
// ページデータ取得
const count = await getOnePageView(host + authorId + '/', currentPage);
if (count > 0) {
grandTotal += count;
currentPage++; // 次のページへ進める
} else {
// データが存在しない場合、ループを終了
break;
}
}
console.log(`総閲覧数: ${grandTotal}`);
} catch (err) {
console.error('エラー発生:', err.message);
}
})();
改善点とベストプラクティス
初期実装では固定数のページ数に対して並列処理を行っていましたが、改修版では以下の点を強化しました。
- 動的ループ制御: エンドポイントまで到達していないことを検知して自動停止します。
- エラーハンドリング: ネットワークエラーが発生した場合に処理が中断されないよう try-catch ブロックを追加しました。
- 可読性向上: 関数の引数と戻り値を明確にし、グローバル変数の乱発を防ぎました。