/**
* Qiita APIから記事を複数ページに渡って取得し、スプレッドシートに出力する関数。
* 組織名と日付をコード内に直接指定し、指定された形式で出力する。
* ユーザー単位でいいね数と記事投稿数を集計し、それぞれ多い順に表示する。
* さらに、いいね数トップ10の記事情報をN〜P列に出力する。
*/
function fetchAndOutputQiitaArticlesWithTopLikes() {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getSheetByName('シート1'); // ☆ココをあなたのシート名に修正!
// 例: const sheet = spreadsheet.getSheetByName('シート1');
// エラーチェック: シートが存在しない場合
if (!sheet) {
Logger.log('指定されたシートが見つかりません。シート名を確認してください。');
SpreadsheetApp.getUi().alert('エラー', '指定されたシートが見つかりません。スクリプト内のシート名を確認してください。', SpreadsheetApp.getUi().ButtonSet.OK);
return;
}
// 既存のデータをクリア(ヘッダー行は残す)
// J列からP列までの集計データ範囲もクリア
sheet.getRange('J:P').clearContent();
const lastRow = sheet.getLastRow();
if (lastRow > 1) { // ヘッダー行のみの場合はクリアしない
sheet.getRange(2, 1, lastRow - 1, sheet.getLastColumn()).clearContent();
}
// --- ヘッダー行を設定 (列順) ---
const articleHeaders = ['日付', 'ユーザー名', 'タイトル', 'いいね数', 'ストック数', '記事URL'];
sheet.getRange(1, 1, 1, articleHeaders.length).setValues([articleHeaders]).setFontWeight('bold');
// --- 検索条件をコード内に直接指定 ---
// ☆ここをあなたの組織名と日付に修正!
const organization = 'uluru'; // Qiitaの組織ID(URLのorganizations/の次の部分)
const startDate = '2025-01-01'; // 検索開始日 (YYYY-MM-DD 形式)
// 実行日を終了日とする。必要であれば特定の固定日付 '2025-06-30' などに設定も可能
const endDate = Utilities.formatDate(new Date(), SpreadsheetApp.getActiveSpreadsheet().getSpreadsheetTimeZone(), 'yyyy-MM-dd');
const apiToken = ''; // ★オプション:ここにQiita APIの個人用アクセストークンを記載(推奨)
// 例: const apiToken = 'your_qiita_api_token_here';
// トークン設定方法は「補足:APIトークンの設定(推奨)」を参照
const perPage = 100; // 1ページあたりの取得件数(Qiita APIの最大は100件)
// Qiita APIのリクエストヘッダー
const requestHeaders = {
'Accept': 'application/json', // JSON形式でレスポンスを受け取ることを指定
};
if (apiToken) {
requestHeaders['Authorization'] = `Bearer ${apiToken}`; // トークンがあれば認証ヘッダーに追加
}
let allArticles = []; // 取得した全記事を格納する配列
let page = 1; // 取得するページ番号
const maxPages = 10; // 取得する最大ページ数(GASの実行時間制限やAPI制限を考慮し、上限を設定)
try {
// クエリ文字列を構築 (例: "org:uluru created:>=2025-01-01 created:
const query = `org:${organization} created:>=${startDate} created:${endDate}`;
const baseUrl = `https://qiita.com/api/v2/items?query=${encodeURIComponent(query)}&per_page=${perPage}`;
// 複数ページに渡って記事を取得するループ
while (page maxPages) {
const urlWithPage = `${baseUrl}&page=${page}`; // ページ番号を追加したURL
Logger.log(`Fetching: ${urlWithPage}`); // デバッグ用にログに出力
const options = {
'headers': requestHeaders,
'muteHttpExceptions': true // エラー時も例外を投げずにレスポンスを取得し、自分でエラーコードをチェック
};
const response = UrlFetchApp.fetch(urlWithPage, options); // APIリクエストを実行
const statusCode = response.getResponseCode(); // HTTPステータスコードを取得
const jsonText = response.getContentText(); // レスポンス本文をテキストで取得
// APIからの応答が成功(200 OK)以外の場合
if (statusCode !== 200) {
let errorMessage = `APIからエラー応答: ステータスコード ${statusCode}`;
try {
const errorData = JSON.parse(jsonText); // エラーレスポンスがJSON形式か試す
if (errorData.message) {
errorMessage += ` - ${errorData.message}`; // エラーメッセージがあれば追加
}
if (errorData.type) {
errorMessage += ` (Type: ${errorData.type})`; // エラータイプがあれば追加
}
} catch (e) {
errorMessage += ` - レスポンス: ${jsonText.substring(0, 200)}...`; // JSONでない場合はレスポンスの一部を表示
}
throw new Error(errorMessage); // エラーを投げてcatchブロックで処理
}
const pageArticles = JSON.parse(jsonText); // レスポンスをJSONとしてパース
if (pageArticles.length === 0) {
// 取得できる記事がなくなったらループを終了
break;
}
allArticles = allArticles.concat(pageArticles); // 取得した記事を全記事リストに追加
// 1ページあたりの記事数がperPage(100件)未満であれば、それが最後のページと判断しループ終了
if (pageArticles.length perPage) {
break;
}
page++; // 次のページへ
}
// 記事が1件も見つからなかった場合
if (allArticles.length === 0) {
sheet.getRange('A2').setValue('該当する記事が見つかりませんでした。検索条件(組織名、日付)を確認してください。');
Logger.log('No articles found for the given criteria.');
return;
}
// --- 記事を日付の古い順にソート ---
allArticles.sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
// 曜日を日本語で表現するための配列
const weekdays = ['日', '月', '火', '水', '木', '金', '土'];
// 取得した記事データを整形して配列に格納 (記事ごとの出力データ)
const outputArticleData = allArticles.map(article => {
const createdDate = new Date(article.created_at);
const year = createdDate.getFullYear();
const month = (createdDate.getMonth() + 1).toString().padStart(2, '0'); // 0埋め
const day = createdDate.getDate().toString().padStart(2, '0'); // 0埋め
const weekday = weekdays[createdDate.getDay()]; // 曜日を取得
// 日付を「YYYY/MM/DD(曜日)」形式にフォーマット
const formattedDate = `${year}/${month}/${day}(${weekday})`;
return [
formattedDate, // A列: 日付
article.user ? article.user.id : 'N/A', // B列: ユーザー名
article.title, // C列: タイトル
article.likes_count, // D列: いいね数
article.stocks_count, // E列: ストック数
article.url // F列: 記事URL
];
});
// スプレッドシートのA列からF列に記事ごとに出力
sheet.getRange(2, 1, outputArticleData.length, outputArticleData[0].length).setValues(outputArticleData);
// --- ユーザー単位でいいね数と記事投稿数を集計 ---
const userLikes = {}; // ユーザーごとの総いいね数を格納
const userArticleCounts = {}; // ユーザーごとの記事投稿数を格納
allArticles.forEach(article => {
const userId = article.user ? article.user.id : 'N/A'; // ユーザーIDを取得
// いいね数の集計
const likes = article.likes_count || 0; // いいね数がnullの場合は0として扱う
if (userLikes[userId]) {
userLikes[userId] += likes;
} else {
userLikes[userId] = likes;
}
// 記事投稿数の集計
if (userArticleCounts[userId]) {
userArticleCounts[userId]++;
} else {
userArticleCounts[userId] = 1;
}
});
// いいね数の集計データを配列に変換し、いいね数が多い順にソート
const sortedUserLikes = Object.entries(userLikes)
.map(([userId, totalLikes]) => [userId, totalLikes]) // [キー, 値]の配列に変換
.sort((a, b) => b[1] - a[1]); // いいね数で降順ソート
// 記事投稿数の集計データを配列に変換し、投稿数が多い順にソート
const sortedUserArticleCounts = Object.entries(userArticleCounts)
.map(([userId, count]) => [userId, count]) // [キー, 値]の配列に変換
.sort((a, b) => b[1] - a[1]); // 投稿数で降順ソート
// J列とK列のヘッダーを設定(ユーザーごとの総いいね数)
sheet.getRange('J1').setValue('ユーザー名 (総いいね)').setFontWeight('bold');
sheet.getRange('K1').setValue('総いいね数').setFontWeight('bold');
// J列とK列に集計結果を出力
if (sortedUserLikes.length > 0) {
sheet.getRange(2, 10, sortedUserLikes.length, sortedUserLikes[0].length).setValues(sortedUserLikes);
} else {
sheet.getRange('J2').setValue('ユーザーごとのいいね集計データがありません');
}
// L列とM列のヘッダーを設定(ユーザーごとの記事投稿数)
sheet.getRange('L1').setValue('ユーザー名 (投稿数)').setFontWeight('bold');
sheet.getRange('M1').setValue('記事投稿数').setFontWeight('bold');
// L列とM列に集計結果を出力
if (sortedUserArticleCounts.length > 0) {
sheet.getRange(2, 12, sortedUserArticleCounts.length, sortedUserArticleCounts[0].length).setValues(sortedUserArticleCounts);
} else {
sheet.getRange('L2').setValue('ユーザーごとの投稿数集計データがありません');
}
// --- いいね数トップ10の記事情報をN, O, P列に出力 ---
// 全記事をいいね数で降順にソート
const sortedArticlesByLikes = [...allArticles].sort((a, b) => b.likes_count - a.likes_count);
// トップ10(または全記事が10未満なら全記事)を抽出
const top10Articles = sortedArticlesByLikes.slice(0, 10);
// N, O, P列のヘッダーを設定
sheet.getRange('N1').setValue('ユーザー名 (トップ記事)').setFontWeight('bold');
sheet.getRange('O1').setValue('記事名 (トップ記事)').setFontWeight('bold');
sheet.getRange('P1').setValue('いいね数 (トップ記事)').setFontWeight('bold');
// N, O, P列に出力するデータを作成
if (top10Articles.length > 0) {
const outputTop10Data = top10Articles.map(article => [
article.user ? article.user.id : 'N/A', // N列: ユーザー名
article.title, // O列: 記事名
article.likes_count // P列: いいね数
]);
sheet.getRange(2, 14, outputTop10Data.length, outputTop10Data[0].length).setValues(outputTop10Data);
} else {
sheet.getRange('N2').setValue('いいね数トップ記事がありません');
}
// 処理完了のログとアラート
Logger.log(`記事を ${allArticles.length} 件取得し、スプレッドシートに出力しました。`);
Logger.log(`ユーザーごとのいいね数を ${sortedUserLikes.length} 件集計し、J列とK列に出力しました。`);
Logger.log(`ユーザーごとの投稿数を ${sortedUserArticleCounts.length} 件集計し、L列とM列に出力しました。`);
Logger.log(`いいね数トップ10の記事を ${top10Articles.length} 件集計し、N列からP列に出力しました。`);
} catch (e) {
// エラー発生時のログとアラート
Logger.log('処理中にエラーが発生しました: ' + e.message);
sheet.getRange('A2').setValue('エラー: ' + e.message.substring(0, 100) + '...');
}
}
Views: 0