2022年から人数上限なしのひとりアドベントカレンダー完走賞が新設されたことに伴い、2023年はひとりでアドベントカレンダーを走る人がそこそこ見受けられました。
ということでこれは、何名がひとりアドベントカレンダーに挑戦し、そのうちどれほどが完走したかを調べてみたという嫌がらせ記事になります。
なお調査のためのデータ抽出は2023/12/30に行っています。
調査結果
全アドベントカレンダー数、1203件。
うち、参加者がひとりのカレンダー、320件。
うち、完走数、98件。
うち、完走賞ぬいぐるみGET、78件・77名。
完走率は30.6%でした。
てっきり完走率10%くらいかと思っていたのですが、1/3も完走しているとはなかなかなかなか高い完走率ですね。
また最初から一人を目指したわけではなく、参加者を募集したけど最終的に一人しかいなかったという悲しきカレンダーも含まれているので、実質完走率はもう少し高くなるかもしれません。
ぬいぐるみはQiita内に記事を投稿していないといけないため、外部サイトに投稿しているひとはもらえません。
最終的にぬいぐるみをGETできる人は77名となりました。
ロジック
アドベントカレンダーを全て拾い出す → 走者が一人しかいないアドベントカレンダーを抽出する → 全日程にリンクがあれば完走、という単純なロジックになります。
しかしHTMLがぐちゃぐちゃで抽出に苦労しました。
なーにが最新ライブラリで綺麗なコードじゃ。
HTMLも綺麗にしろ。
プログラム自体は最後に載せておきます。
ぬいぐるみGETカレンダー一覧
以下の78カレンダー、77名です。
おめでとうございます。
・個人的なまとめのカレンダー by @0_terarin_0
・ここのえのカレンダー by @99no_exit
・aibo ers-11x/210で遊ぶのカレンダー by @nakmura
・自動運転AIチャレンジRacing大会(仮称)参加奮闘記のカレンダー by @Massy0127
・最新のRuby on Railsに一人で迫ってみる挑戦のカレンダー by @eichann
・AWS完全初心者によるアウトプットのカレンダー by @pandausa
・学びや気づきの備忘録のカレンダー by @Takaharu_01
・ぼっち論文サーベイメモのカレンダー by @matumu20
・ぐらだけのカレンダー by @c9h11o2
・「ChatGPTとPythonで学ぶ」シリーズのカレンダー by @maskot1977
・Python(Django) x Docker x AWSのカレンダー by @Ryo-0131
・コンポーネントごとに考えるアクセシビリティのカレンダー by @degudegu2510
・CoreBluetoothForUnityのカレンダー by @Teach
・○○作ってみない?のカレンダー by @nattyan_tv
・daily articleのカレンダー by @dev-satoshi
・PostgreSQL 入門のカレンダー by @dai_chi
・【完走賞めざす!】データベースとSQLのススメのカレンダー by @n_yamadamadamada
・日々の隙間で何かを学ぶのカレンダー by @eikidesu
・Elixirのカレンダー by @the_haigo
・Elixirのカレンダー by @Yoosuke
・ERC・EIPのカレンダー by @cardene
・firesignのカレンダー by @firesign2023
・はじめてのアドベントカレンダーのカレンダー by @kaedeee
・はじめてのアドベントカレンダーのカレンダー by @tech4anyone
・フロントエンド開発で役に立つTipsのカレンダー by @tanimoto-hikari
・1人フロントエンドのカレンダー by @KokiSakano
・がちもとさんのカレンダー by @SatoshiGachiFujimoto
・ゲーミフィケーションのカレンダー by @aoinakanishi
・@gi-ra-ffeのカレンダー by @gi-ra-ffe
・インフォマティカ・ジャパン株式会社のカレンダー by @masatoshimada-max
・海外の転職面接関連で完走を目指すのカレンダー by @KitaDaro
・JointJSのカレンダー by @acnaman
・軽深(かるしん なんちゃって圏論)のカレンダー by @zanjibar
・Keisuke Death Marchのカレンダー by @ke_sukesakuma
・k.k.Factoryのカレンダー by @koji0705
・Laravel とPHP Tipsのカレンダー by @sgrs38
・Go 言語を学ぶのカレンダー by @rapirapi
・Go 言語を学ぶのカレンダー by @shuyaeer
・shellgei160を通じて言語習得のカレンダー by @mechten
・Webアプリ構築カレンダーのカレンダー by @maaaashi
・TIPSのカレンダー by @lowking
・1年間の活動を振り返るのカレンダー by @does_not_exist
・【完走チャレンジ】2023年わたしが学んだことまとめのカレンダー by @Maruhoppe8
・めんどい太郎ののカレンダー by @mendoitarou_
・MicroPython/CircuitPythonのカレンダー by @inachi
・持ってる知識全般を棚卸するのカレンダー by @Killinneko
・milk-V Duoのカレンダー by @kazueda
・miriwoお一人様のカレンダー by @miriwo
・Oktaに関するアウトプットのカレンダー by @mtbRck
・ぽんぬの個人的のカレンダー by @MURAMASA2470
・なんでもアウトプットのカレンダー by @marurusan
・なりかくんのカレンダー by @narikakun
・生成AIのハードルを下げたい!のカレンダー by @Isaka-code
・negishi_tako完走したい!!のカレンダー by @Negishi_tako
・にわのわさんのカレンダー by @niwanowa
・俺の2023のカレンダー by @nomurasan
・備忘録のカレンダー by @nyanyacyan
・エンジニア歴3ヶ月のプログラミング初心者による初めての挑戦のカレンダー by @odendayoko
・ORANGE picoのカレンダー by @mikecat_mixc
・Microsoft Power BIのカレンダー by @akihiro_suto
・Python「Hello, world!」のカレンダー by @bucks
・一分で読める小ネタのカレンダー by @raki
・RuruCun個人開発のカレンダー by @RuruCun
・Qiita全国学生対抗戦のカレンダー by @SNQ-2001
・SuiSuiのカレンダー by @suipy
・Symfony Componentのカレンダー by @ippey_s
・初学者でも完走賞をとりたい!!のカレンダー by @skm_bnn
・土山竜輝のカレンダー by @tatsuki-tsuchiyama
・【学習備忘録】アウトプットの習慣化を目指したいのカレンダー by @TKY_study
・ここ2年くらいで学んだことをまとめて完走したいのカレンダー by @KitaDaro
・UEFN / Verseのカレンダー by @eisuke114
・完走賞のQiitanぬいぐるみをお迎えするためにUnityでゲーム作ってみるのカレンダー by @usomaru
・UWSCRのカレンダー by @stuncloud
・wslでnervesのカレンダー by @ohisama@github
・ここ最近のおもひでぽろぽろのカレンダー by @yaikawa227
・開発のお役立ち情報のカレンダー by @yam_dev
・今年の振り返りのカレンダー by @youfuku
・完走賞ゲットのため小ネタ 25記事を投稿しようとチャレンジ v2のカレンダー by @youtoy
感想
私はいつものペースなので、最初から参加する気はありませんでした。
毎日記事を書くなんて面倒臭い。
でも実際、私が面倒臭いと思うようなことを情熱を持ってやり遂げる人たちがこれほど存在するというわけで、彼らのような人たちがいるかぎり、この業界もきっと安泰にちがいありませんね。
彼らのこれからの活躍にも期待しましょう。
あと手元には三日坊主で完走できなかった人、それどころか登録したけど1件すら投稿のない人のリストなんかもあったりするわけですが、まあ流石に正月一発目から下品なことをするのもあれなのでやめておくことにします。
プログラム
一回動けばそれでいい、の精神で作られているので適当です。
まずアドベントカレンダーをぜんぶダウンロードしてローカルに保存する。
(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);
}
}
}
ローカルファイルを読み込んで分析する。
(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に負荷をかけるのを避けるためです。