Qiita Advent Calendar 2021 に登録された記事は Qiita公開 と Zenn公開 のどちらが多かったか？【Swift でスクレイピング】

:::note info
記事初出時点では2021年12月24日までの情報でしたが、2021年12月25日までの情報を反映させました。
:::

[![README_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/794c36c8-c229-6657-17de-ad57a1ea369e.png)](https://github.com/treastrain/QACPercentageSurvey)

> **アドベントカレンダーにはどのような記事を紐付けていいのですか？**
> Qiita Advent CalendarにはQiitaはもちろんZennやnote、はてなブログ、個人ブログなど、エンジニアに関わる内容であればどのサイトの記事でも歓迎です。
>
> Qiita Advent Calendar について | Qiita Support https://help.qiita.com/ja/articles/qiita-adcal-1#faq

https://twitter.com/zenn_dev/status/1455039650892967938

さて、今年の Qiita Advent Calendar の記事の公開先は「Qiita で公開」と「Zenn で公開」のどちらが多かったのでしょうか？

# 実装方法など
本記事をお読みの方は何よりも結果を先に知りたいかと思いますので、このために作成したプログラムなどについては記事末尾にまとめて記載します。キーワードだけ列挙しておきます。

Swift, Swift Concurrency, SwiftUI, Combine, Xcode 13.2, Swift Playgrounds 4
iOS App, iPadOS App, macOS Command Line Tool, Swift Package, Kanna

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/8b2c7403-bc68-68d4-3c90-88536846216d.png" width=16%>　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/6dab45a9-534a-9aae-d381-18f5a48ae911.png" width=16%>　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/bb863c22-eb7a-0a0a-f0a2-afb785ce8f7f.jpeg" width=50%>


# 注意点
後述しますが、Qiita Advent Calendar 2021 のカレンダー数は約 700 であり、それらすべてにアクセスしてスクレイピングを行いました。ソフトウェアを開発・実行するときは以下に注意して行いました。

- User-Agent を設定し、その中に自分への連絡先情報を含める
- 実行にあたって Qiita API は使用していないが、[Qiita API v2ドキュメント にある「利用制限」](https://qiita.com/api/v2/docs)を参考に、リクエストの間隔は最低60秒以上空けるようにする

> 利用制限
> 認証している状態ではユーザごとに1時間に1000回まで、認証していない状態ではIPアドレスごとに1時間に60回までリクエストを受け付けます。
>
> Qiita API v2ドキュメント - Qiita:Developer https://qiita.com/api/v2/docs

そのため、すべてのデータの取得タイミングに60秒の間隔がありますので、タイミングによっては以下に示した結果が現在の状況を示していない場合があります。
~~（実行期間: 2021年12月24日10時33分ごろ 〜 2021年12月24日22時58分ごろ　　つまり2021年12月25日公開の記事が含まれていません）~~
（2021年12月25日までの情報を反映させました　　実行期間: 2021年12月26日0時33分ごろ 〜 2021年12月26日14時47分ごろ）

# 結果（全体）
![全体.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/93e6d1f3-e986-0b14-c053-3407dd1c4761.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|7760|60.3%|
|zenn.dev|888|6.9%|
|note.com|720|5.6%|
|medium.com|129|1.0%|

**Qiita公開 のほうが多い**

# Qiita で公開のほうが多かった
というわけで、今年の Qiita Advent Calendar は、Qiita で公開された記事数が Zenn で公開された記事数の約8.9倍という結果でした。
…と、ここで思い浮かぶのは、「カレンダーカテゴリー別だと違う結果になるのではないか？」という予想です。確認してみることにしましょう。

# 結果（カレンダーカテゴリー別）
## [プレゼント](https://qiita.com/advent-calendar/2021/categories/sponsor)
![プレゼント.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/ed64aa4c-8d24-fc21-7cab-859fdff76ba9.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|241|85.2%|
|zenn.dev|7|2.5%|
|note.com|5|1.8%|

**Qiita公開 のほうが多い**

## [Qiita主催](https://qiita.com/advent-calendar/2021/categories/qiita_official)
![Qiita主催.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/c7db480d-66b7-bf93-ab9c-275d45d1c824.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|97|82.2%|
|zenn.dev|2|1.7%|

**Qiita公開 のほうが多い**

## [プログラミング言語](https://qiita.com/advent-calendar/2021/categories/programming_languages)
![プログラミング言語.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/9c99a9b2-2bae-082f-627d-3ffafa82597a.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|714|57.5%|
|zenn.dev|151|12.9%|

**Qiita公開 のほうが多い**

## [ライブラリ・フレームワーク](https://qiita.com/advent-calendar/2021/categories/libraries)
![ライブラリ・フレームワーク.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/9f4afe0d-57c0-ac49-3c9c-b033782db00c.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|454|55.9%|
|zenn.dev|90|11.1%|
|github.com|16|2.0%|

**Qiita公開 のほうが多い**

## [データベース](https://qiita.com/advent-calendar/2021/categories/databases)
![データベース.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/2024cc77-4261-8a01-fe48-306543ee4b58.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|63|40.9%|
|scrapbox.io|25|16.2%|
|zenn.dev|13|8.4%|

**Qiita公開 のほうが多い**

## [Webテクノロジー](https://qiita.com/advent-calendar/2021/categories/web_technologies)
![Webテクノロジー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/d74aad8a-5eb4-f8e4-b35a-f01c217785dd.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|166|53.4%|
|zenn.dev|42|13.5%|
|note.com|35|11.3%|

**Qiita公開 のほうが多い**

## [モバイル](https://qiita.com/advent-calendar/2021/categories/mobile)
![モバイル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/76f726cf-1e66-a530-0641-6768fd194bcd.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|129|61.7%|
|zenn.dev|41|19.6%|

**Qiita公開 のほうが多い**

## [DevOps・インフラ](https://qiita.com/advent-calendar/2021/categories/devops)
![DevOps・インフラ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/9bfef411-da3f-338e-6475-fe1379717fa2.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|248|55.4%|
|zenn.dev|49|10.9%|

**Qiita公開 のほうが多い**

## [IoT・ハードウェア](https://qiita.com/advent-calendar/2021/categories/iot)
![IoT・ハードウェア.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/64a36270-b349-c43d-0b3f-e907abb04998.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|332|68.2%|
|zenn.dev|22|4.5%|

**Qiita公開 のほうが多い**

## [OS](https://qiita.com/advent-calendar/2021/categories/os)
![OS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/8fe0c0c1-2fc2-def4-7ecf-42604a76ff6c.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|95|77.2%|
|github.com|3|2.4%|
|zenn.dev|1|0.8%|

**Qiita公開 のほうが多い**

## [エディタ](https://qiita.com/advent-calendar/2021/categories/editors)
![エディタ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/8bc562ef-50f0-283d-6ef3-b925ddf034ea.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|zenn.dev|41|32.3%|
|qiita.com|33|26.0%|
|twitter.com|23|18.1%|
|note.com|8|6.3%|

**Zenn公開 のほうが多い**

## [学術](https://qiita.com/advent-calendar/2021/categories/academic)
![学術.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/5b276c08-46d1-a18a-5b6c-857122cada2a.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|178|68.7%|
|zenn.dev|19|7.3%|

**Qiita公開 のほうが多い**

## [サービス・アプリケーション](https://qiita.com/advent-calendar/2021/categories/services)
![サービス・アプリケーション.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/6beab03d-f2e2-0958-9d0d-738112a41e87.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|890|57.0%|
|zenn.dev|100|6.4%|
|note.com|75|4.8%|
|dev.classmethod.jp|60|3.8%|
|medium.com|28|1.8%|

**Qiita公開 のほうが多い**

## [企業・学校・団体](https://qiita.com/advent-calendar/2021/categories/company)
![企業・学校・団体.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/0beb5da2-ff87-0d17-e1b6-1044491552ba.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|3952|61.5%|
|note.com|431|7.4%|
|zenn.dev|234|4.0%|
|medium.com|85|1.5%|

**Qiita公開 のほうが多い**

## [その他](https://qiita.com/advent-calendar/2021/categories/miscellaneous)
![その他.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/5e5bc8ef-85e6-cfb7-8c6c-43e2e32ed395.png)

|ドメイン|記事数|割合|
|:-:|:-:|:-:|
|qiita.com|528|58.9%|
|note.com|127|14.2%|
|zenn.dev|66|7.4%|

**Qiita公開 のほうが多い**

## [未設定](https://qiita.com/advent-calendar/2021/categories/to_be_decided)
集計せず

# 「Zenn で公開」の割合が多いカテゴリーがあった！
なんと「エディタ」カテゴリーでは Qiita で公開された記事数よりも、Zenn で公開された記事数のほうが多いという結果でした。
そもそも「エディタ」カテゴリーでは Twitter での公開も多くあり、かなり分散されているのが印象的でした。

また、今回は「ドメイン」で集計していますので、独自ドメインが設定可能なはてなブログや note など、サービス単位でまとめるとまた違った結果が見えてくる可能性もあります。
さらに「カテゴリー」別ではなくそれぞれのカレンダーごとに見てもおもしろいかもしれません。

今回、データを集計するために得たデータについては GitHub で公開していますので、気になる方はこの記事の続きをお読みください…。

# 実施方法
Mac・iPad の Swift Playgrounds 4 および Mac の Xcode 13.2 で開発可能な .swiftpm プロジェクトを開発しました。
これにより iOS & iPadOS App、macOS Command Line Tool のいずれでも動作します。行った処理は大まかに以下のとおりです。

1. Qiita Advent Calendar 2021 に登録されているすべてのカレンダーの各情報を JSON に保存する
    1. カレンダータイトル
    1. カレンダー URL
    1. カレンダー参加者数（参加者数がゼロのカレンダーはここで弾く）
1. 「1.」の情報を元に、各カレンダーの詳細情報と登録されている記事の各情報を JSON に保存する
    1. カレンダータイトル
    1. カレンダーカテゴリ
    1. カレンダー作成者
    1. カレンダー参加者数
    1. カレンダー URL
    1. カレンダー総 LGTM 数
    1. カレンダー購読者数
    1. 登録されている記事情報
        1. シリーズ（カレンダー1、カレンダー2、…）
        1. n日目
        1. 記事タイトル
        1. 記事 URL
        1. 記事作成者
1. 「2.」の情報をまとめて JSON として出力する
    1. Qiita Advent Calendar 2021 全体の記事総数
    1. 記事 URL にあるドメインとその数

HTML のパーサーには [Kanna](https://github.com/tid-kijyun/Kanna) を用いました。

https://github.com/tid-kijyun/Kanna

# ディレクトリ構成
基本的には .swiftpm の中に Swift ファイルを配置し、macOS Command Line Tool で使用するための `main.swift` のみ外側に持ってきています。macOS Command Line Tool から見える `main.swift` 以外の Swift ファイルは、.swiftpm の中のディレクトリのシンボリックリンクになっています。

```
QACPercentageSurvey
├── README.md
├── Package.swift
├── Sources
│   ├── 1-StoreCalendarsData
│   │   └── main.swift
│   ├── 2-StoreArticlesData
│   │   └── main.swift
│   ├── 3-AggregateHostPercentages
│   │   └── main.swift
│   └── AppModule
│       ├── Entities   -> ../../QACPercentageSurvey.swiftpm/Entities
│       ├── Extensions -> ../../QACPercentageSurvey.swiftpm/Extensions
│       └── Utilities  -> ../../QACPercentageSurvey.swiftpm/Utilities
└── QACPercentageSurvey.swiftpm
    ├── Package.swift
    ├── QACPercentageSurveyApp.swift
    ├── Assets.xcassets
    │   ├── AccentColor.colorset
    │   │   └── Contents.json
    │   ├── AppIcon.appiconset
    │   │   ├── Contents.json
    │   │   ├── PlaceholderAppIcon-gift-1024.png
    │   │   ├── PlaceholderAppIcon-gift-60@2x.png
    │   │   ├── PlaceholderAppIcon-gift-60@3x.png
    │   │   ├── PlaceholderAppIcon-gift-76@2x.png
    │   │   └── PlaceholderAppIcon-gift-83.5@2x.png
    │   └── Contents.json
    ├── Entities
    │   ├── AggregateHostPercentagesResult.swift
    │   ├── QACalendar.swift
    │   ├── QACalendarArticle.swift
    │   ├── QACalendarArticlesData.swift
    │   └── QACalendarsData.swift
    ├── Extensions
    │   ├── ActivityIndicatorView.swift
    │   ├── ActivityView.swift
    │   ├── JSONDecoder+Extensions.swift
    │   └── JSONEncoder+Exntensions.swift
    ├── Utilities
    │   ├── FileHelper.swift
    │   ├── MyURLSession.swift
    │   ├── QACHelper.swift
    │   ├── QACHelperFor1-StoreCalendarsData.swift
    │   └── QACHelperFor2-StoreArticlesData.swift
    └── Views
        ├── 1-StoreCalendarsDataView.swift
        ├── 2-StoreArticlesDataView.swift
        └── 3-AggregateHostPercentagesResultView.swift
```

# HTTP リクエストの間隔を60秒空ける
一度に大量のアクセスを行わないようにするため、async な [`Task.sleep(nanoseconds:)`](https://developer.apple.com/documentation/swift/task/3862701-sleep) を使って HTTP リクエストの間隔を最低60秒空けるようにしました。
また、残りの待機時間が何秒かを Combine の [`@Published`](https://developer.apple.com/documentation/combine/published) で伝えるようにしました。

```swift:2-StoreArticlesDataView.swift
import SwiftUI

@MainActor
struct StoreArticlesDataView: View {
    @ObservedObject private var session = MyURLSession.shared

    // Text("\(MyURLSession.shared.waitingTime) 秒後に接続します…")
    //     .monospacedDigit()
}
```

```swift:MyURLSession.swift
import Foundation
import Combine

public class MyURLSession: ObservableObject {
    public static var shared = MyURLSession()
    
    // ...
    
    @MainActor @Published private(set) var waitingTime: Int = 0
    @MainActor private func set(waitingTime: Int) {
        self.waitingTime = waitingTime
    }
    
    private var session: URLSession { /* ... */ }
    
    private var previousDate: Date? = nil
    public func data(from url: URL) async throws -> Data {
        // ...
        if let previousDate = previousDate {
            // Qiita API v2 ドキュメント https://qiita.com/api/v2/docs の「利用制限」を参考に、自主的に前回のアクセスから最低 60 秒は間隔を空ける
            let interval = DateInterval(start: previousDate, end: Date())
            let requiredWaitingTime = 60 - Int(interval.duration)
            if requiredWaitingTime > .zero {
                await set(waitingTime: requiredWaitingTime)
                for _ in 0..<requiredWaitingTime {
                    try await Task.sleep(nanoseconds: 1 * 1000 * 1000 * 1000)
                    await set(waitingTime: waitingTime - seconds)
                }
            }
        }
        
        let (data, _) = try await session.data(from: url)
        previousDate = Date()
        return data
    }
}
```

# 使用したプログラム・生データ JSON の公開
この記事を執筆するために使用したプログラム一式は GitHub で公開しています。
生データ（JSON）も一緒に置いてありますので、再度スクレイピングする必要がありません。
カレンダーごとの割合を出してみたり、ドメインごとではなくサービスごとに調べた結果の公開、どなたかお待ちしております…。

https://github.com/treastrain/QACPercentageSurvey

```json:swift.json
{
  "created_at" : "2021-12-22T13:40:49Z",
  "calendar_meta_data" : {
    "author" : "@treastrain",
    "category" : "プログラミング言語",
    "lgtm" : 171,
    "subscribers" : 74,
    "title" : "Swift Advent Calendar 2021",
    "participants" : 19,
    "unique_name" : "swift",
    "url" : "https://qiita.com/advent-calendar/2021/swift"
  },
  "articles" : [
    {
      "series" : "カレンダー1",
      "title" : "Swift5 全予約語 (106語) の解説",
      "author" : "@ezura",
      "day" : 1,
      "url" : "https://qiita.com/ezura/items/a4e43dfdadedb738be8a"
    },
    {
      "series" : "カレンダー1",
      "title" : "Swiftのプロトコルは何？",
      "author" : "@hironytic",
      "day" : 2,
      "url" : "https://qiita.com/hironytic/items/6a50b7cc6dd1ea332ee2"
    },
```