0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Qiitaアドベントカレンダー集計の自動化(&会社のカレンダーのランキングの結果発表付き)

0
Posted at

はじめに

株式会社ビザスク - VisasQ Inc. Advent Calendar 202525日目 の投稿です!

image.png
こちらの投稿によりどうにか(25日中内ではないものの)完走できて良かったです🙇 (ご参加・ご閲覧ありがとうございました🙏🙏🙏)

今回は、特定のカレンダーの参加者のうち、投稿数やいいね数、いいねした数のランキング作成の自動化を行う簡易なコードの紹介です!
※会社(ビザスク)のカレンダーのランキングの結果発表付き

集計するもの

【お礼 - Organization応援グッズ】会社のアドベントカレンダーを盛り上げるためにやったこと(準備編) で紹介いたしました...

  • 投稿数部門
  • 注目度部門
  • 他己関心部門

の集計を自動化します!
※QiitaアドベントカレンダーはQiita以外の記事のURLでも参加できるので、その参加がある場合は自動で集計した内容の手直しが必要なため完全な自動化にはならないもののだいぶ捗るので作ってみました!

使う物

  • Node.js
  • https://qiita.com/advent-calendar/2025/<任意のカレンダー 例:visasq>/feed
    • XMLをparseしカレンダー上の記事(item_id)一覧を取得
    • また author (ユーザーID)も取得
    • シリーズ2 以降の記事については、今回は想定しない (残念ながら筆者の会社の今年のカレンダーではシリーズ1で精一杯🙏)
  • GET /api/v2/items/:item_id/likes (記事のいいね数を取得)

やること

  • カレンダー記事一覧を取得
  • 各記事の投稿者情報を集計し、投稿数ランキングを生成
  • 各記事のいいね数を取得し、ランキングを生成
  • 各記事のいいねしたユーザー数を集計し、ランキングを生成
    • ただし、カレンダーに参加してないユーザーのいいねは除外する
  • 結果をコンソールに出力

できたコード

tips: 冒頭に自然言語で目的や使うべき API とか処理内容とかいろいろ書いておくと AI に「続きお願い!」程度の指示で割と書いてくれる✨️

/**
 * # qiita-calendar-ranking.js
 * 
 * 目的: Qiita Advent Calendar の記事に対して、投稿数ランキング、いいね数ランキングと、いいねした数ランキングを生成する
 * 備考: 【お礼 - Organization応援グッズ】会社のアドベントカレンダーを盛り上げるためにやったこと(準備編) https://qiita.com/waricoma/items/2db881348c86f7257180 で紹介された「投稿数部門」「注目度部門」「社内評価部門」「他己関心部門」「ママパパ応援企画」の内、「投稿数部門」「注目度部門」「他己関心部門」の集計の支援を目的とするスクリプト
 * 
 * ## Qiita リソース
 * 
 * - https://qiita.com/advent-calendar/2025/visasq/feed
 *   - XMLをparseしカレンダー上の記事(item_id)一覧を取得
 *   - また author (ユーザーID)も取得
 *.  - シリーズ2 以降の記事については、今回は想定しない (残念ながら筆者の会社の今年のカレンダーではシリーズ1で精一杯🙏)
 * - GET /api/v2/items/:item_id/likes (記事のいいね数を取得)
 *   - docs: https://qiita.com/api/v2/docs#%E3%81%84%E3%81%84%E3%81%AD
 *   - 誰がいいねしたかの情報も取得できるので取得しオンメモリで利用する
 *   - Qiita API は GET なら認証なしで利用可
 * 
 * ## やること
 * 
 * - カレンダー記事一覧を取得
 * - 各記事の投稿者情報を集計し、投稿数ランキングを生成
 * - 各記事のいいね数を取得し、ランキングを生成
 * - 各記事のいいねしたユーザー数を集計し、ランキングを生成
 *   - ただし、カレンダーに参加してないユーザーのいいねは除外する
 * - 結果をコンソールに出力
 */

// ユーティリティ関数
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

// 設定
const CALENDAR_NAME = 'visasq'; // アドベントカレンダーの名前
const YEAR = 2025;
const FEED_URL = `https://qiita.com/advent-calendar/${YEAR}/${CALENDAR_NAME}/feed`;
const API_BASE = 'https://qiita.com/api/v2';
const DELAY_MS = 1000; // API呼び出し間の遅延(レート制限対策で増やし)

/**
 * 簡易XMLパーサー(Atom/RSS用)
 * @param {string} xmlText XML文字列
 * @returns {Array} 記事情報の配列
 */
function parseRSSFeed(xmlText) {
  const articles = [];
  
  // Atom形式とRSS形式の両方に対応
  const isAtom = xmlText.includes('<feed');
  console.log(`XMLフォーマット: ${isAtom ? 'Atom' : 'RSS'}`);
  
  if (isAtom) {
    // Atom形式の場合、<entry>タグを抽出
    const entryMatches = xmlText.match(/<entry[^>]*>[\s\S]*?<\/entry>/g) || [];
    console.log(`Atomから${entryMatches.length}個のentryタグを検出`);
    
    let qiitaCount = 0;
    let externalCount = 0;
    
    for (let i = 0; i < entryMatches.length; i++) {
      const entryXml = entryMatches[i];
      
      // Atom形式の要素を抽出(より柔軟なマッチング)
      let titleMatch = entryXml.match(/<title[^>]*>([^<]+)<\/title>/);
      if (!titleMatch) {
        titleMatch = entryXml.match(/<title[^>]*><!\[CDATA\[([^\]]+)\]\]><\/title>/);
      }
      
      let linkMatch = entryXml.match(/<link[^>]*href="([^"]+)"[^>]*\/?>/);
      if (!linkMatch) {
        linkMatch = entryXml.match(/<link[^>]*>([^<]+)<\/link>/);
      }
      
      let authorMatch = entryXml.match(/<author[^>]*>[\s\S]*?<name>([^<]+)<\/name>[\s\S]*?<\/author>/);
      if (!authorMatch) {
        authorMatch = entryXml.match(/<author>([^<]+)<\/author>/);
      }
      
      const title = titleMatch ? titleMatch[1].trim() : 'タイトル不明';
      const link = linkMatch ? linkMatch[1].trim() : '';
      const author = authorMatch ? authorMatch[1].trim() : '作者不明';
      
      if (!link) {
        console.log(`entry ${i + 1}: リンクが見つからないためスキップ`);
        continue;
      }
      
      // qiita以外のURLも投稿数ランキングに含める
      const isQiitaUrl = link.includes('qiita.com');
      
      if (isQiitaUrl) {
        const itemIdMatch = link.match(/\/items\/([a-f0-9]+)/);
        
        if (itemIdMatch) {
          const itemId = itemIdMatch[1];
          qiitaCount++;
          
          articles.push({
            itemId,
            title,
            author,
            link,
            isQiita: true
          });
          
          console.log(`entry ${i + 1}: ✓ qiita記事を追加 - ${title}`);
        } else {
          console.log(`entry ${i + 1}: 記事IDが抽出できないためスキップ - ${link}`);
        }
      } else {
        externalCount++;
        
        articles.push({
          itemId: null, // 外部URLにはitemIdなし
          title,
          author,
          link,
          isQiita: false
        });
        
        console.log(`entry ${i + 1}: ✓ 外部記事を追加 (投稿数ランキングのみ) - ${title}`);
      }
    }
    
    console.log(`\n=== 集計結果 ===`);
    console.log(`全entry数: ${entryMatches.length}`);
    console.log(`qiita記事: ${qiitaCount} (いいね数取得対象)`);
    console.log(`外部記事: ${externalCount} (投稿数ランキングのみ)`);
    console.log(`収集対象: ${qiitaCount + externalCount}`);
    console.log(`スキップ: ${entryMatches.length - qiitaCount - externalCount}`);
  } else {
    // RSS形式の場合、<item>タグを抽出
    const itemMatches = xmlText.match(/<item[^>]*>[\s\S]*?<\/item>/g) || [];
    console.log(`RSSから${itemMatches.length}個のitemタグを検出`);
    
    for (let i = 0; i < itemMatches.length; i++) {
      const itemXml = itemMatches[i];
      
      // 各要素を抽出
      let titleMatch = itemXml.match(/<title><!\[CDATA\[([^\]]+)\]\]><\/title>/);
      if (!titleMatch) {
        titleMatch = itemXml.match(/<title[^>]*>([^<]+)<\/title>/);
      }
      
      let linkMatch = itemXml.match(/<link>([^<]+)<\/link>/);
      if (!linkMatch) {
        linkMatch = itemXml.match(/<link[^>]*href="([^"]+)"[^>]*\/?>/);
      }
      
      let creatorMatch = itemXml.match(/<dc:creator><!\[CDATA\[([^\]]+)\]\]><\/dc:creator>/);
      if (!creatorMatch) {
        creatorMatch = itemXml.match(/<dc:creator[^>]*>([^<]+)<\/dc:creator>/);
      }
      if (!creatorMatch) {
        creatorMatch = itemXml.match(/<author[^>]*>([^<]+)<\/author>/);
      }
      
      const title = titleMatch ? titleMatch[1].trim() : 'タイトル不明';
      const link = linkMatch ? linkMatch[1].trim() : '';
      const author = creatorMatch ? creatorMatch[1].trim() : '作者不明';
      
      if (!link) {
        console.log(`item ${i + 1}: リンクが見つからないためスキップ`);
        continue;
      }
      
      // qiita以外のURLも投稿数ランキングに含める
      const isQiitaUrl = link.includes('qiita.com');
      
      if (isQiitaUrl) {
        const itemIdMatch = link.match(/\/items\/([a-f0-9]+)/);
        
        if (itemIdMatch) {
          const itemId = itemIdMatch[1];
          
          articles.push({
            itemId,
            title,
            author,
            link,
            isQiita: true
          });
          
          console.log(`item ${i + 1}: ✓ qiita記事を追加 - ${title}`);
        } else {
          console.log(`item ${i + 1}: 記事IDが抽出できないためスキップ - ${link}`);
        }
      } else {
        articles.push({
          itemId: null, // 外部URLにはitemIdなし
          title,
          author,
          link,
          isQiita: false
        });
        
        console.log(`item ${i + 1}: ✓ 外部記事を追加 (投稿数ランキングのみ) - ${title}`);
      }
    }
  }
  
  return articles;
}

/**
 * XMLフィードからカレンダーの記事一覧を取得
 * @returns {Promise<Array>} 記事情報の配列
 */
async function getCalendarArticles() {
  try {
    console.log(`フィードを取得中: ${FEED_URL}`);
    const response = await fetch(FEED_URL);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const xmlData = await response.text();
    
    // デバッグ用:XMLデータの一部を表示
    console.log('XMLデータの先頭500文字:');
    console.log(xmlData.substring(0, 500));
    console.log('---');
    
    const articles = parseRSSFeed(xmlData);
    
    console.log(`${articles.length}件の記事を取得しました`);
    return articles;
    
  } catch (error) {
    console.error('カレンダー記事取得エラー:', error);
    throw error;
  }
}

/**
 * 記事のいいね情報を取得(ページネーション対応)
 * @param {string} itemId 記事ID
 * @param {string} title 記事タイトル
 * @param {string} link 記事URL
 * @param {string} author 筆者ID
 * @returns {Promise<Array>} いいねしたユーザー情報の配列
 */
async function getArticleLikes(itemId, title = '', link = '', author = '') {
  const maxRetries = 3;
  const retryDelay = 2000; // リトライ時の遅延(2秒)
  const allLikes = [];
  let page = 1;
  let hasMorePages = true;
  
  console.log(`いいね取得中: ${itemId}`);
  console.log(`  タイトル: ${title}`);
  console.log(`  筆者ID: ${author}`);
  console.log(`  URL: ${link}`);
  
  while (hasMorePages) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        const url = `${API_BASE}/items/${itemId}/likes?page=${page}`;
        console.log(`  ページ ${page} を取得中... (API URL: ${url})${attempt > 1 ? ` (リトライ ${attempt}/${maxRetries})` : ''}`);
        
        const response = await fetch(url);
        
        if (!response.ok) {
          if (response.status === 404) {
            console.warn(`記事が見つかりません: ${itemId}`);
            return allLikes;
          }
          
          if (response.status === 403) {
            console.warn(`API アクセス制限エラー (${itemId}, page ${page}): 試行 ${attempt}/${maxRetries}`);
            
            if (attempt < maxRetries) {
              console.log(`  ${retryDelay}ms待機してリトライします...`);
              await sleep(retryDelay);
              continue; // リトライ
            } else {
              console.error(`  最大リトライ回数に達しました。ページ ${page} をスキップして継続します。`);
              hasMorePages = false;
              break;
            }
          }
          
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const likes = await response.json();
        console.log(`    ページ ${page}: ${likes.length}件取得`);
        
        if (likes.length === 0) {
          hasMorePages = false;
        } else {
          allLikes.push(...likes);
          page++;
          
          // ページ間で短い待機(API制限対策)
          if (hasMorePages) {
            await sleep(200);
          }
        }
        
        break; // 成功したのでリトライループを抜ける
        
      } catch (error) {
        if (attempt < maxRetries && (error.message.includes('HTTP error! status: 403') || error.message.includes('fetch'))) {
          console.error(`いいね取得エラー (${itemId}, page ${page}) 試行 ${attempt}/${maxRetries}:`, error.message);
          console.log(`  ${retryDelay}ms待機してリトライします...`);
          await sleep(retryDelay);
          continue;
        }
        
        console.error(`いいね取得エラー (${itemId}, page ${page}):`, error);
        hasMorePages = false;
        break;
      }
    }
  }
  
  console.log(`  合計いいね数: ${allLikes.length}件 (${page - 1}ページ)`);
  
  return allLikes;
}

/**
 * 投稿数ランキングを生成
 * @param {Array} articles 記事配列
 * @returns {Array} ランキング配列
 */
function generatePostCountRanking(articles) {
  const postCounts = {};
  
  for (const article of articles) {
    const author = article.author;
    postCounts[author] = (postCounts[author] || 0) + 1;
  }
  
  const ranking = Object.entries(postCounts)
    .map(([author, count]) => ({ author, count }))
    .sort((a, b) => b.count - a.count);
  
  return ranking;
}

/**
 * いいね数ランキングを生成
 * @param {Array} articles 記事配列(いいね情報付き)
 * @returns {Array} ランキング配列
 */
function generateLikeCountRanking(articles) {
  const likeCounts = {};
  
  for (const article of articles) {
    const author = article.author;
    const likeCount = article.likes ? article.likes.length : 0;
    
    likeCounts[author] = (likeCounts[author] || 0) + likeCount;
  }
  
  const ranking = Object.entries(likeCounts)
    .map(([author, count]) => ({ author, count }))
    .sort((a, b) => b.count - a.count);
  
  return ranking;
}

/**
 * いいねした数ランキングを生成(カレンダー参加者のみ)
 * @param {Array} articles 記事配列(いいね情報付き)
 * @returns {Array} ランキング配列
 */
function generateLikedCountRanking(articles) {
  // カレンダー参加者のリストを作成
  const participants = new Set(articles.map(article => article.author));
  const likedCounts = {};
  
  // 各記事のいいねを確認
  for (const article of articles) {
    if (!article.likes) continue;
    
    for (const like of article.likes) {
      const liker = like.user.id;
      
      // カレンダー参加者のいいねのみカウント
      if (participants.has(liker)) {
        likedCounts[liker] = (likedCounts[liker] || 0) + 1;
      }
    }
  }
  
  const ranking = Object.entries(likedCounts)
    .map(([author, count]) => ({ author, count }))
    .sort((a, b) => b.count - a.count);
  
  return ranking;
}

/**
 * ランキングを表示
 * @param {string} title ランキングタイトル
 * @param {Array} ranking ランキング配列
 * @param {string} unit 単位
 */
function displayRanking(title, ranking, unit = '') {
  console.log(`\n=== ${title} ===`);
  
  if (ranking.length === 0) {
    console.log('データがありません');
    return;
  }
  
  let currentRank = 1;
  let currentCount = ranking[0].count;
  
  for (let i = 0; i < ranking.length; i++) {
    const { author, count } = ranking[i];
    
    if (count < currentCount) {
      currentRank = i + 1;
      currentCount = count;
    }
    
    console.log(`${currentRank}位: ${author} (${count}${unit})`);
  }
}

/**
 * メイン処理
 */
async function main() {
  try {
    console.log('=== Qiita Advent Calendar ランキング集計開始 ===\n');
    
    // カレンダー記事一覧を取得
    const articles = await getCalendarArticles();
    
    if (articles.length === 0) {
      console.log('記事が見つかりませんでした');
      return;
    }
    
    // qiita記事のいいね情報を取得(外部記事は除外)
    const qiitaArticles = articles.filter(article => article.isQiita);
    console.log(`\nいいね情報取得対象: ${qiitaArticles.length}件のqiita記事`);
    
    for (let i = 0; i < qiitaArticles.length; i++) {
      const article = qiitaArticles[i];
      console.log(`\n--- ${i + 1}/${qiitaArticles.length} ---`);
      article.likes = await getArticleLikes(
        article.itemId, 
        article.title, 
        article.link, 
        article.author
      );
      
      // API制限対策で少し待機
      if (i < qiitaArticles.length - 1) {
        await sleep(DELAY_MS);
      }
    }
    
    // 外部記事にはいいね情報を設定しない
    const externalArticles = articles.filter(article => !article.isQiita);
    externalArticles.forEach(article => {
      article.likes = []; // 空の配列を設定
    });
    
    if (externalArticles.length > 0) {
      console.log(`\n外部記事 ${externalArticles.length}件はいいね数取得をスキップしました`);
      externalArticles.forEach((article, index) => {
        console.log(`  ${index + 1}. ${article.title} by ${article.author}`);
        console.log(`     URL: ${article.link}`);
      });
    }
    
    console.log('\n=== 集計結果 ===');
    
    // 投稿数ランキング
    const postRanking = generatePostCountRanking(articles);
    displayRanking('投稿数ランキング', postRanking, '投稿');
    
    // いいね数ランキング(記事がもらったいいね数)
    const likeRanking = generateLikeCountRanking(articles);
    displayRanking('いいね数ランキング(記事がもらったいいね)', likeRanking, 'いいね');
    
    // いいねした数ランキング(カレンダー参加者のみ)
    const likedRanking = generateLikedCountRanking(articles);
    displayRanking('いいねした数ランキング(カレンダー参加者のみ)', likedRanking, 'いいね');
    
    console.log('\n=== 集計完了 ===');
    
  } catch (error) {
    console.error('エラーが発生しました:', error);
    process.exit(1);
  }
}

// スクリプトが直接実行された場合のみメイン処理を実行
if (require.main === module) {
  main();
}

使い方

とくにライブラリのインストールや API Key の設定は不要です✨️
fetch を使うのである程度新しい Node.js ランタイムをご利用ください 🙏

[~]> node qiita-calendar-ranking.js

結果発表(19日時点)

TIPS: Qiita Tシャツなどの 🎁 のお渡しを行った表彰式(ビザスクアドベントカレンダーアワード2025) は 2025年12月19日(金曜日) に開催された 開発部合同オフサイト のランチタイムのお時間を借りて実施いたしました🥳
オフサイトの様子がわかるブログは年始に投稿されるらしいのでよければ VISASQ Dev Blog https://tech.visasq.com/ をチェックしてください🙏🙏🙏

※ note, hatena などでの参加者のイイネ数など手直し反映済↓

投稿数部門

お名前 備考
🥇最優秀賞 @kei3524848 , @waricoma 19日迄に各5投稿
🥈優秀賞 @gaplant_tr5 お忙しい中2投稿🙇
🥉入賞 @tomoris , @rtkjm22 , @17_3840 , @knito , @macomaru , @mharada770 各1投稿 執筆感謝です!

注目度部門

お名前 備考
🥇最優秀賞 @waricoma 一部の投稿が100イイネ以上 & Qiitaトレンド2位へ
🥈優秀賞 @kei3524848 13イイネ
🥉入賞 @17_3840 11イイネ

他己関心部門

お名前 備考
🏆️特別賞 @kei3524848 , @waricoma 期間中の他の社員の投稿に各11イイネ

動作ログ(31日当記事投稿前時点)

[~]> node qiita-calendar-ranking.js
=== Qiita Advent Calendar ランキング集計開始 ===

フィードを取得中: https://qiita.com/advent-calendar/2025/visasq/feed
XMLデータの先頭500文字:
<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="ja-JP" xmlns="http://www.w3.org/2005/Atom" xmlns:re="http://purl.org/atompub/rank/1.0">
  <id>tag:qiita.com,2012:/advent-calendar/2025/visasq/feed</id>
  <link rel="alternate" type="text/html" href="https://qiita.com"/>
  <link rel="self" type="application/atom+xml" href="https://qiita.com/advent-calendar/2025/visasq/feed"/>
  <title>株式会社ビザスク - VisasQ Inc. Advent Calendarの記事 - Qiita</title>
  <updated>2025-12-31T14:26:10+09:00</updated>
  <e
---
XMLフォーマット: Atom
Atomから24個のentryタグを検出
entry 1: ✓ qiita記事を追加 - [GitHub] 見てるPRがstagingマージ済か確認するブックマークレット
entry 2: ✓ qiita記事を追加 - SlackとGASで作るフィルタ/除外機能付きRSS/Feedリーダ(connpass用)
entry 3: ✓ qiita記事を追加 - [Slack] 任意のチャンネルから期間指定で会話エクスポート
entry 4: ✓ qiita記事を追加 - Confluence で 3か月以上更新されてないページを通知する
entry 5: ✓ qiita記事を追加 - できる限り正確にAI文字起こしをする方法
entry 6: ✓ qiita記事を追加 - ボイパでAIとジャムセッションしてみたかったけどできなかった
entry 7: ✓ 外部記事を追加 (投稿数ランキングのみ) - AI Engineering Summit Tokyo 2025 参加&amp;登壇してきました
entry 8: ✓ qiita記事を追加 - gitコマンドでTODOアプリつくってみた
entry 9: ✓ 外部記事を追加 (投稿数ランキングのみ) - UIデザイナーが家を建てる(建築はわからんけど動線設計はこだわりたい)
entry 10: ✓ 外部記事を追加 (投稿数ランキングのみ) - クライアントログイン情報フロー + Bulk API 2.0によるSalesforce一括データクエリ
entry 11: ✓ 外部記事を追加 (投稿数ランキングのみ) - ノベルティを作るデザイナーのための、印刷まわりの話
entry 12: ✓ qiita記事を追加 - 【Moonlander】カスタマイズ性MAXな分割キーボードを布教したい
entry 13: ✓ qiita記事を追加 - 私のObsidian設定を紹介するよ!(プラグイン, Graph view, 同期など)
entry 14: ✓ 外部記事を追加 (投稿数ランキングのみ) - 勉強会に出ることで得られること
entry 15: ✓ qiita記事を追加 - 株価や為替を1時間おきにSlackへ自動で投稿する
entry 16: ✓ qiita記事を追加 - Azure OpenAIをゼロコストで健康チェック
entry 17: ✓ qiita記事を追加 - 1クリックで情報を整理!Gemini API × Obsidianで実現する自分だけのスクラップブック
entry 18: ✓ 外部記事を追加 (投稿数ランキングのみ) - SalesforceのAPI使用状況をSlackに通知する仕組みを作った話
entry 19: ✓ qiita記事を追加 - すぐはじめられる契約種別別Slackチャンネル・メンショングルー招待の自動化
entry 20: ✓ 外部記事を追加 (投稿数ランキングのみ) - DevinでJiraのチケットを起票する
entry 21: ✓ qiita記事を追加 - 【お礼 - Organization応援グッズ】会社のアドベントカレンダーを盛り上げるためにやったこと(準備編)
entry 22: ✓ qiita記事を追加 - コンセント配線で9万円を無駄にしかけた話
entry 23: ✓ qiita記事を追加 - すぐはじめられるSlackチャンネル・メンショングループ招待の自動化
entry 24: ✓ qiita記事を追加 - 1クリックで知識をストック!Gemini API × Obsidianで実現する自分だけの単語帳

=== 集計結果 ===
全entry数: 24
qiita記事: 17 (いいね数取得対象)
外部記事: 7 (投稿数ランキングのみ)
収集対象: 24
スキップ: 0
24件の記事を取得しました

いいね情報取得対象: 17件のqiita記事

--- 1/17 ---
いいね取得中: 86a2ada92c79fa26e4f2
  タイトル: [GitHub] 見てるPRがstagingマージ済か確認するブックマークレット
  筆者ID: waricoma
  URL: https://qiita.com/waricoma/items/86a2ada92c79fa26e4f2
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/86a2ada92c79fa26e4f2/likes?page=1)
    ページ 1: 0件取得
  合計いいね数: 0件 (0ページ)

--- 2/17 ---
いいね取得中: 87beaef1d484dd9aa4ba
  タイトル: SlackとGASで作るフィルタ/除外機能付きRSS/Feedリーダ(connpass用)
  筆者ID: waricoma
  URL: https://qiita.com/waricoma/items/87beaef1d484dd9aa4ba
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/87beaef1d484dd9aa4ba/likes?page=1)
    ページ 1: 0件取得
  合計いいね数: 0件 (0ページ)

--- 3/17 ---
いいね取得中: 97d4c95eb9ab2be8223a
  タイトル: [Slack] 任意のチャンネルから期間指定で会話エクスポート
  筆者ID: waricoma
  URL: https://qiita.com/waricoma/items/97d4c95eb9ab2be8223a
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/97d4c95eb9ab2be8223a/likes?page=1)
    ページ 1: 0件取得
  合計いいね数: 0件 (0ページ)

--- 4/17 ---
いいね取得中: 81a1e38448f97883793f
  タイトル: Confluence で 3か月以上更新されてないページを通知する
  筆者ID: tanker
  URL: https://qiita.com/tanker/items/81a1e38448f97883793f
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/81a1e38448f97883793f/likes?page=1)
    ページ 1: 3件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/81a1e38448f97883793f/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 3件 (1ページ)

--- 5/17 ---
いいね取得中: b3211b1ab0eaace06333
  タイトル: できる限り正確にAI文字起こしをする方法
  筆者ID: kei3524848
  URL: https://qiita.com/kei3524848/items/b3211b1ab0eaace06333
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/b3211b1ab0eaace06333/likes?page=1)
    ページ 1: 1件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/b3211b1ab0eaace06333/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 1件 (1ページ)

--- 6/17 ---
いいね取得中: e1d45f54819b3ee0b072
  タイトル: ボイパでAIとジャムセッションしてみたかったけどできなかった
  筆者ID: kei3524848
  URL: https://qiita.com/kei3524848/items/e1d45f54819b3ee0b072
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/e1d45f54819b3ee0b072/likes?page=1)
    ページ 1: 1件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/e1d45f54819b3ee0b072/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 1件 (1ページ)

--- 7/17 ---
いいね取得中: 806f7812f9e78e38cce9
  タイトル: gitコマンドでTODOアプリつくってみた
  筆者ID: rtkjm22
  URL: https://qiita.com/rtkjm22/items/806f7812f9e78e38cce9
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/806f7812f9e78e38cce9/likes?page=1)
    ページ 1: 3件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/806f7812f9e78e38cce9/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 3件 (1ページ)

--- 8/17 ---
いいね取得中: dee1f187c75cf87943bd
  タイトル: 【Moonlander】カスタマイズ性MAXな分割キーボードを布教したい
  筆者ID: kei3524848
  URL: https://qiita.com/kei3524848/items/dee1f187c75cf87943bd
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/dee1f187c75cf87943bd/likes?page=1)
    ページ 1: 2件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/dee1f187c75cf87943bd/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 2件 (1ページ)

--- 9/17 ---
いいね取得中: 5d686050922b1e85308e
  タイトル: 私のObsidian設定を紹介するよ!(プラグイン, Graph view, 同期など)
  筆者ID: kei3524848
  URL: https://qiita.com/kei3524848/items/5d686050922b1e85308e
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/5d686050922b1e85308e/likes?page=1)
    ページ 1: 3件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/5d686050922b1e85308e/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 3件 (1ページ)

--- 10/17 ---
いいね取得中: 81c9686427a140e03d0e
  タイトル: 株価や為替を1時間おきにSlackへ自動で投稿する
  筆者ID: waricoma
  URL: https://qiita.com/waricoma/items/81c9686427a140e03d0e
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/81c9686427a140e03d0e/likes?page=1)
    ページ 1: 4件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/81c9686427a140e03d0e/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 4件 (1ページ)

--- 11/17 ---
いいね取得中: 366e53ab32e65d072804
  タイトル: Azure OpenAIをゼロコストで健康チェック
  筆者ID: tanker
  URL: https://qiita.com/tanker/items/366e53ab32e65d072804
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/366e53ab32e65d072804/likes?page=1)
    ページ 1: 3件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/366e53ab32e65d072804/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 3件 (1ページ)

--- 12/17 ---
いいね取得中: adacf0ae479ec6b80969
  タイトル: 1クリックで情報を整理!Gemini API × Obsidianで実現する自分だけのスクラップブック
  筆者ID: kei3524848
  URL: https://qiita.com/kei3524848/items/adacf0ae479ec6b80969
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/adacf0ae479ec6b80969/likes?page=1)
    ページ 1: 3件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/adacf0ae479ec6b80969/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 3件 (1ページ)

--- 13/17 ---
いいね取得中: 6b9556ffa292805f300b
  タイトル: すぐはじめられる契約種別別Slackチャンネル・メンショングルー招待の自動化
  筆者ID: waricoma
  URL: https://qiita.com/waricoma/items/6b9556ffa292805f300b
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/6b9556ffa292805f300b/likes?page=1)
    ページ 1: 4件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/6b9556ffa292805f300b/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 4件 (1ページ)

--- 14/17 ---
いいね取得中: 2db881348c86f7257180
  タイトル: 【お礼 - Organization応援グッズ】会社のアドベントカレンダーを盛り上げるためにやったこと(準備編)
  筆者ID: waricoma
  URL: https://qiita.com/waricoma/items/2db881348c86f7257180
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/2db881348c86f7257180/likes?page=1)
    ページ 1: 3件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/2db881348c86f7257180/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 3件 (1ページ)

--- 15/17 ---
いいね取得中: 03d53fbbc1c5862e0140
  タイトル: コンセント配線で9万円を無駄にしかけた話
  筆者ID: waricoma
  URL: https://qiita.com/waricoma/items/03d53fbbc1c5862e0140
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/03d53fbbc1c5862e0140/likes?page=1)
    ページ 1: 20件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/03d53fbbc1c5862e0140/likes?page=2)
    ページ 2: 20件取得
  ページ 3 を取得中... (API URL: https://qiita.com/api/v2/items/03d53fbbc1c5862e0140/likes?page=3)
    ページ 3: 20件取得
  ページ 4 を取得中... (API URL: https://qiita.com/api/v2/items/03d53fbbc1c5862e0140/likes?page=4)
    ページ 4: 20件取得
  ページ 5 を取得中... (API URL: https://qiita.com/api/v2/items/03d53fbbc1c5862e0140/likes?page=5)
    ページ 5: 20件取得
  ページ 6 を取得中... (API URL: https://qiita.com/api/v2/items/03d53fbbc1c5862e0140/likes?page=6)
    ページ 6: 9件取得
  ページ 7 を取得中... (API URL: https://qiita.com/api/v2/items/03d53fbbc1c5862e0140/likes?page=7)
    ページ 7: 0件取得
  合計いいね数: 109件 (6ページ)

--- 16/17 ---
いいね取得中: e55b2b2dd9446068c0e2
  タイトル: すぐはじめられるSlackチャンネル・メンショングループ招待の自動化
  筆者ID: waricoma
  URL: https://qiita.com/waricoma/items/e55b2b2dd9446068c0e2
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/e55b2b2dd9446068c0e2/likes?page=1)
    ページ 1: 3件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/e55b2b2dd9446068c0e2/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 3件 (1ページ)

--- 17/17 ---
いいね取得中: 7592236f6212deed4a48
  タイトル: 1クリックで知識をストック!Gemini API × Obsidianで実現する自分だけの単語帳
  筆者ID: kei3524848
  URL: https://qiita.com/kei3524848/items/7592236f6212deed4a48
  ページ 1 を取得中... (API URL: https://qiita.com/api/v2/items/7592236f6212deed4a48/likes?page=1)
    ページ 1: 7件取得
  ページ 2 を取得中... (API URL: https://qiita.com/api/v2/items/7592236f6212deed4a48/likes?page=2)
    ページ 2: 0件取得
  合計いいね数: 7件 (1ページ)

外部記事 7件はいいね数取得をスキップしました
  1. AI Engineering Summit Tokyo 2025 参加&amp;登壇してきました by tomoris
     URL: https://tech.visasq.com/2025/12/23/143711
  2. UIデザイナーが家を建てる(建築はわからんけど動線設計はこだわりたい) by 17_3840
     URL: https://note.com/daiki_miyajima/n/n389153bed4c1?sub_rt=share_pb
  3. クライアントログイン情報フロー + Bulk API 2.0によるSalesforce一括データクエリ by knito
     URL: https://tech.visasq.com/salesforce-client-credentials-bulk-v2
  4. ノベルティを作るデザイナーのための、印刷まわりの話 by macomaru
     URL: https://note.com/maco/n/n44d602db7ba6
  5. 勉強会に出ることで得られること by gaplant_tr5
     URL: https://gaplanttr-5.hatenablog.com/entry/kichipm
  6. SalesforceのAPI使用状況をSlackに通知する仕組みを作った話 by mharada7704
     URL: https://tech.visasq.com/salesforce-api-usage-slack-notification
  7. DevinでJiraのチケットを起票する by gaplant_tr5
     URL: https://tech.visasq.com/devin_creates_jira_ticket

=== 集計結果 ===

=== 投稿数ランキング ===
1位: waricoma (8投稿)
2位: kei3524848 (6投稿)
3位: tanker (2投稿)
3位: gaplant_tr5 (2投稿)
5位: tomoris (1投稿)
5位: rtkjm22 (1投稿)
5位: 17_3840 (1投稿)
5位: knito (1投稿)
5位: macomaru (1投稿)
5位: mharada7704 (1投稿)

=== いいね数ランキング(記事がもらったいいね) ===
1位: waricoma (123いいね)
2位: kei3524848 (17いいね)
3位: tanker (6いいね)
4位: rtkjm22 (3いいね)
5位: tomoris (0いいね)
5位: 17_3840 (0いいね)
5位: knito (0いいね)
5位: macomaru (0いいね)
5位: gaplant_tr5 (0いいね)
5位: mharada7704 (0いいね)

=== いいねした数ランキング(カレンダー参加者のみ) ===
1位: rtkjm22 (10いいね)
2位: waricoma (9いいね)
3位: kei3524848 (8いいね)
4位: mharada7704 (1いいね)

=== 集計完了 ===

さいごに

改めて 株式会社ビザスク - VisasQ Inc. Advent Calendar 2025 について、ご参加・ご閲覧ありがとうございました🙏🙏🙏
嬉しいことに参加者が多く、手作業でやるには大変な集計なので自動化できて良かったです!!!💪
((来年もこのコードでサクッと集計できたらいいなと思いつつ、Qiita公式で集計機能が実装されたりしたらいいなと思ったりするなど…👀 (知らないだけである…?)))
多分ビザスク以外のカレンダーでも使える内容だと思うのでどなたかのご参考になれば幸いです!!!!!
ここまでご覧くださりありがとうございました 🙇🙇🙇🙇🙇 🙇🙇🙇🙇🙇
&年始に渋谷で公式のお疲れ様イベントあるみたいです🥳🥳🥳 良いお年を👋👋👋🎍🗻🦅🦅🍆🍆🍆

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?