1. はじめに
本稿では、Qiita API v2 を用いて記事データを取得し、統計解析および可視化を行う CLI ツール群「Qiita Data Toolkit」の実装について、解説します。
GitHub リポジトリ: Qiita-Data-Toolkit
2. プロジェクトのディレクトリ構成と依存関係
本ツールセットは、保守性と拡張性を考慮し、機能ごとに独立した 3 つのスクリプトで構成されています。
構成ファイル
.
├── users.py # 特定ユーザーの投稿データをクロールし JSON 出力
├── search.py # 検索クエリに基づき条件に合致する記事をクロールし JSON 出力
├── analyze.py # 取得済み JSON データの統計解析・可視化(グラフ生成)
├── requirements.txt # 依存ライブラリ一覧
└── README.md # セットアップ手順
依存ライブラリ (requirements.txt)
本ツールが依存する外部ライブラリは以下の 3 つに絞り込んでいます。
-
requests: HTTP 通信用(API 叩き) -
pandas: データ処理・解析用 -
matplotlib: グラフ描画用
3. 実装:構成管理と認証(Configuration Management)
API トークンなどの機密情報を安全に扱うため、標準ライブラリ os を用いた認証情報の解決ロジックを実装しています。
3.1. トークンの解決優先順位
システム環境変数、.env ファイル、実行時入力の順にトークンを探索します。これにより、CI/CD 環境(環境変数推奨)とローカル開発環境(.env 推奨)の両立を可能にしています。
import os
def load_token():
"""
認証トークンの読み込み。
優先順位: 環境変数 > .envファイル > 実行時入力
"""
# 1. 環境変数のチェック
# コンテナ環境や GitHub Actions などでの実行を想定
token = os.environ.get("QIITA_TOKEN")
if token:
return token
# 2. .env ファイルのチェック
# ファイルを 1 行ずつ読み込み、キー・バリュー形式でパースする
dotenv_path = ".env"
if os.path.exists(dotenv_path):
try:
with open(dotenv_path, "r", encoding="utf-8") as f:
for line in f:
# 前後空白の除去とコメント行の無視
line = line.strip()
if not line or line.startswith("#"):
continue
# キーが QIITA_TOKEN= で始まる行を抽出
if line.startswith("QIITA_TOKEN="):
# キーと値を分離(最初の '=' でのみ分割)
value = line.split("=", 1)[1]
return value.strip().strip('"').strip("'")
except IOError as e:
print(f"Warning: .env ファイルの読み込みに失敗しました: {e}")
# 3. 実行時の対話型入力
# 設定が一切ない場合、ユーザーに入力を促す
print("Qiitaアクセストークンが設定されていません。")
token_input = input("アクセストークンを入力してください (Enterでスキップ): ").strip()
if token_input:
# 入力されたトークンを .env に保存して永続化する(利便性のため)
save_confirm = input("この情報を .env ファイルに保存しますか? (y/n): ").strip().lower()
if save_confirm == 'y':
with open(dotenv_path, "w", encoding="utf-8") as f:
f.write(f"QIITA_TOKEN={token_input}\n")
print(f"DEBUG: {dotenv_path} に保存しました。")
return token_input
4. 実装:Qiita API v2 との通信(Data Ingestion)
データを取得する際は、API の仕様に基づいたページネーションと、サーバー負荷を考慮したレートリミット制御が必須です。
4.1. 堅牢なクロール・ループの実装
Qiita API は一度のリクエストで最大 100 件のデータしか返しません。全データを網羅するためには、レスポンスが空になるまでループを回す必要があります。
import requests
import time
def fetch_qiita_items(url, headers, base_params):
"""
Qiita API から記事データをページネーションを考慮して取得
"""
all_data = []
page = 1
print(f"Fetching from: {url}")
while True:
# パラメータの更新
params = base_params.copy()
params.update({
"page": page,
"per_page": 100 # 最大件数を指定してリクエスト回数を最小化
})
try:
# HTTP GET リクエストの実行
response = requests.get(url, headers=headers, params=params)
# ステータスコードのチェック (200 OK 以外は例外を発生させる)
response.raise_for_status()
items = response.json()
# データが空 (page を進めて記事がなくなった) ならループ終了
if not items:
print("End of data reached.")
break
all_data.extend(items)
print(f" - Page {page}: {len(items)} items fetched (Total: {len(all_data)})")
# レスポンスが 100 件に満たない場合、次ページが存在しないことが確定する
if len(items) < 100:
break
# ページインクリメント
page += 1
# サーバー負荷軽減および 429 Too Many Requests 回避のためのスリープ
# Qiita API v2 のレート制限範囲内 (認証あり時 1000req/h) に収める
time.sleep(0.2)
except requests.exceptions.RequestException as e:
print(f"Error during API request: {e}")
break
return all_data
5. 実装:データのクレンジングと平坦化(Data Processing)
API から返ってくる JSON は、高度にネストされた構造(特に tags や user オブジェクト)を持っています。これを統計解析可能な DataFrame に変換するプロセスを解説します。
5.1. pandas による JSON flattening
JSON のリストから必要なフィールドを抽出し、統計処理が行いやすい型へ変換します。
import pandas as pd
def normalize_to_dataframe(json_list):
"""
ネストされた JSON データをフラットな DataFrame に変換
"""
rows = []
for item in json_list:
# 特定の属性のみを抽出
row = {
"id": item.get("id"),
"title": item.get("title", "No Title"),
"url": item.get("url"),
"likes": int(item.get("likes_count", 0)),
"stocks": int(item.get("stocks_count", 0)),
# 日時文字列を datetime オブジェクトにパース
"created_at": pd.to_datetime(item.get("created_at")),
# タグはリスト形式で保持
"tags": [tag.get("name") for tag in item.get("tags", [])]
}
rows.append(row)
df = pd.DataFrame(rows)
return df
5.2. 多対多関係の正規化(explode を活用)
1 記事に複数のタグがあるため、タグごとの集計(例:どのタグが一番人気か)を行うには、各タグを独立したレコードとして展開する必要があります。
def create_tags_ranking(df):
"""
タグごとの統計用 DataFrame を作成
"""
# 1記事1行の状態から、タグごとにレコードを複製(平坦化)
# 例: ['Python', 'Django'] -> Pythonの行, Djangoの行 に分かれる
exploded_df = df.explode("tags")
# タグ名でグルーピングし、いいね合計や記事数を算出
ranking = exploded_df.groupby("tags").agg({
"id": "count", # 記事数
"likes": "sum", # 合計LGTM数
}).rename(columns={"id": "count"})
return ranking.sort_values(by="count", ascending=False)
6. 実装:マルチプラットフォーム対応の可視化(Visualization)
matplotlib はデフォルトで日本語を表示できませんが、OS 判定を行うことでフォント設定を動的に切り替えています。
6.1. クロスプラットフォーム・フォント設定
Windows、macOS、Linux で異なる日本語フォントパスを自動解決します。
import sys
import matplotlib.pyplot as plt
def initialize_plotting():
"""
OS 環境に応じて適切な日本語フォントをセットアップ
"""
platform = sys.platform
if platform == "win32":
# Windows
plt.rcParams["font.family"] = "MS Gothic"
elif platform == "darwin":
# macOS
plt.rcParams["font.family"] = "Hiragino Sans"
else:
# Linux / Other (IPAフォントなどがインストールされている前提)
plt.rcParams["font.family"] = "sans-serif"
# 図表のデフォルトサイズ設定
plt.rcParams["figure.figsize"] = (12, 6)
# マイナス記号の文字化け防止
plt.rcParams["axes.unicode_minus"] = False
6.2. 記事分布とタグランキングの描画
実際にグラフを生成し、ファイル(PNG)として保存するロジックです。
def plot_results(df, tag_ranking_df, out_dir):
"""
解析結果のグラフ化
"""
# 1. いいね数の分布 (ヒストグラム)
plt.figure()
df["likes"].plot(kind="hist", bins=30, alpha=0.7, color="skyblue", edgecolor="black")
plt.title("LGTM数分布")
plt.xlabel("いいね数")
plt.ylabel("記事数")
plt.grid(axis="y", alpha=0.3)
plt.savefig(f"{out_dir}/likes_hist.png")
plt.close()
# 2. タグランキング (バーチャート)
plt.figure()
top_20_tags = tag_ranking_df.head(20)
top_20_tags["count"].sort_values().plot(kind="barh", color="forestgreen")
plt.title("出現数の多いタグ (TOP 20)")
plt.xlabel("記事数")
plt.ylabel("タグ名")
plt.tight_layout()
plt.savefig(f"{out_dir}/top_tags.png")
plt.close()
7. 実装の課題と最適化
本ツールを大規模(数万件単位)な解析に適用する際の検討事項です。
-
メモリ効率: 全データを
pd.DataFrameに一度に保持するため、数万件を超えるとメモリ不足(OOM)の懸念があります。数ギガバイト規模になる場合は、SQLite などのローカル DB への蓄積を検討すべきです。 -
取得速度: 現在の実装はシングルスレッドです。I/O 待ちが支配的であるため、
asyncioや多スレッド化による高速化が可能ですが、Qiita API のレート制限に即座に抵触するため、あえて制限を設けています。
8. 結論
本ツールの実装を通して、以下のデータ分析プロセスの基礎をカバーしました。
-
requestsによるページネーション付き API クローラーの構築 -
.envを活用したポータブルな環境変数解決 -
pandas.explode()を用いた非構造化(リスト内蔵)データの正規化 -
sys.platform判定による OS 依存性の正規化
これらのパターンは、Qiita API に限らず、多くの REST API 連携ツールを構築する際に利用可能な設計です。
詳細な全ソースコードについては、ぜひ GitHub リポジトリを参照してください。(Star等もぜひ)
GitHub リポジトリ: Qiita-Data-Toolkit