はじめに
会社の技術ブログ発信の手段として Qiita を利用している場合,技術ブログを成果として報告する際に,期間毎の閲覧数を定量指標として示すことが有効です.また,いいね数や閲覧数などを定量的に分析することで,どのような記事が人気であるかを把握することができます.
上記の目的を達成するために,Qiita API を利用して,指定の期間内の Qiita 投稿記事のいいね数・閲覧数などを取得し,集計する Python スクリプトを作成しました.集計コードは以下のリンクで公開しています.
利用方法
- コードを clone し,
src
ディレクトリに移動します.
git clone https://github.com/ren8k/analyze_qiita.git
cd src
-
Qiita のアクセストークンを発行します.
- ログインした状態で https://qiita.com/settings/tokens/new へアクセス
- スコープでは
read_qiita
を選択し,任意の名前をアクセストークンの説明
に入力した後,発行
を押下 - 発行されたアクセストークンをコピー
-
config.py
の以下の変数を設定します.-
USER_ID
: Qiita のユーザ名 -
API_TOKEN
: Qiita API のアクセストークン -
START_DATE
: 集計開始日時 -
END_DATE
: 集計終了日時
-
from datetime import datetime, timezone
from typing import Final
PER_PAGE: Final[int] = 100
USER_ID: Final[str] = "<user name>"
API_TOKEN: Final[str] = "<api token>"
START_DATE: Final[datetime] = datetime(2024, 4, 1, tzinfo=timezone.utc)
END_DATE: Final[datetime] = datetime(2025, 3, 31, tzinfo=timezone.utc)
-
main.py
を実行します.
python main.py
-
config.py
で指定した期間の各記事の閲覧数やいいね数を含む,以下のような出力が得られます.
| 記事タイトル | いいね数 | ストック数 | View 数 | 作成日 | タグ |
| :------------------------------------------------------------------------------------------- | -------: | ---------: | ------: | :--------- | :------------------------------------------------------- |
| Amazon Titan Image Generator v2 の全機能を徹底検証:機能解説と実践ガイド | 14 | 8 | 3820 | 2024-09-26 | Python, AWS, bedrock, 画像生成, 生成 AI |
| Amazon Bedrock における Claude 3 Haiku の Fine-Tuning 検証レポート | 28 | 15 | 13230 | 2024-07-30 | Python, AWS, bedrock, 生成 AI, claude |
| Amazon Bedrock Converse API で Claude3 の JSON モードを利用する | 20 | 11 | 8314 | 2024-07-01 | Python, AWS, bedrock, 生成 AI, LLM |
| 「Amazon Bedrock」で始める生成 AI アプリ開発入門バイブルの登場!! | 7 | 7 | 5663 | 2024-06-22 | AWS, 技術書, bedrock, 生成 AI, LLM |
| Amazon Bedrock Converse API と Tool use を知識ゼロから学び,発展的なチャットアプリを実装する | 32 | 19 | 14341 | 2024-06-10 | Python, AWS, bedrock, 生成 AI, claude |
| Amazon Bedrock の Converse API と Streamlit で 10 分でチャットアプリを作成する | 12 | 6 | 4348 | 2024-05-31 | Python, AWS, bedrock, Streamlit, 生成 AI |
| 世界最速!?Amazon Bedrock の Custom model import の機能検証 | 17 | 8 | 8498 | 2024-05-26 | AWS, bedrock, 生成 AI, LLM, Llama3 |
| Amazon Bedrock で Advanced RAG を実装する上での Tips | 27 | 22 | 19018 | 2024-05-18 | Python, AWS, rag, bedrock, KnowledgeBaseForAmazonBedrock |
============ Summary ============
- 期間: 2024-04-01 から 2025-03-31
- 対象記事数: 8
- View 総計: 77232
- 平均いいね数: 19.6
- 平均ストック数: 12.0
- 平均いいね率: 0.2%
================================
- markdown プレビューすると,以下のように表示されます.
記事タイトル | いいね数 | ストック数 | View 数 | 作成日 | タグ |
---|---|---|---|---|---|
Amazon Titan Image Generator v2 の全機能を徹底検証:機能解説と実践ガイド | 14 | 8 | 3820 | 2024-09-26 | Python, AWS, bedrock, 画像生成, 生成 AI |
Amazon Bedrock における Claude 3 Haiku の Fine-Tuning 検証レポート | 28 | 15 | 13230 | 2024-07-30 | Python, AWS, bedrock, 生成 AI, claude |
Amazon Bedrock Converse API で Claude3 の JSON モードを利用する | 20 | 11 | 8314 | 2024-07-01 | Python, AWS, bedrock, 生成 AI, LLM |
「Amazon Bedrock」で始める生成 AI アプリ開発入門バイブルの登場!! | 7 | 7 | 5663 | 2024-06-22 | AWS, 技術書, bedrock, 生成 AI, LLM |
Amazon Bedrock Converse API と Tool use を知識ゼロから学び,発展的なチャットアプリを実装する | 32 | 19 | 14341 | 2024-06-10 | Python, AWS, bedrock, 生成 AI, claude |
Amazon Bedrock の Converse API と Streamlit で 10 分でチャットアプリを作成する | 12 | 6 | 4348 | 2024-05-31 | Python, AWS, bedrock, Streamlit, 生成 AI |
世界最速!?Amazon Bedrock の Custom model import の機能検証 | 17 | 8 | 8498 | 2024-05-26 | AWS, bedrock, 生成 AI, LLM, Llama3 |
Amazon Bedrock で Advanced RAG を実装する上での Tips | 27 | 22 | 19018 | 2024-05-18 | Python, AWS, rag, bedrock, KnowledgeBaseForAmazonBedrock |
============ Summary ============
- 期間: 2024-04-01 から 2025-03-31
- 対象記事数: 8
- View 総計: 77232
- 平均いいね数: 19.6
- 平均ストック数: 12.0
- 平均いいね率: 0.2%
================================
上記の私の記事の集計結果によると,全ての記事に AWS や Bedrock が関連していることがわかります.また,記事毎の閲覧数にばらつきがありますが,いいね数と閲覧数には相関がありそうです.
コード
config.py
from datetime import datetime, timezone
from typing import Final
PER_PAGE: Final[int] = 100
USER_ID: Final[str] = "<user name>"
API_TOKEN: Final[str] = "<api token>"
START_DATE: Final[datetime] = datetime(2024, 4, 1, tzinfo=timezone.utc)
END_DATE: Final[datetime] = datetime(2025, 3, 31, tzinfo=timezone.utc)
api.py
import requests
from typing import Any, Dict, Final, List
from config import API_TOKEN
BASE_URL: Final[str] = "https://qiita.com/api/v2"
def get_headers() -> Dict[str, str]:
return {
"content-type": "application/json",
"Authorization": f"Bearer {API_TOKEN}",
}
# https://qiita.com/api/v2/docs#get-apiv2authenticated_useritems
def get_user_items(page: int) -> List[Dict[str, Any]]:
url: str = f"{BASE_URL}/authenticated_user/items?page={page}"
response: requests.Response = requests.get(url, headers=get_headers())
return response.json()
analyzer.py
import time
from datetime import datetime
from typing import Any, Dict, List, Tuple
from api import get_user_items
from config import END_DATE, PER_PAGE, START_DATE
def is_within_date_range(date: datetime) -> bool:
return START_DATE <= date <= END_DATE
def get_article_stats() -> Tuple[List[Dict[str, Any]], int, int, int, int]:
all_views: int = 0
all_likes: int = 0
all_stocks: int = 0
articles: List[Dict[str, Any]] = []
items_count: int = 0
page: int = 1
while True:
items: List[Dict[str, Any]] = get_user_items(page)
if not items:
break
for item in items:
created_at: datetime = datetime.fromisoformat(
item["created_at"].replace("Z", "+00:00")
)
if not is_within_date_range(created_at) or item["private"]:
continue
all_views += item["page_views_count"]
all_likes += item["likes_count"]
all_stocks += item["stocks_count"]
items_count += 1
articles.append(
{
"title": item["title"],
"likes": item["likes_count"],
"stocks": item["stocks_count"],
"views": item["page_views_count"],
"created_at": created_at.strftime("%Y-%m-%d"),
"tags": ", ".join([tag["name"] for tag in item["tags"]]),
}
)
if len(items) < PER_PAGE:
break # 最後のページであればループを終了
page += 1
time.sleep(1)
return articles, all_views, all_likes, all_stocks, items_count
def calculate_averages(
all_likes: int,
all_stocks: int,
all_views: int,
items_count: int,
) -> Tuple[float, float, float]:
ave_likes: float = round(all_likes / items_count, 1) if items_count > 0 else 0
ave_stocks: float = round(all_stocks / items_count, 1) if items_count > 0 else 0
engagement_rate: float = (
round(all_likes / all_views * 100, 2) if all_views > 0 else 0
)
return ave_likes, ave_stocks, engagement_rate
main.py
from typing import Any, Dict, List
import pandas as pd
from analyzer import calculate_averages, get_article_stats
from config import END_DATE, START_DATE
def print_article_table(articles: List[Dict[str, Any]]) -> None:
df = pd.DataFrame(articles)
# df = df[['title', 'likes', 'stocks', 'views', 'created_at', 'tags']]
df.columns = ["記事タイトル", "いいね数", "ストック数", "View数", "作成日", "タグ"]
markdown_table = df.to_markdown(index=False)
print(markdown_table)
def print_summary(
all_views: int,
ave_likes: float,
ave_stocks: float,
engagement_rate: float,
items_count: int,
) -> None:
print("\n============ Summary ============")
print(f"- 期間: {START_DATE.date()} から {END_DATE.date()}")
print(f"- 対象記事数: {items_count}")
print(f"- View総計: {all_views}")
print(f"- 平均いいね数: {ave_likes}")
print(f"- 平均ストック数: {ave_stocks}")
print(f"- 平均いいね率: {engagement_rate}%")
print("================================")
def main() -> None:
articles, all_views, all_likes, all_stocks, items_count = get_article_stats()
ave_likes, ave_stocks, engagement_rate = calculate_averages(
all_likes, all_stocks, all_views, items_count
)
print_article_table(articles)
print_summary(all_views, ave_likes, ave_stocks, engagement_rate, items_count)
if __name__ == "__main__":
main()
利用している API について
GET /api/v2/authenticated_user/items
により,認証中のユーザーの記事の一覧を作成日時の降順で取得できます.レスポンスには,各記事の以下の情報を含まれます. (以下の情報以外のメタデータも含んでおります.詳細は API リファレンスを参照下さい.)
- id: 記事の ID
- title: 記事のタイトル
- created_at: 作成日時
- updated_at: 更新日時
- likes_count: いいね数
- stocks_count: ストック数
- comments_count: コメント数
- page_views_count: 閲覧数
GET /api/v2/authenticated_user/items
で記事の ID を取得し,それを基に GET /api/v2/items/:item_id
で閲覧数を取得している他の記事もありましたが,前者の API のみで得られる情報は包含されています.
コードの簡易説明
本実装では,analyzer.py
で定義している関数 get_article_stats
にて,Qiita API を利用した集計処理を行っています.アルゴリズムとしては以下です.
- ページング処理により記事を取得し,集計を実施
- 指定した期間内の記事,または,限定共有記事でないものを対象とする
- 関数
get_user_items
で,認証中のユーザーの記事の一覧を作成日時の降順で取得
- 記事の以下の詳細情報を抽出
- タイトル
- いいね数
- ストック数
- View 数
- 作成日
- タグ
def get_article_stats() -> Tuple[List[Dict[str, Any]], int, int, int, int]:
all_views: int = 0
all_likes: int = 0
all_stocks: int = 0
articles: List[Dict[str, Any]] = []
items_count: int = 0
page: int = 1
while True:
items: List[Dict[str, Any]] = get_user_items(page)
if not items:
break
for item in items:
created_at: datetime = datetime.fromisoformat(
item["created_at"].replace("Z", "+00:00")
)
if not is_within_date_range(created_at) or item["private"]:
continue
all_views += item["page_views_count"]
all_likes += item["likes_count"]
all_stocks += item["stocks_count"]
items_count += 1
articles.append(
{
"title": item["title"],
"likes": item["likes_count"],
"stocks": item["stocks_count"],
"views": item["page_views_count"],
"created_at": created_at.strftime("%Y-%m-%d"),
"tags": ", ".join([tag["name"] for tag in item["tags"]]),
}
)
if len(items) < PER_PAGE:
break # 最後のページであればループを終了
page += 1
time.sleep(1)
return articles, all_views, all_likes, all_stocks, items_count
上記の関数を改修することで,例えば指定のタグが付与された記事のみを集計することも可能です.
まとめ
Qiita API を利用して,指定の期間に投稿した記事の閲覧数・いいね数を取得し,集計する Python スクリプトを作成しました.本稿が,Qiita API を利用した集計を行う際の参考になりましたら幸いです.是非本実装をカスタマイズしてお使い下さい!