環境の変化と戦う②(connpass API v2編)

# はじめに

コロナ禍を経て、技術イベントやエンジニア勉強会の形態が大きく変わりました。
オフラインからオンラインへ環境が大きく変わり、イベントや勉強会の傾向を把握するため、connpassのイベント検索結果を日次で集計してグラフで可視化を始めました。

https://note.com/kitazaki/n/nd1d6296b2055

https://qiita.com/kitazaki/items/3bf90d38ad20a305958d

![スクリーンショット 2025-04-13 19.47.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/a9c06006-0aaf-4256-ac09-9f50cdf782c5.png)

# 何も変更したくないのに、変更のお知らせはやってくる

2024年2月21日にイベントサーチAPIの有料化のお知らせがありました。

![スクリーンショット 2025-04-13 17.59.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/80b737a8-b4bc-4183-b571-212714c68dd1.png)

https://help.connpass.com/news#news20240221

2024年5月23日に無料提供を終了する予定ということで、イベント・勉強会の傾向把握のための情報収集は終わりかな、と考えていました。
後日(2024年4月24日)、API利用の有料化方針が変更され、既存機能の無料利用が維持されることになりました。

https://x.com/connpass_jp/status/1782969172919738636

また、コミュニティ・個人向けについては申請をすることで、無償で利用可能となりました。

![スクリーンショット 2025-04-13 19.53.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/1f31b1b9-ddbe-4b73-9280-f8e26086a537.png)

https://help.connpass.com/api/

# これまでの経緯

API利用は接続元IPアドレスを1つに固定する必要があったため、固定のグローバルIPアドレスを占有できるVPS(仮想専用サーバ)を契約して、レンタルサーバ(共有サーバ)からスクリプトを移行して動作させることにしました。

![スクリーンショット 2025-04-13 17.38.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/2e666e4e-1e92-43d1-bf67-f83408447471.png)

2024年5月20日に「connpass 個人・コミュニティ向けAPI利用申請フォーム」から申請し、無事に承認されました。

![1_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/649281bc-662c-4ab8-9780-206c2fd309d5.png)

# 何も変更したくないのに、バージョンアップのお知らせはやってくる

2025年4月1日に「connpass APIメジャーバージョンアップの先行のご案内」というメールを受信しました。

![スクリーンショット 2025-04-13 20.09.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/82bd39f9-8a4b-431d-909e-88ac21f6ae48.png)

API v2はv1と互換性が無いということで、スクリプトの変更(移行)が必要になりました。
API v1の提供終了予定は2025年12月31日で、移行期間に余裕がありますが、移行方針を検討する必要があります。
一方で、APIキーによる認証方式へ変更となることで、固定IPアドレスの登録が不要になるのは大きなメリットがあります。

# 対策を考える

まずは、API v2のドキュメントを参照して、v1とv2の変更内容を確認します。
変更点の概要がまとまっており、大きな変更は無いことがわかりました。

- 認証の導入 (接続元IPアドレスの指定 → APIキーによる認証へ)
- アクセス制限の導入 (APIキーごとに「1秒間に1リクエストまで」のリクエスト制限(スロットリング)の導入)
- エンドポイントの変更 (/api/v1/ → /api/v2/、コレクション名称の変更)
- パラメーター名・レスポンスフィールド名の整理 (名称の変更)

https://connpass.com/about/api/v2/

最初にVPS(仮想専用サーバ)のスクリプトを変更してAPI v2に対応します。

![スクリーンショット 2025-04-20 10.21.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/962aa228-aa39-4afd-a887-c7ebaef3db98.png)


Node.js(v16)のJavaScriptでスクリプトを作成していました。
変更後のスクリプト(サンプルコード)です。(apiKeyに自分のAPIキーを設定します)

```javascript:connpass_count_v2.js
'use strict';
 
const fetch = require('node-fetch');
var dateformat = require('dateformat');

const apiKey = 'APIキー';

const prefectures = ["hokkaido", "aomori", "iwate", "miyagi", "akita", "yamagata", "fukushima", "ibaraki", "tochigi", "gunma", "saitama", "chiba", "tokyo", "kanagawa", "yamanashi", "nagano", "niigata", "toyama", "i
shikawa", "fukui", "gifu", "shizuoka", "aichi", "mie", "shiga", "kyoto", "osaka", "hyogo", "nara", "wakayama", "tottori", "shimane", "okayama", "hiroshima", "yamaguchi", "tokushima", "kagawa", "ehime", "kochi", "fu
kuoka", "saga", "nagasaki", "kumamoto", "oita", "miyazaki", "kagoshima", "okinawa", "online"];

const date = dateformat(new Date(), 'yyyymmdd');

(async () => {

  for (let i = 0; i < prefectures.length; i++) {

    const cd = {};  // community data

    const URL = `https://connpass.com/api/v2/events/?ymd=` + encodeURIComponent(`${date}`) + `&prefecture=${prefectures[i]}`;

    cd.name = prefectures[i];

    cd.event = await fetch(URL, {
         headers: {
             'X-API-Key': apiKey
         }})
         .then(response => {
                 return response.json();
         })
         .then(data => {
                 return data.results_available;
         })
         .catch(error => {
                 //console.log(error);
         })

    await sleep(1000);  // 1秒間に1リクエストまで
    console.log(cd);
  }
})();
 
function sleep(waitSec) {
    return new Promise(function (resolve) {
        setTimeout(function() { resolve() }, waitSec);
    });
} 
```

固定IPアドレスが不要になった(スクリプトをどこでも動かせるようになった)ので、Googleスプレッドシートでグラフを公開していることから、GAS(Google Apps Script)でスクリプトを動かすことにしました。

![スクリーンショット 2025-04-20 10.22.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/f672f748-2324-404d-b315-db019847ebcf.png)


# 生成AIの活用

JavaScriptからGAS(Google Apps Script)へのコード変換は生成AIに任せます。
ChatGPTのo3-mini-highモデルを使用して、変更点の説明と合わせて、ほぼ完璧なコードが出力されました。
スプレッドシートへの結果出力もプロンプトからの指示で対応しました。

![スクリーンショット 2025-04-13 20.56.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/0a98db1d-34ae-43f0-9f6d-dd31d37b7cb4.png)

![スクリーンショット 2025-04-13 20.56.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/21997b13-a626-41f5-92f7-168f1b3e4d56.png)

GASのスクリプト(サンプルコード)です。(apiKeyに自分のAPIキーを設定します)

```javascript:main()
function main() {
  // APIキー
  var apiKey = 'APIキー';

  // 都道府県の配列（online を含む）
  var prefectures = [
    "hokkaido", "aomori", "iwate", "miyagi", "akita", "yamagata", "fukushima", 
    "ibaraki", "tochigi", "gunma", "saitama", "chiba", "tokyo", "kanagawa", 
    "yamanashi", "nagano", "niigata", "toyama", "ishikawa", "fukui", "gifu", 
    "shizuoka", "aichi", "mie", "shiga", "kyoto", "osaka", "hyogo", "nara", 
    "wakayama", "tottori", "shimane", "okayama", "hiroshima", "yamaguchi", 
    "tokushima", "kagawa", "ehime", "kochi", "fukuoka", "saga", "nagasaki", 
    "kumamoto", "oita", "miyazaki", "kagoshima", "okinawa", "online"
  ];

  // アルファベット順（a〜z）にソート
  prefectures.sort();

  // 日付を"yyyyMMdd"形式で取得
  // タイムゾーンはスクリプトの設定に従います（必要に応じて "JST" などに変更可能）
  var date = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyyMMdd");
  
  // 各都道府県の結果を格納する配列
  var results = [];

  // 各都道府県ごとにAPIリクエストを実施
  for (var i = 0; i < prefectures.length; i++) {
    // Connpass APIのURLを組み立て
    var URL = "https://connpass.com/api/v2/events/?ymd=" + encodeURIComponent(date) + "&prefecture=" + prefectures[i];
    // リクエストオプション（ヘッダーにAPIキーを設定）
    var options = {
      headers: {
        "X-API-Key": apiKey
      },
      muteHttpExceptions: true
    };

    try {
      // UrlFetchAppを使用してリクエストを送信
      var response = UrlFetchApp.fetch(URL, options);
      // 文字列のレスポンスをJSONオブジェクトに変換
      var data = JSON.parse(response.getContentText());
      Logger.log(prefectures[i] + ": " + data.results_available);
      // results_availableの値を配列に追加
      results.push(data.results_available);
    } catch(e) {
      Logger.log("エラーが発生しました: " + e);
      results.push(0);
    }

    // Connpass側のリクエスト制限に配慮して1秒のスリープ
    Utilities.sleep(1000);
  }

  // スプレッドシートの「log」シートに1行（カラム方向）で出力する
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("log");
  // 日付を結果の先頭に追加する
  results.unshift(date);
  sheet.appendRow(results);
}
```

最後に時間ベースのトリガー(日次実行)を設定して完了です。

![スクリーンショット 2025-04-13 21.02.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/1f48548a-1756-464e-9634-7f0b34c1e790.png)

# あとがき

現実の世界で環境が変わった場合に適応するための方針検討は大変ですが、生成AIの進化の恩恵を受けて作業を楽することができました。(感謝