この記事は Postman Advent Calendar 2023 のシリーズ 3、1日目の記事です。
2023年のアドベントカレンダーの季節が終わりました。記事を投稿した皆さん、大変お疲れ様でございます!
さて、私たちは Postman Advent Calendar 2023 の中の人でもあるので、今回のプレゼント付きカレンダーの受賞者を選定するという役割があります。受賞の対象は多面的な観点から選ばせていただくものの、記事に対する「いいね数」「ストック数」は重要な評価要素です。
そのため、アドベントカレンダーに投稿された記事に関する情報を集計する必要があるのですが、Qiita には API があります!せっかくですので、Postman のスクリプト内からのリクエストの送信およびビジュアライズ機能を使って、記事別およびユーザー別のランキングを作ってみました。
Postman をお使いであれば、誰でも任意のアドベントカレンダー(過去の年のカレンダーも含む)のランキングを表示できます。以下で詳しい手順を解説します。
まずは Qiita API の利用に必要なアクセストークンを、Qiita 設定の「アプリケーション」→「個人用アクセストークン」から発行します。スコープは「read_qiita」にチェックが入っていれば OK です。一度しか表示されないので、忘れずにコピーしてください。
ランキング表示のリクエストが入っているコレクションは、Postman Japan のパブリックワークスペース(世界中の誰もがアクセスできるワークスペース)で公開しています。まずはお手元の Postman でこのワークスペースを検索します。
Postman アプリの上部にある検索窓で「Postman Japan」と入力し、Postman Japanパブリックワークスペースを探します。ワークスペースは、四角が4つ組み合わさったアイコンで表示されています。
「Postman Japan」を選び、ワークスペースに移動します。次に、ワークスペースサイドバーで「コレクション」が選択されていることを確認します。コレクション「Qiita」が今回使うコレクションですが、パブリックワークスペース上では直接リクエストの実行ができないので、まずはコレクションを自分のプライベートワークスペースにフォークします。
サイドバーのコレクション「Qiita」表示の右端にある「•••」メニューから「フォークを作成」を選択します。「コレクションをフォーク」画面の「ワークスペース」で自分のプライベートワークスペースを選択します。デフォルトではどのユーザーも「My Workspace」というプライベートワークスペースを持っているので、特に好みがなければこれを選ぶと良いでしょう。そして「コレクションをフォーク」ボタンを押してフォークします。
フォークを行うとフォーク先のプライベートワークスペースに移動しますが、ここでは戻るボタン(画面左上の「←」)をクリックして「Postman Japan」ワークスペースに戻ります。今度は、ワークスペースサイドバーで「環境」を選択します。サイドバーの環境「Qiita」の「•••」メニューから「フォークを作成」を選択します。「環境をフォーク」画面の「ワークスペース」で自分のプライベートワークスペースを選択し、「環境をフォーク」ボタンを押して環境もフォークします。
さて、ここで自分のプライベートワークスペースにいること、そしてワークスペースサイドバーで「環境」が選択されていることを確認します。サイドバーにあるフォークされた環境「Qiita」を選択して、下記のように変数「token」の初期値および現在値に Qiita アクセストークンを入力し、保存ボタンを押します。
変数 | タイプ | 初期値 | 現在値 |
---|---|---|---|
token | シークレット | <Qiita アクセストークン> | <Qiita アクセストークン> |
ここで設定した変数はそのままでは使用されないため、画面右上の環境メニューから「Qiita」を選択してアクティブにするのを忘れないようにしましょう。
続いて、ワークスペースサイドバーで「コレクション」を選択すると、サイドバーにフォークされたコレクション「Qiita」が見えます。このコレクションを選択し、「認証」タブを開いてみましょう。認証タイプとして「Bearer トークン」が選択されており、トークンに変数「token」が参照されるように設定されています。この設定により、Qiita API ドキュメントのアクセストークンで説明されている通り、コレクションのすべてのリクエストの Authorization リクエストヘッダに、アクセストークンが含められるようになります。
このコレクションを展開して、中にあるリクエスト「Advent Calendar ランキング」を選択し、中身を詳しく見てみましょう。
これは、次のようなエンドポイントです。
GET https://qiita.com/advent-calendar/:year/:calendar
「year」と「calendar」の 2 つのパス変数をパラメーターとして指定する形です。デフォルトでは2023年の Postman アドベントカレンダーを対象にするようになっています。
キー | 値 |
---|---|
year | 2023 |
calendar | postman |
実はこのリクエストはアドベントカレンダーのメインページの HTML を GET メソッドで取得するだけなのですが、テストスクリプトの中で Qiita API や外部サイトへのアクセスを行い、各記事のいいね数、ストック数などの情報を収集して可視化を行なっています。
以下では、「テスト」タブに記入されているスクリプトについて説明していきます。
冒頭、ビジュアライズ機能のための、HTML のテンプレート文字列が定義されています。これは Handlebars テンプレートエンジンの形式に基づいており、二重波括弧内で入力が評価されます。本スクリプトでは、記事別ランキングとユーザー別ランキングの 2 つの表を作っています。
// ビジュアライザーテンプレート定義
const template = `
<h5>{{name}} Advent Calendar {{year}} 記事別ランキング</h5>
<table bgcolor="#FFFFFF">
<tr>
<th>タイトル</th>
<th>ユーザーID</th>
<th>いいね数</th>
<th>ストック数</th>
</tr>
{{#each responses}}
<tr>
<td>{{title}}</td>
<td><img src="{{user.profile_image_url}}" height="32" style="vertical-align: middle"> {{user.id}}</td>
<td>{{likes_count}}</td>
<td>{{stocks_count}}</td>
</tr>
{{/each}}
</table>
<h5>{{name}} Advent Calendar {{year}} ユーザー別ランキング</h5>
<table bgcolor="#FFFFFF">
<tr>
<th>ユーザーID</th>
<th>いいね数</th>
<th>ストック数</th>
<th>投稿数</th>
</tr>
{{#each users}}
<tr>
<td><img src="{{user.profile_image_url}}" height="32" style="vertical-align: middle"> {{user.id}}</td>
<td>{{likes_count}}</td>
<td>{{stocks_count}}</td>
<td>{{items_count}}</td>
</tr>
{{/each}}
</table>
`;
次の部分は、レスポンスを取得して、レスポンスのテキストからカレンダー名、年、投稿のリストを取得しています。
// レスポンス文字列を取得
const response = pm.response.text();
// 最後のscriptタグのマッチを取得
const matches = response.match(/<script.*?>(.*?)<\/script>.*?$/);
// カレンダー名、年、投稿のリストを取得
const {name, year, items} = JSON.parse(matches[1]).adventCalendars.tableAdventCalendars[0];
次は、各投稿が Qiita の記事か、Zenn の記事か、それ以外かを判定し、Qiita の記事の場合は Qiita API を呼び出し、Zenn の記事の場合は記事ページをリクエストしています。pm.environment.get()
を使って環境からアクセストークンを取得している点、pm.sendRequest()
を使って非同期にリクエストを送信している点がポイントです。取得したデータは Promise
のリストとして格納されます。
// APIトークンを取得
const token = pm.environment.get('token');
// 各投稿のデータを非同期で取得
const promises = [];
for (const {comment, url, user} of items) {
// 投稿があったかチェック
if (url) {
// Qiitaの投稿かチェック
if (url.startsWith('https://qiita.com')) {
// 記事IDの取得
const id = url.match(/\w+$/)[0];
promises.push(new Promise((resolve, reject) => {
// Qiita API呼び出し
pm.sendRequest({
url: `https://qiita.com/api/v2/items/${id}`,
header: {'Authorization': `Bearer ${token}`}
}, (error, response) => {
if (error) {
reject(error);
} else {
resolve(response.json());
}
});
}));
} else if (url.startsWith('https://zenn.dev')) {
// Zennの投稿
promises.push(new Promise((resolve, reject) => {
// 記事を取得
pm.sendRequest(url, (error, response) => {
if (error) {
reject(error);
} else {
// いいね数の取得
const matches = response.text().match(/<script.*?>(.*?)<\/script>.*?$/);
const {likedCount} = JSON.parse(matches[1]).props.pageProps.article;
resolve({
title: comment,
user: {id: user.urlName, profile_image_url: user.profileImageUrl},
likes_count: likedCount,
stocks_count: 0
});
}
});
}));
} else {
// Qiita、Zenn以外の投稿
promises.push(Promise.resolve({
title: comment,
user: {id: user.urlName, profile_image_url: user.profileImageUrl},
likes_count: 0,
stocks_count: 0
}));
}
}
}
最後に、すべての非同期レスポンスが得られるまで待機した後、取得したいいね数、ストック数、ユーザー毎の記事数をもとに、投稿リストとユーザーリストを並べ替えて、pm.visualizer.set()
でビジュアライザーにテンプレートと入力データをセットします。
// すべてのレスポンスを待機
Promise.all(promises).then(responses => {
// 投稿をいいね数を第一優先、ストック数を第二優先で並び替え
responses.sort(sortFn);
// ユーザーで集計
dict = {};
for (const {user, likes_count, stocks_count} of responses) {
if (dict[user.id]) {
dict[user.id].likes_count += likes_count;
dict[user.id].stocks_count += stocks_count;
dict[user.id].items_count++;
} else {
dict[user.id] = {user, likes_count, stocks_count, items_count: 1};
}
}
const users = Object.values(dict);
// ユーザーをいいね数を第一優先、ストック数を第二優先、投稿数を第三優先で並び替え
users.sort(sortFn);
// ビジュアライザーにデータをセット
pm.visualizer.set(template, {name, year, responses, users});
});
こちらはソート用の関数です。各オブジェクトを、いいね数を第一優先、ストック数を第二優先、投稿数を第三優先で並び替えます。
// 並べ替え関数
const sortFn = (a, b) => {
if (a.likes_count !== b.likes_count) {
return b.likes_count - a.likes_count || 0;
}
if (a.stocks_count !== b.stocks_count) {
return b.stocks_count - a.stocks_count || 0;
}
return b.items_count - a.items_count || 0;
};
ここまで確認したら、リクエストの「送信」ボタンをクリックしてみましょう。レスポンスの「ボディ」タブの「可視化」ボタンを押すと、ランキングが表示されているはずです。
パス変数を書き換えることで、任意の年の任意のカレンダーのランキングを表示することができます。可視化ビューにうまく結果が表示されない、という方は、見落としがちな次の点を再確認してください。
- 環境「Qiita」画面で、変数を設定した後に「保存」ボタンをクリックしたか
- リクエスト「Advent Calendar ランキング」を実行する時に、画面右上の環境メニューで「Qiita」が選択されてアクティブになっているか