App Store Connect の「評価とレビュー」画面を毎週開いて、星3つ以下のコメントだけを目で追いかける——個人開発を2014年から続けていると、これがじわじわ時間を奪います。私が運営している壁紙アプリは累計5,000万ダウンロードを超え、現在も日次で50〜120件ほどのレビューが日本語・英語・スペイン語・ドイツ語などで届きます。1週間まとめると軽く700件を超え、週末にまとめて読もうとして「結局読み切れずに月曜になる」ことが何度もありました。
そこで2週間前から、レビューを CSV にまとめて Gemini File API に投げ、「謝罪が必要」「機能要望」「広告への苦情」「OS バージョン依存の不具合報告」のように仕分けさせる運用を始めました。Batch Mode で夜間に流す案も並行で試しましたが、CSV を直接 File API に渡す方が小回りが効く場面が多く、今は両者を組み合わせて回しています。この記事は、2週間で見えてきた挙動と判断材料を整理したメモです。
なぜ File API に CSV を直接投げる方針にしたか
最初は素直に「1レビュー = 1リクエスト」で generateContent に投げていました。リクエストの粒度が細かいので失敗時の再試行は楽です。ただ、半年分(約1.2万件)を一気に再分類しようとすると、API のレートリミットに引っかかってしまいます。リトライ間隔を入れても、丸1日のジョブになってしまいました。
File API は、最大2GB のファイルをいったんアップロードしておけば、48時間は同じファイル参照を parts から指定できる仕組みです。1ファイルにつき1回のアップロード、あとは何度でもプロンプトを差し替えてモデルに渡せるため、「同じレビュー群を別の観点で2度・3度分類し直す」用途にぴったりはまりました。
もうひとつの理由は、トークン消費の見通しが立てやすいことです。CSV をテキストとして展開してプロンプトに直接埋め込むと、行数次第で入力トークンが激しくぶれます。File API 経由なら、Gemini 側で「ファイル参照」として扱われ、入力トークンの計上が安定します。私の場合、500行程度の CSV を1日に5〜8回読み直す運用なので、この安定感は地味に効いてきました。
2週間でやらせた3種類の分類
具体的に走らせたタスクは3つです。
- 重要度ラベル付け — 各レビューに「critical(クラッシュ報告・課金トラブル)」「support(操作疑問・要望)」「neutral(感想・低星でも具体性なし)」のいずれかを付ける
- クラスタリング要約 — 同じ訴えが何件あるかをまとめ、「広告頻度が高すぎる」「特定の壁紙のダウンロードが失敗する」のような塊で見せる
- 返信ドラフト下書き — 重要度 critical のものだけ、お詫び+一次回答の文面を日本語・英語の両方で生成する
最初の1週間は (1) と (2) だけ動かし、出力の方向性に手応えが出てから (3) を追加しました。順に追加していくと、どの段階で何にトークンを使っているかが切り分けやすくなります。
File API に CSV をアップロードする最小コード
実際に動いている最小構成は、Python の google-genai SDK で組んでいます。upload_file で CSV を上げて、その参照をプロンプトの先頭に置く形です。
import csv
import io
import pathlib
from google import genai
from google.genai import types
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
csv_path = pathlib.Path("reviews_2026-05-week2.csv")
uploaded = client.files.upload(
file=csv_path,
config=types.UploadFileConfig(
display_name="reviews_2026-05-week2",
mime_type="text/csv",
),
)
prompt = """
次の CSV は App Store Connect から書き出したレビューです。
1行 = 1レビュー。列は rating, locale, title, body, app_version, os_version, created_at。
各レビューに、次のラベルを1つだけ JSON 配列で返してください。
- critical: クラッシュ・課金トラブル・データ消失・購入したのに反映されない 等
- support: 操作方法の疑問・機能要望・UI わかりにくい
- neutral: 感想のみ・低評価でも具体性がない・スパム
JSON 以外は出力しないでください。
出力形式: [{"row": 1, "label": "critical"}, ...]
"""
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=[uploaded, prompt],
config=types.GenerateContentConfig(
response_mime_type="application/json",
temperature=0.1,
),
)
print(response.text)temperature=0.1 にしておくと、同じ CSV を2回投げても結果がほぼ揃います。ラベル付けのように「揺らがないこと」が価値になるタスクでは、揺らぎを抑える側に倒した方が後工程の差分検出が楽になります。
ファイルは48時間で自動的に消える仕様なので、毎週まとまったタイミングで上げ直す運用にしています。1.2万件規模を扱うときは、月ごとに分割して個別ファイルにする方が、エラー時の切り分けがしやすくなりました。
ぶつかった3つの壁と対処
2週間動かして、ぶつかった壁を3つに整理しておきます。同じことをする方の参考になればと思います。
ひとつ目は、CSV 内に改行入りの本文があるとパース順がずれる問題です。レビュー本文に絵文字混じりの改行が入っていると、Gemini が「行番号」をモデル側で再カウントすることがありました。対処として、CSV をエンコードする段階で csv.writer(quoting=csv.QUOTE_ALL) にして、全フィールドをダブルクォートで囲むようにしています。これだけで行番号のずれは収まりました。
with open("reviews_2026-05-week2.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f, quoting=csv.QUOTE_ALL)
writer.writerow(["row", "rating", "locale", "title", "body", "app_version", "os_version", "created_at"])
for i, r in enumerate(reviews, start=1):
writer.writerow([
i, r.rating, r.locale, r.title, r.body,
r.app_version, r.os_version, r.created_at,
])行番号を自前で row 列として持たせるのも重要でした。Gemini に「何行目」を聞くのではなく、CSV 側に書いてあるラベルを返してもらう設計にすると、後段で本文と照らし合わせるのが一気に楽になります。
ふたつ目は、ラベルの偏りです。最初の数日は、critical の比率が想定より少なく出ました。理由を探っていくと、英語レビューで「Crashed once」「Couldn't restore purchase」のような短い書き方を neutral に寄せてしまっていました。プロンプトの critical 定義に「short reports of crash or purchase failure even without details」と一文追加しただけで、肌感に合う比率になりました。
3つ目は、返信ドラフトの口調が定型化しすぎる問題です。Gemini に文面を任せると、お詫び→事象確認→ご協力のお願い、というテンプレに収まりがちでした。私自身、お詫びはあまり長くしたくない性格なので、プロンプトの例示を2件、自分が過去に手書きで返した実例(個人情報を除いたもの)に差し替えました。例示を変えると、生成される文面のトーンがはっきり寄ります。プロンプトでルールを書き連ねるより、自分の手書きを2〜3例見せる方が早かったです。
Batch Mode との使い分けで見えてきたこと
並行で試していた Batch Mode は、夜間に流して翌朝まとめて結果を回収できる点が魅力でした。50%のコストで動くのも個人開発者には大きな利点です。一方で、レイテンシが「24時間以内」と保証される性質なので、「明日朝までに critical だけ拾って手で返したい」用途には少し合いません。
2週間使った今は、次のような切り分けに落ち着いています。
- File API +
generateContent(同期) — 当日分のレビューをその日のうちに分類・要約したいとき。週次のgemini-2.5-flash中心 - Batch Mode — 過去ログ(半年分・1年分)の再分類、ラベル定義を変えて全件にかけ直すとき。
gemini-2.5-proで精度寄せ
夜間バッチで雑にラベルを振り直し、翌日 File API で再確認しながら回答ドラフトを当てる、という流れが今は一番安定しています。指標としては、私が手で読むレビュー数が週700件→週90件まで下がりました。critical 候補だけを開いて、Gemini の下書きに2割ほど手を入れて返信、という運用です。
これから2ヶ月で取り組むこと
直近で手を入れたいところは3つあります。ひとつは、App Store Connect API でのレビュー自動取得を組み込んで、CSV エクスポート手順を完全自動化することです。今は週末に手で書き出しているのですが、ここを CI 化すれば「気づいたら critical の下書きが Slack に並んでいる」状態にできます。
ふたつ目は、Google Play 側のレビューを同じパイプラインに乗せることです。Android 版の壁紙アプリも別アプリで運用していて、こちらは Reply to Reviews API があるのでフローが組みやすそうです。
3つ目は、返信文面の効果計測です。返信したレビューが後日「星を上げ直してもらえたか」を追えるようにすれば、Gemini の下書きをどの方向にチューニングすべきかの根拠になります。私は1997年に16歳でインターネットに触れて「国境を越えて人とつながれる」体験をしたことを今でも覚えていて、レビューに返信することは「同じ画面の向こう側にいる人に手紙を返す」感覚に近いと思っています。だからこそ、文面の手触りを定量で見直す土台を作っておきたいと考えています。
同じように個人開発でレビュー対応に追われている方の参考になれば幸いです。お読みいただきありがとうございました。