【Qiita】ひとりアドベントカレンダー完走率を調査してみたよ

2022年から人数上限なしの[ひとりアドベントカレンダー完走賞](https://qiita.com/advent-calendar/2023/present-calendar#qiita_award)が新設されたことに伴い、2023年はひとりで[アドベントカレンダー](https://qiita.com/advent-calendar/2023/categories)を走る人がそこそこ見受けられました。

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/28bbf8e6-6951-8fe3-bbe5-8ceaf8569ada.png)


ということでこれは、何名がひとりアドベントカレンダーに挑戦し、そのうちどれほどが完走したかを調べてみたという嫌がらせ記事になります。

なお調査のためのデータ抽出は2023/12/30に行っています。


# 調査結果

全アドベントカレンダー数、1203件。

うち、参加者がひとりのカレンダー、320件。
うち、完走数、98件。
うち、完走賞ぬいぐるみGET、78件・77名。

完走率は30.6%でした。

てっきり完走率10%くらいかと思っていたのですが、1/3も完走しているとはなかなかなかなか高い完走率ですね。

また最初から一人を目指したわけではなく、参加者を募集したけど最終的に一人しかいなかったという悲しきカレンダーも含まれているので、実質完走率はもう少し高くなるかもしれません。

ぬいぐるみはQiita内に記事を投稿していないといけないため、外部サイトに投稿しているひとはもらえません。
最終的にぬいぐるみをGETできる人は77名となりました。


# ロジック

[アドベントカレンダー](https://qiita.com/advent-calendar/2023/categories)を全て拾い出す → 走者が一人しかいないアドベントカレンダーを抽出する → 全日程にリンクがあれば完走、という単純なロジックになります。
しかしHTMLがぐちゃぐちゃで抽出に苦労しました。
なーにが最新ライブラリで綺麗なコードじゃ。
HTMLも綺麗にしろ。

プログラム自体は最後に載せておきます。


# ぬいぐるみGETカレンダー一覧

以下の78カレンダー、77名です。
おめでとうございます。

・[個人的なまとめのカレンダー](https://qiita.com/advent-calendar/2023/0_terarin_0-individualrun-23) by @0_terarin_0
・[ここのえのカレンダー](https://qiita.com/advent-calendar/2023/99no_exit) by @99no_exit
・[aibo ers-11x/210で遊ぶのカレンダー](https://qiita.com/advent-calendar/2023/aibo-makaizo) by @nakmura
・[自動運転AIチャレンジRacing大会(仮称)参加奮闘記のカレンダー](https://qiita.com/advent-calendar/2023/aichallenge_racing_for_mas) by @Massy0127
・[最新のRuby on Railsに一人で迫ってみる挑戦のカレンダー](https://qiita.com/advent-calendar/2023/alone-rails) by @eichann
・[AWS完全初心者によるアウトプットのカレンダー](https://qiita.com/advent-calendar/2023/aws-beginner-output) by @pandausa
・[学びや気づきの備忘録のカレンダー](https://qiita.com/advent-calendar/2023/bibouroku-kansou) by @Takaharu_01
・[ぼっち論文サーベイメモのカレンダー](https://qiita.com/advent-calendar/2023/bocchi_papersurveymemo_2023) by @matumu20
・[ぐらだけのカレンダー](https://qiita.com/advent-calendar/2023/c9h11o2) by @c9h11o2
・[「ChatGPTとPythonで学ぶ」シリーズのカレンダー](https://qiita.com/advent-calendar/2023/chatgpt_to_python_de) by @maskot1977
・[Python(Django) x Docker x AWSのカレンダー](https://qiita.com/advent-calendar/2023/citrus) by @Ryo-0131
・[コンポーネントごとに考えるアクセシビリティのカレンダー](https://qiita.com/advent-calendar/2023/component-accessibility) by @degudegu2510
・[CoreBluetoothForUnityのカレンダー](https://qiita.com/advent-calendar/2023/core-bluetooth-for-unity) by @Teach
・[○○作ってみない？のカレンダー](https://qiita.com/advent-calendar/2023/create-any-nattyantv) by @nattyan_tv
・[daily articleのカレンダー](https://qiita.com/advent-calendar/2023/daily-article) by @dev-satoshi
・[PostgreSQL 入門のカレンダー](https://qiita.com/advent-calendar/2023/db-engineer-postgresql) by @dai_chi
・[【完走賞めざす！】データベースとSQLのススメのカレンダー](https://qiita.com/advent-calendar/2023/db_sql_practice) by @n_yamadamadamada
・[日々の隙間で何かを学ぶのカレンダー](https://qiita.com/advent-calendar/2023/eikiyakisoba) by @eikidesu
・[Elixirのカレンダー](https://qiita.com/advent-calendar/2023/elixir) by @the_haigo
・[Elixirのカレンダー](https://qiita.com/advent-calendar/2023/elixir) by @Yoosuke
・[ERC・EIPのカレンダー](https://qiita.com/advent-calendar/2023/erc-eip) by @cardene
・[firesignのカレンダー](https://qiita.com/advent-calendar/2023/firesign_adventcalendar) by @firesign2023
・[はじめてのアドベントカレンダーのカレンダー](https://qiita.com/advent-calendar/2023/first-time) by @kaedeee
・[はじめてのアドベントカレンダーのカレンダー](https://qiita.com/advent-calendar/2023/first-time) by @tech4anyone
・[フロントエンド開発で役に立つTipsのカレンダー](https://qiita.com/advent-calendar/2023/frontend-engineer_tips) by @tanimoto-hikari
・[1人フロントエンドのカレンダー](https://qiita.com/advent-calendar/2023/frontend) by @KokiSakano
・[がちもとさんのカレンダー](https://qiita.com/advent-calendar/2023/gachimoto3) by @SatoshiGachiFujimoto
・[ゲーミフィケーションのカレンダー](https://qiita.com/advent-calendar/2023/gamification) by @aoinakanishi
・[@gi-ra-ffeのカレンダー](https://qiita.com/advent-calendar/2023/gi-ra-ffe) by @gi-ra-ffe
・[インフォマティカ・ジャパン株式会社のカレンダー](https://qiita.com/advent-calendar/2023/informatica) by @masatoshimada-max
・[海外の転職面接関連で完走を目指すのカレンダー](https://qiita.com/advent-calendar/2023/job-interview) by @KitaDaro
・[JointJSのカレンダー](https://qiita.com/advent-calendar/2023/jointjs) by @acnaman
・[軽深（かるしん　なんちゃって圏論）のカレンダー](https://qiita.com/advent-calendar/2023/karushin) by @zanjibar
・[Keisuke Death Marchのカレンダー](https://qiita.com/advent-calendar/2023/keisukedeathmarch) by @ke_sukesakuma
・[k.k.Factoryのカレンダー](https://qiita.com/advent-calendar/2023/kkfactory) by @koji0705
・[Laravel とPHP Tipsのカレンダー](https://qiita.com/advent-calendar/2023/laravel-tips) by @sgrs38
・[Go 言語を学ぶのカレンダー](https://qiita.com/advent-calendar/2023/learn-go) by @rapirapi
・[Go 言語を学ぶのカレンダー](https://qiita.com/advent-calendar/2023/learn-go) by @shuyaeer
・[shellgei160を通じて言語習得のカレンダー](https://qiita.com/advent-calendar/2023/learning-via-shellgei160) by @mechten
・[Webアプリ構築カレンダーのカレンダー](https://qiita.com/advent-calendar/2023/life-sync-app) by @maaaashi
・[TIPSのカレンダー](https://qiita.com/advent-calendar/2023/lowking-tips) by @lowking
・[1年間の活動を振り返るのカレンダー](https://qiita.com/advent-calendar/2023/make_test) by @does_not_exist
・[【完走チャレンジ】2023年わたしが学んだことまとめのカレンダー](https://qiita.com/advent-calendar/2023/maruhoppe2023) by @Maruhoppe8
・[めんどい太郎ののカレンダー](https://qiita.com/advent-calendar/2023/mendoitarou_first_calendar) by @mendoitarou_
・[MicroPython/CircuitPythonのカレンダー](https://qiita.com/advent-calendar/2023/micro-circuit-python) by @inachi
・[持ってる知識全般を棚卸するのカレンダー](https://qiita.com/advent-calendar/2023/mikaptosmianpropranstelon) by @Killinneko
・[milk-V Duoのカレンダー](https://qiita.com/advent-calendar/2023/milkvduo) by @kazueda
・[miriwoお一人様のカレンダー](https://qiita.com/advent-calendar/2023/miriwo) by @miriwo
・[Oktaに関するアウトプットのカレンダー](https://qiita.com/advent-calendar/2023/mtbrck) by @mtbRck
・[ぽんぬの個人的のカレンダー](https://qiita.com/advent-calendar/2023/muramasa2470) by @MURAMASA2470
・[なんでもアウトプットのカレンダー](https://qiita.com/advent-calendar/2023/nandemo-output) by @marurusan
・[なりかくんのカレンダー](https://qiita.com/advent-calendar/2023/narikakun) by @narikakun
・[生成AIのハードルを下げたい！のカレンダー](https://qiita.com/advent-calendar/2023/nebosuke-ai) by @Isaka-code
・[negishi_tako完走したい!!のカレンダー](https://qiita.com/advent-calendar/2023/negishi_tako) by @Negishi_tako
・[にわのわさんのカレンダー](https://qiita.com/advent-calendar/2023/niwanowa) by @niwanowa
・[俺の2023のカレンダー](https://qiita.com/advent-calendar/2023/nomuraya_oreno) by @nomurasan
・[備忘録のカレンダー](https://qiita.com/advent-calendar/2023/nyanyacyanbibouroku) by @nyanyacyan
・[エンジニア歴3ヶ月のプログラミング初心者による初めての挑戦のカレンダー](https://qiita.com/advent-calendar/2023/odendayoko) by @odendayoko
・[ORANGE picoのカレンダー](https://qiita.com/advent-calendar/2023/orange_pico) by @mikecat_mixc
・[Microsoft Power BIのカレンダー](https://qiita.com/advent-calendar/2023/powerbi) by @akihiro_suto
・[Python「Hello, world!」のカレンダー](https://qiita.com/advent-calendar/2023/python-hello-world) by @bucks
・[一分で読める小ネタのカレンダー](https://qiita.com/advent-calendar/2023/raki) by @raki
・[RuruCun個人開発のカレンダー](https://qiita.com/advent-calendar/2023/rurucun) by @RuruCun
・[Qiita全国学生対抗戦のカレンダー](https://qiita.com/advent-calendar/2023/student) by @SNQ-2001
・[SuiSuiのカレンダー](https://qiita.com/advent-calendar/2023/suipy) by @suipy
・[Symfony Componentのカレンダー](https://qiita.com/advent-calendar/2023/symfony-component) by @ippey_s
・[初学者でも完走賞をとりたい！！のカレンダー](https://qiita.com/advent-calendar/2023/syogakusya-demo-kansousyo) by @skm_bnn
・[土山竜輝のカレンダー](https://qiita.com/advent-calendar/2023/tatsuki-tsuchiyama) by @tatsuki-tsuchiyama
・[【学習備忘録】アウトプットの習慣化を目指したいのカレンダー](https://qiita.com/advent-calendar/2023/tky_study) by @TKY_study
・[ここ2年くらいで学んだことをまとめて完走したいのカレンダー](https://qiita.com/advent-calendar/2023/two-years) by @KitaDaro
・[UEFN / Verseのカレンダー](https://qiita.com/advent-calendar/2023/uefn-daisuki) by @eisuke114
・[完走賞のQiitanぬいぐるみをお迎えするためにUnityでゲーム作ってみるのカレンダー](https://qiita.com/advent-calendar/2023/usomaru) by @usomaru
・[UWSCRのカレンダー](https://qiita.com/advent-calendar/2023/uwscr) by @stuncloud
・[wslでnervesのカレンダー](https://qiita.com/advent-calendar/2023/wsldenerves) by @ohisama@github
・[ここ最近のおもひでぽろぽろのカレンダー](https://qiita.com/advent-calendar/2023/yaikawa_memory_2023) by @yaikawa227
・[開発のお役立ち情報のカレンダー](https://qiita.com/advent-calendar/2023/yam_dev) by @yam_dev
・[今年の振り返りのカレンダー](https://qiita.com/advent-calendar/2023/youfuku) by @youfuku
・[完走賞ゲットのため小ネタ 25記事を投稿しようとチャレンジ v2のカレンダー](https://qiita.com/advent-calendar/2023/youtoy) by @youtoy



# 感想

私は[いつものペース](https://qiita.com/rana_kualu/items/02520a80e0d53fc2df8f#%E5%85%AC%E9%96%8B)[なので](https://qiita.com/rana_kualu)、最初から参加する気はありませんでした。
毎日記事を書くなんて面倒臭い。

でも実際、私が面倒臭いと思うようなことを情熱を持ってやり遂げる人たちがこれほど存在するというわけで、彼らのような人たちがいるかぎり、この業界もきっと安泰にちがいありませんね。
彼らのこれからの活躍にも期待しましょう。

あと手元には三日坊主で完走できなかった人、それどころか登録したけど1件すら投稿のない人のリストなんかもあったりするわけですが、まあ流石に正月一発目から下品なことをするのもあれなのでやめておくことにします。


# プログラム

一回動けばそれでいい、の精神で作られているので適当です。


まずアドベントカレンダーをぜんぶダウンロードしてローカルに保存する。

```php
(new A())->run();

class A
{
    public function run()
    {
        // 新着カレンダーリストを全取得
        $this->getCalendarsList();

        // 新着カレンダーリストから個々のカレンダーを取得
        $files = glob('./list/*');
        foreach ($files as $file) {
            $this->getCalendar($file);
        }
    }

    /**
     * 新着カレンダーリストを取得してローカル保存する
     */
    public function getCalendarsList()
    {
        /*
            1-47ページまで取得
            カレンダー数はわかっているので手抜き。
        */
        $urlp = 'https://qiita.com/advent-calendar/2023/calendars?page=';
        $page = 0;
        while ($page++ < 47) {
            $url = $urlp . $page;
            $html = file_get_contents($url);
            file_put_contents('./list/page' . $page, $html);
        }
    }

    /**
     * 新着カレンダーリストから個々のカレンダーを取得してローカル保存する
     * @param string $file ファイル名
     */
    public function getCalendar(string $file)
    {
        $html = file_get_contents($file);

        // カレンダーへのリンクを抽出
        $pattern = '|<div class="style-1dkfpx"><div class="style-ltzqlx"><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);
        }
    }

}
```

ローカルファイルを読み込んで分析する。

```php
(new A())->run();

class A
{
    public function run()
    {
        $output = [];
        // ファイル
        $files = glob('./calendar/*');

        // カレンダーごとに中身を分析
        foreach ($files as $file) {
            $html = file_get_contents($file);
            $url = substr($file, 11);
            $output[] = $this->analyze($url, $html);
        }
        // 終了
        file_put_contents(
            './analyse.json',
            json_encode($output)
        );
    }

    /**
     * HTMLを調べる
     * @param string $url  URL
     * @param string $html HTML
     */
    public function analyze($url, $html)
    {
        // タイトル
        $pattern = '|<title>(.*?) \| Advent Calendar 2023|us';
        preg_match($pattern, $html, $matches1);
        $title = $matches1[1];

        // シリーズに分割
        $pattern = '|<h3 class="style-r7hjq4">(.*?)style-1ey0ayb|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/2023/' . basename($url);
            $ret[] = $tmp;
        }
        return $ret;
    }

    /**
     * シリーズごとに判定
     * @param  string $calendar カレンダーのHTML
     * @return array  [user, complete, qiita]
     */
    private function check($calendar)
    {
        $ret = [
            'user' => null,
            'complete' => true, // 完走
            'qiita' => true,    // Qiitaか
            'count' => 0,
        ];

        // 記事ごとに分割
        $pattern = '|<div class="style-1ssbn0c">(.*?)<div class="st-Modal">|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['user'] = $ret['complete'] = $ret['qiita'] = false;
                    return $ret;
                }
            }

            // 記事リンク
            $pattern = '|<div class="style-1dctyxx"><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;
    }
}
```

順に実行すると分析結果が`analyse.json`に保存されるので、後はそれを適当に集計したりするだけです。
なおダウンロードが分かれているのは、分析するたびにダウンロードを実行してQiitaに負荷をかけるのを避けるためです。