Qiitaアドベントカレンダーには、ひとりで25記事以上を投稿すると賞品をもらえる完走賞があります。
中でも一人でひとつのアドベントカレンダーを埋め尽くす、ひとりアドベントカレンダーを走っている人が毎年散見されます。
ということで、これは何名がひとりアドベントカレンダーに挑戦し、そのうちどれほどが完走したかを調べてみたという嫌がらせ記事になります。
調査のためのデータ抽出を行ったのは2024/12/30です。
調査結果
全アドベントカレンダー数、1314件。
うち、参加者がひとりのカレンダー、402件。
うち、完走数、122件。
うち、完走賞ぬいぐるみGET、98件・98名。
完走率は30.2%でした。
昨年の30.6%とほぼ同率です。
しかし完走者数は98名から122名へ、ぬいぐるみゲット者も77名から98名へと増加しています。
前回より参加者が増えているということですばらしいことですね。
ぬいぐるみはQiita内に記事を投稿していないといけないため、外部サイトに投稿しているひとはもらえません。
最終的にぬいぐるみをGETできる人は98名となりました。
ロジック
アドベントカレンダーを全て拾い出す → 走者が一人しかいないアドベントカレンダーを抽出する → 全日程にリンクがあれば完走、という単純なロジックです。
ただソースコードがとっても酷いので抽出が面倒でした。
はっきりいってHTMLがぐちゃぐちゃです。
なーにが最新ライブラリで綺麗なコードじゃ。
HTMLも綺麗にしろ。
プログラム自体は最後に載せておきます。
ぬいぐるみGETカレンダー一覧
以下の98カレンダー、98名です。
・ひとりアドカレ@yshimizu by @yshimizu
・SVG by @Tadataka_Takahashi
・はな丸's QA by @EgaSaQA
・protobuf.devのAPI Best Practicesを読んでいく全部俺 by @bushiyama
・Sequence Maker by @sequencemaker
・ひとりJavaScript by @nanasi-1
・適応型ソフトウェア開発 by @h-kinocoboy
・sphero boltで遊ぶ by @nakmura
・コンピューターシステム株式会社 by @nimzo6689
・manabian by @manabian
・ひとりアドベントカレンダー WSLで学ぶLINUXコマンド by @yu1016
・ Hamster Output by @game_hamster
・競プロ用ライブラリを作る by @Yukkku
・新人ウェブエンジニア向けセキュリティー教育 by @mamono210
・ひとりアドベントカレンダー 初めてのチャレンジ編 by @Mymt_aggw2208
・【完走チャレンジ】2024年わたしが学んだことまとめ by @Maruhoppe8
・科学と神々株式会社 by @jasagiri
・claustra01's Daily CTF by @claustra01
・完走カレンダー by @chi1180
・kazuedaの活動記録2024 by @kazueda
・X のアルゴリズム・ルール・専門用語について by @haihaikazuma
・えーえす by @AS_atsushi
・Gakken LEAP by @GleapPost
・ex-crowdworks by @nisyuu
・RCC (立命館コンピュータークラブ) by @bearl27
・RCC (立命館コンピュータークラブ) by @JavaLangRuntimeException
・RCC (立命館コンピュータークラブ) by @anapausis
・RCC (立命館コンピュータークラブ) by @maooz4426
・ひとりアドベントカレンダーチャレンジ by @hikagami
・ひとりアドカレ_私の本棚から、「思考法」や「プロジェクト管理系」 by @AkmIOtUE4
・Cloud Run functions × TypeScript 開発をしよう! by @KyongminS
・使っているクラウドなどの小ネタ by @Keiji_otsubo
・Java by @_mi
・Yamashita by @yam_dev
・TypeScriptで学べるデザインパターン by @hato_code
・【ひとりカレンダー】Clojure by @maaaashi
・keitamax by @keitaMax
・LINEミニアプリ by @non_watanabe
・ひとりアドベントカレンダー by @tamutamuta
・脱After Effects by @potistudio
・フロントエンドの世界 by @nuinteedev
・一歩ずつRustに慣れていくTypeScriptエンジニアの記録 by @k4nd4
・Rickyアドカレ2024 by @akiraarika932
・1人フロントエンド by @KokiSakano
・FoundingBase by @mziyut
・米田の補題 by @zanjibar
・インフォマティカ・ジャパン株式会社 by @shunsuzu
・JavaとPythonを基礎から学びたい私のための by @Aruhimanahito
・一年を振り返る by @kwtc_
・🦀ひとりRustとBevyでゲーム開発🕊️ by @hiruberuto
・kaggle入門 by @leafeon00000
・一人アドベントカレンダー!主にPythonに関して投稿します。 by @shun_sakamoto
・俺の2024年 by @nomurasan
・個人的アウトプットカレンダー by @kenny-m
・技術書一冊やり込もう by @tatataichi
・なんかかく by @e99h2121
・ひとりアドベントカレンダー by Mas by @Massy0127
・ぼっちアドベントカレンダー by bon10 by @bon10
・真のエンジニアを目指して by @devmatsuko
・Goで学ぶコンピューターサイエンス by @engineer_tacky
・Python(Django) x AWS 一人アドベントカレンダー by @Ryo-0131
・セキュリティごった煮一人完走チャレンジ by @sigma_devsecops
・Machiken’s by @ken-1200
・ひとりアドベントカレンダー by @ei_540
・転機となった一年の出来事を振り返る by @does_not_exist
・Tauriでエンジンからゲームを作ってみる by @nattyan_tv
・Delphi by @ht_deko
・ひとりアドカレ2024 byもんすん by @phibi-soon
・nem / symbol by @ccHarvestasya
・今年作った勉強動画🕊!@飛鷹しずか by @ShizukaHidaka
・ただただアウトプットを癖付けるための by @yotabouya
・ZOZO by @shiozaki
・k.k.Factory by @koji0705
・Power Query へそのゴマ by @spumoni
・Elixir by @Yoosuke
・ひとりアドベント(グラフ電卓,関数電卓,Python) by @harycurl
・kaggle初心者に送る by @v_one5
・Basic Study Log by @mattsu_mocha
・アウトプットアドカレ2024 by @ysit
・文章題で身に付くプログラミング思考入門️ by TheWaggle by @yuki_thewaggle
・まっさらな新人研修コーチ by @uekkie
・Webフロントエンド by @axoloto210
・一人ゲーム開発Tips by @4_mio_11
・競技プログラミングなんでも by @comet725
・技術記事未経験で個人完走を目指す by @thule
・ilasm by @ohisama@github
・My Infrastructure Journey by @future_kame
・Qiitanがほしい人の一人アドカレ by @nikawamikan
・個人的アドカレ by @enumura1
・Rails学習者向けのLaravel by @chdkm
・Java Silver確実合格を目指すカレンダー by @pandausa
・ひとりで完走_C# is GOD by @C-Sharp_is_GOD
・API Lab for LINE WORKS by @iwaohig
・一人アドカレ by @kk0128
・kurogoma939のひとりアドベントカレンダー by @kurogoma939
・個人的なまとめ by @0_terarin_0
・midPoint by OpenStandia by @wadahiro
・ぼっち論文サーベイメモ2  by @matumu20
ただし『最終的な達成者は、コミュニティガイドラインに則していない記事か運営が内容確認・審査をした上で選定いたします』という条文があるので、中身のないユーザは弾かれる可能性があります。
また、正確なぬいぐるみゲット条件は『Qiita記事に1人で25記事投稿しきった方』なので、複数のアドベントカレンダーで合わせて25記事投稿した場合も対象となります。
その対象者は、このリストには含まれていません。
全リストはあるので単純に合計すれば簡単に出せるのですが、面倒なのでまあいいや。
感想
完走者のみなさんおめでとうございます。
約一ヶ月毎日投稿し続けるという、相当な情熱がないとやりきれないことをやってのけた人がこれだけいるということは驚きですね。
彼らはきっとこれからも活躍してくれることでしょう、期待しましょう。
さて手元には三日坊主の人のリストとか、アドカレを作るだけ作ったのに1件すらも投稿がない人のリストなんかもあったりするわけですが、はてさてどうしたものかな。
プログラム
一回動けばそれでいいの精神で作られています。
まずアドベントカレンダーをぜんぶダウンロードしてローカルに保存する。
(new A())->run();
class A
{
    public function run()
    {
        // 新着カレンダーを全取得
        $this->getCalendarsList();
        // 新着カレンダーを順に処理
        $files = glob('./list/*');
        foreach ($files as $file) {
            $this->getCalendar($file);
        }
    }
    /**
     * 新着カレンダーページから個々のカレンダーを取得してローカル保存する
     * @param string $file ファイル名
     */
    public function getCalendar(string $file)
    {
        $html = file_get_contents($file);
        // カレンダーへのリンクを取得
        $pattern = '|<li class="style-1a7kcgn"><div class="style-11zreon"><div><div class="style-1xn5a1f"><a href="(.*?)" class|us';
        preg_match_all($pattern, $html, $matches1);
        foreach ($matches1[1] as $tmp) {
            // ページを取得
            $url = 'https://qiita.com' . $tmp;
            $html = file_get_contents($url);
            // ローカルに保存
            file_put_contents('./calendar/' . basename($tmp) . '.html', $html);
            sleep(1);
        }
    }
    /**
     * 新着カレンダーを全取得してローカル保存する
     */
    public function getCalendarsList()
    {
        /*
            1-52ページまで取得
            ページ数はわかっているので手抜き。
        */
        $urlp = 'https://qiita.com/advent-calendar/2024/calendars?page=';
        $page = 0;
        while ($page++ < 52) {
            // ページを取得
            $url = $urlp . $page;
            $html = file_get_contents($url);
            // ローカルに保存
            file_put_contents('./list/page' . $page . '.html', $html);
        }
    }
}
ローカルファイルを読み込んで分析する。
(new A())->run();
class A
{
    public function run()
    {
        $output = [];
        // ファイル
        $files = glob('./calendar/*');
        // カレンダーごとに中身を分析
        foreach ($files as $file) {
            echo 'Analyzing ' . $file . "\n";
            $html = file_get_contents($file);
            $url = substr($file, 11);
            $output[] = $this->analyze($url, $html);
        }
        // 終了
        file_put_contents(
            './analyse.json',
            json_encode(
                $output,
                \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE
            )
        );
    }
    /**
     * HTMLを調べる
     * @param string $url  URL
     * @param string $html HTML
     */
    public function analyze($url, $html)
    {
        $ret = [];
        // タイトル
        $pattern = '|<title>(.*?) - Qiita Advent Calendar 2024|us';
        preg_match($pattern, $html, $matches1);
        $title = $matches1[1];
        // 大ざっぱに切り取り
        $pattern = '|<section class="style-1covvrn">(.*)<section class="style-196d0fg"><div class="style-1i27l4i">|us';
        preg_match($pattern, $html, $matches1);
        $html = $matches1[1];
        // シリーズに分割
        $pattern = '|<section class="style-t7g594">(.*?)</div></div></dialog></div></li></ol></div></section>|us';
        preg_match_all($pattern, $html, $matches);
        // シリーズごとにループ
        foreach ($matches[1] as $k => $calendar) {
            // 判定
            $tmp = $this->check($calendar);
            $tmp['title'] = $title;
            $tmp['series'] = $k + 1;
            $tmp['url'] = 'https://qiita.com/advent-calendar/2024/' . basename($url, '.html');
            $ret[] = $tmp;
        }
        return $ret;
    }
    /**
     * シリーズごとに判定
     * @param  string $calendar カレンダーのHTML
     * @return array  [user, complete, qiita, count]
     */
    private function check($calendar)
    {
        $ret = [
            'user' => null,
            'complete' => true, // 完走
            'qiita' => true,    // Qiitaか
            'count' => 0,
        ];
        // 記事ごとに分割
        $pattern = '|<td class="style-1dw8kp9"><div class="style-1ssbn0c">(.*?)aria-labelledby="OtherCalendarInfo-label"|us';
        preg_match_all($pattern, $calendar, $posts);
        // 記事でループ
        foreach ($posts[1] as $post) {
            // ユーザ名
            $pattern = '|@<!-- -->(.*?)</a>|us';
            preg_match($pattern, $post, $matches);
            
            if (isset($matches[1])) {
                if ($ret['user'] === null) {
                    $ret['user'] = $matches[1];
                } elseif ($ret['user'] !== $matches[1]) {
                    $ret['complete'] = $ret['qiita'] = false;
                    return $ret;
                }
            }
            // 記事リンク
            $pattern = '|<div class="style-mpez5z"><a href="(.*?)"|us';
            preg_match($pattern, $post, $matches);
            if (!isset($matches[1])) {
                $ret['complete'] = $ret['qiita'] = false;
                return $ret;
            }
            // Qiita内か
            if (!str_starts_with($matches[1], 'https://qiita.com')) {
                $ret['qiita'] = false;
            }
            ++$ret['count'];
        }
        // 最後まで進んだ
        if ($ret['count'] < 25) {
            // 登録が足りない
            $ret['complete'] = $ret['qiita'] = false;
            return $ret;
        }
        return $ret;
    }
}
このクラス名とかさあ、ちゃんと意味のある名前にしてくれんかね。
ほんと開発者には優しいのかもしれないが利用者には優しくないフレームワークばっかりで困る。
