壁紙アプリのレビューが3,000件を超えたあたりで、自分の限界に気づきました。
毎週末、App Store Connectにログインしてレビューをひとつひとつ読み、Notionに分類しながらメモしていく作業。2014年から続けてきたその習慣は、アプリが増えるにつれて現実的ではなくなっていきました。4本のアプリで合計1,000件超のレビューが毎月つき、半日かけても全部読めありません。
そこでClaude APIを使ったバッチ分析を組んでみたのですが、試して最初に驚いたのは「分析の精度」ではなく「自分の認知バイアス」が可視化されたことでした。
手で読む限界と、3つ星レビューに眠っていたもの
人間がレビューを読むと、どうしても5つ星と1つ星に目が行きます。嬉しいコメントと、傷つく批判。その中間にある3つ星レビューは、感情的な引力が弱く、流し読みしがちです。
ところが、Claude APIでレビューを感情・キーワード・改善要望の3軸で構造化分析してみると、3つ星レビューに明確に多くの「具体的な改善ヒント」が含まれていることがわかりました。
「通知のタイミングが微妙にずれる」「ダウンロード後に一度アプリを閉じないと壁紙が反映されない」「有料プランへの導線がわかりにくい」——これらはどれも1〜2件ずつ書かれていて、手で読むと見落とすレベルです。でも1,000件をAPIで処理すると、同じ問題が30件に1件の頻度で繰り返されているのが見えてきます。
実装の全体像
分析フローはシンプルです。
- App Store Connect APIまたはiTunesのRSS APIでレビューを取得
- Claude APIに渡して構造化レスポンスを得る
- 結果をカテゴリ別に集計して優先度スコアを算出
以下に、実際に使っているPythonコードの要所を示します(APIキーやアプリIDは環境変数から読み込む構成です)。
import anthropic
import json
import os
from typing import Optional
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
def analyze_reviews_batch(reviews: list[dict]) -> list[dict]:
"""
App Storeレビューのリストを受け取り、
感情・カテゴリ・改善提案の構造化データを返す
"""
# 1回のAPIコールで最大20件をまとめて処理(コスト削減)
reviews_text = "\n---\n".join([
f"評価: {r['rating']}星\nタイトル: {r['title']}\n本文: {r['body']}"
for r in reviews[:20]
])
message = client.messages.create(
model="claude-haiku-4-5-20251001", # 分析はHaikuで十分なコスパ
max_tokens=2048,
messages=[
{
"role": "user",
"content": f"""以下のApp Storeレビューを分析し、JSON配列で返してください。
各レビューに対して:
- sentiment: "positive" / "neutral" / "negative"
- category: "ui" / "bug" / "feature_request" / "performance" / "billing" / "other"
- improvement_hint: 具体的な改善点(あれば)、なければ null
- priority_score: 1〜5(5が最高優先度。具体的で再現性の高い問題ほど高くする)
レビュー:
{reviews_text}
JSON配列のみを返してください。説明文は不要です。"""
}
]
)
try:
results = json.loads(message.content[0].text)
return results
except json.JSONDecodeError:
# パース失敗時は空のリストを返してスキップ
return []
def aggregate_by_priority(analyzed_reviews: list[dict]) -> dict:
"""
分析結果を優先度スコアで集計し、
次のスプリントで対応すべき改善点を上位5件抽出する
"""
# priority_score 4以上の improvement_hint を抽出
high_priority = [
r["improvement_hint"]
for r in analyzed_reviews
if r.get("priority_score", 0) >= 4
and r.get("improvement_hint")
]
# Claude Sonnet 4.6で優先度上位5件を要約・整理
if not high_priority:
return {"top_issues": [], "summary": "高優先度の改善点は見つかりませんでした"}
issues_text = "\n".join(f"- {h}" for h in high_priority)
summary_message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
messages=[
{
"role": "user",
"content": f"""以下のApp Storeレビューから抽出した改善ヒントをまとめ、
重複を排除して上位5件に絞ってください。
開発者がすぐに対応できる形式で、箇条書きで出力してください。
改善ヒント一覧:
{issues_text}"""
}
]
)
return {
"top_issues": high_priority[:10],
"summary": summary_message.content[0].text
}Haikuで一括分析し、SonnetでまとめるツーステップにすることでAPI費用を抑えています。1,000件を処理してもだいたい$0.5〜$1.5程度です。
バッチ処理のポイント — レート制限とコスト管理
実装で一番詰まったのは、APIのレート制限との戦いでした。
レビューを1件ずつAPIに投げると遅すぎますし、一気に100件まとめると応答のJSON解析が安定しません。20件ごとのバッチが、精度とコストのバランス上もっとも安定していました。
import time
def process_all_reviews(reviews: list[dict]) -> list[dict]:
"""全レビューをバッチで処理する"""
all_results = []
batch_size = 20
for i in range(0, len(reviews), batch_size):
batch = reviews[i:i + batch_size]
results = analyze_reviews_batch(batch)
all_results.extend(results)
# レート制限を避けるため1秒待機
# Haikuの場合、TPMが高いので通常は不要だが安全のため入れる
if i + batch_size < len(reviews):
time.sleep(1)
print(f"処理済み: {min(i + batch_size, len(reviews))}/{len(reviews)}")
return all_resultstime.sleep(1) を入れるかどうかは使っているティアによりますが、個人開発で使っているTier 1〜2の範囲では、これがあると安心です。
もうひとつ、max_tokens=2048 の制限は必ず設定してください。設定しないと、Claudeが丁寧に解説付きのJSONを返してしまい、パースに失敗することがあります(実際にこれで1時間溶かしました)。
分析結果を見てわかった「自分の思い込み」
実装そのものよりも、分析結果を見たときの体験の方が印象に残っています。
1,000件の分析を終えて出てきた優先度上位の課題は「ウィジェット機能のiOS最新版での動作不安定」でした。自分ではウィジェット機能には手をかけているつもりでしたし、レビューを手で読んでいたときは気づいていませんでした。でも頻度で見ると、この問題が他のどのカテゴリよりも多く報告されていたのです。
個人開発を長く続けていると、自分が熱中している部分と、ユーザーが困っている部分がずれてくることがあります。累計5,000万DLを超えてなお、この感覚のずれは埋まらないのだと気づきました。APIを使った定量分析は、その感覚的なずれを補正する道具として、思っていたより有用でした。
App Store Connect APIとiTunes RSS APIについて
レビューの取得方法についても簡単に触れます。
App Store Connect APIは公式ですが、セットアップに秘密鍵の設定が必要です。素早く試したい場合は、iTunes RSS APIが手軽です。
import requests
def fetch_reviews_rss(app_id: str, page: int = 1) -> list[dict]:
"""
iTunes RSS APIを使ってApp Storeレビューを取得する。
最大10ページ(500件)まで取得可能。
"""
url = f"https://itunes.apple.com/jp/rss/customerreviews/page={page}/id={app_id}/sortby=mostrecent/json"
response = requests.get(url)
if response.status_code != 200:
return []
data = response.json()
entries = data.get("feed", {}).get("entry", [])
# 最初のentryはアプリ情報なのでスキップ
return [
{
"rating": int(e["im:rating"]["label"]),
"title": e["title"]["label"],
"body": e["content"]["label"]
}
for e in entries[1:] # index 0をスキップ
if "im:rating" in e
]ただしiTunes RSS APIは非公式扱いで、取得できるのは最新500件程度です。それ以上のデータが必要な場合はApp Store Connect APIが必要です。
試して気づいた落とし穴
- JSON解析の失敗: Claudeが「以下がJSONです:」のような前置きをつけることがあります。
message.content[0].textをstrip()した上で、最初の[から最後の]を切り出すと安定します - 感情の誤分類: 「〇〇機能を追加してほしい」という要望を、ネガティブと分類することがあります。プロンプトに「機能要望はfeature_requestカテゴリにしてください」と明示すると改善します
- 日本語レビューの精度: Haikuは日本語の文脈を読む精度が高く、意外なほど正確でした。一方で、絵文字が多いレビューは誤分類されやすい傾向があります
まず手持ちのアプリのレビュー100件から試してみるのが一番早いと思います。小さい規模でも、自分が見落としていた傾向が出てくるはずです。
関連記事: Python SDKを使ったClaude APIチャットボット実装チュートリアル