Google Play データ活用

Google Playのデータ取得大全:google-play-scraperでできること【Pythonサンプル付き】

2023年5月9日

はじめに

本記事は google-play-scraper(Python)で「何ができるか」を俯瞰し、
それぞれの機能を 最小サンプルコードとともにまとめた“実装カタログ”です。
対象:アプリ分析/ASO/競合調査/可視化の素材作りをしたい方。

注意: 非公式ライブラリのため、過度な並列・大量アクセスは控え、規約遵守の範囲で活用してください。


セットアップ

pip install google-play-scraper pandas

1. アプリ詳細(タイトル/説明/スコア/評価数/インストール数/価格/カテゴリ/開発者/画像URL など)

単一アプリの「現在の顔」を一括取得します。メタデータのベースラインづくりに便利です。

from google_play_scraper import app
import pandas as pd

APP_ID = "com.miHoYo.GenshinImpact"  # 任意のアプリID

detail = app(APP_ID, lang="ja", country="jp")
df = pd.json_normalize(detail)

# 例: よく使う主要フィールド
cols = [
    "title", "description", "summary", "score", "ratings",
    "installs", "minInstalls", "price", "free", "currency",
    "genre", "genreId", "developer", "developerId",
    "developerEmail", "developerWebsite",
    "icon", "headerImage", "screenshots"
]
print(df[cols].T)

用途例:一覧化してカタログを作る/インストール数とスコアの散布図/スクショURLを使ったギャラリーなど。


2. レビュー取得(ページング対応)

レビュー本文・スコア・投稿日・バージョンなどをページングで安定取得。分析や可視化の土台に。

最初の100件だけ取得する(簡易版)

全件ではなく、初回の1ページ分(最大100件)だけを取得したい場合の最小コードです。
ページングを行わないため処理が軽く、手早くサンプルデータを用意したいときに便利です。

from google_play_scraper import reviews, Sort
import pandas as pd

APP_ID = "com.miHoYo.GenshinImpact"

# 最初の1ページ(最大100件)のみ取得
rv, token = reviews(
    APP_ID,
    lang="ja",
    country="jp",
    sort=Sort.NEWEST,   # NEWEST / RATING / HELPFUL
    count=100,          # ここを 20 / 50 などに調整可
    continuation_token=None  # ページングしない
)

df = pd.DataFrame(rv)
df.to_csv("reviews_first100.csv", index=False, encoding="utf-8-sig")
print(f"{len(df)} 件のレビューを取得しました。(初回1ページのみ)")

注意: 並び順(sort)によって取得される最初の100件の内容が変わります。
例:Sort.NEWEST=直近の投稿が中心、Sort.HELPFUL=「参考になった」投票が多い順など。


3. 全件レビュー取得(内部で多数リクエスト)

すべてのレビューを可能な限り取りに行くユーティリティ。大規模アプリではリクエスト数が膨らむため、運用にはご注意を。

from google_play_scraper import reviews_all, Sort
import pandas as pd

APP_ID = "com.miHoYo.GenshinImpact"

data = reviews_all(
    APP_ID,
    lang="ja",
    country="jp",
    sort=Sort.NEWEST
)

df = pd.DataFrame(data)
df.to_csv("reviews_all.csv", index=False, encoding="utf-8-sig")
print(len(df), "件を取得(reviews_all)")

TIP: 再現性や制御性を重視する場合は、前章の reviews() + ループのほうが扱いやすいです。
※ レビュー削除・非表示・地域差・ページング仕様により、取得総数は実行ごとに揺らぐことがあります。

進捗表示つき:安定取得サンプル(tqdm)

長時間の取得でも現在の進捗(取得件数・ページ数・速度)が分かるよう、tqdmで可視化したコードです。安全弁(最大ページ/最大件数/リトライ/バックオフ/重複除去)も入れています。

pip install tqdm
from google_play_scraper import reviews, Sort, app
import pandas as pd
from tqdm import tqdm
import time

def fetch_reviews_with_progress(
    app_id: str,
    lang: str = "ja",
    country: str = "jp",
    sort: Sort = Sort.NEWEST,
    count_per_page: int = 100,
    max_pages: int | None = None,     # ページ上限(安全弁)
    max_reviews: int | None = None,   # 件数上限(安全弁)
    pause_sec: float = 0.6,           # リクエスト間隔(優しめ)
    max_empty_batches: int = 3,       # 新規0件が連続したら終了
    max_retries: int = 3,             # リトライ回数
    backoff_sec: float = 1.5          # バックオフ係数
) -> pd.DataFrame:
    # 目安用の総件数(厳密ではない)
    try:
        meta = app(app_id, lang=lang, country=country)
        approx_total = meta.get("ratings")  # 完全一致ではありません
    except Exception:
        approx_total = None

    all_reviews: list[dict] = []
    seen_ids: set[str] = set()
    token = None
    page = 0
    empty_batches = 0
    start = time.time()

    total_for_bar = max_reviews if max_reviews else approx_total

    with tqdm(total=total_for_bar, unit="rev", desc="Fetching reviews", leave=True) as pbar:
        while True:
            page += 1
            if max_pages is not None and page > max_pages:
                break

            # リトライ付き1ページ取得
            for attempt in range(1, max_retries + 1):
                try:
                    rv, token = reviews(
                        app_id,
                        lang=lang,
                        country=country,
                        sort=sort,
                        count=count_per_page,
                        continuation_token=token,
                    )
                    break
                except Exception:
                    if attempt == max_retries:
                        raise
                    time.sleep(backoff_sec * attempt)

            # 重複除去
            new_rows = [r for r in (rv or []) if r.get("reviewId") not in seen_ids]

            if new_rows:
                all_reviews.extend(new_rows)
                seen_ids.update(r["reviewId"] for r in new_rows if "reviewId" in r)
                pbar.update(len(new_rows))

            elapsed = max(time.time() - start, 1e-6)
            speed = len(all_reviews) / elapsed  # reviews/sec の目安
            pbar.set_postfix(pages=page, batch=len(new_rows), total=len(all_reviews), rps=f"{speed:.1f}")

            if max_reviews is not None and len(all_reviews) >= max_reviews:
                break
            if not token:
                break
            if new_rows:
                empty_batches = 0
            else:
                empty_batches += 1
                if empty_batches >= max_empty_batches:
                    break

            time.sleep(pause_sec)

    return pd.DataFrame(all_reviews)

# ---- 実行例 ----
if __name__ == "__main__":
    APP_ID = "com.miHoYo.GenshinImpact"

    df = fetch_reviews_with_progress(
        app_id=APP_ID,
        lang="ja",
        country="jp",
        sort=Sort.NEWEST,
        count_per_page=100,
        max_pages=None,     # 例: 50 など指定で早めに止められます
        max_reviews=None,   # 例: 5000 など指定で件数上限
        pause_sec=0.6,      # 0.5〜1.0秒を推奨
    )
    df.to_csv("reviews_genshin_paged.csv", index=False, encoding="utf-8-sig")
    print(f"{len(df)} 件のレビューを取得しました。")
  • 件数とページの進捗を表示しつつ、rps(reviews/sec)の概算も表示します。
  • pause_sec0.5〜1.0秒を推奨(負荷と安定性のバランス)。
  • 取得総数は仕様上揺らぐため、max_pagesmax_reviewsで制御するのがおすすめです。

データの仕様と注意点

Google Playのレビューは、ユーザー1人につきアプリごとに1件のみ保持される仕組みです。
同一ユーザーが再度レビューを投稿した場合、以前の内容は上書きされ、過去レビューは保持されません。

  • 同じユーザーが再投稿しても、古いスコアは残らない
  • reviewId は通常固定だが、削除→再投稿時には変更されることもある
  • 時系列の平均スコアは、過去履歴の正確な再現ではなく、取得時点のスナップショット

そのため、スコア推移や件数推移のグラフは参考値としての傾向把握に用いるのが適切です。
厳密な時系列再現を行いたい場合は、定期的にデータをクロールし、差分を記録する運用が推奨されます。

補足: 一部のレビューでは appVersionNone となる場合があります。
これはGoogle Play側でレビュー投稿時にバージョン情報を記録していないためで、ライブラリの不具合ではありません。
Web経由の投稿や古いレビュー、ベータ版からの投稿に多く見られます。分析時は「不明」として扱うのが一般的です。


4. 権限(Permissions)の取得

アプリが要求するパーミッション一覧。プライバシー観点のチェックや、類似アプリ比較に有用です。

from google_play_scraper import permissions

APP_ID = "com.miHoYo.GenshinImpact"
perms = permissions(APP_ID, lang="ja", country="jp")

# 辞書({permission_group: [permissions...]})形式で返る想定
for group, items in perms.items():
    print(f"[{group}]")
    for p in items:
        print(" -", p)

5. 検索(キーワード→アプリ一覧)

キーワードからアプリを列挙。アイコン・スクショ・スコア・レビュー数などもあわせて取得できます。

検索クエリに対して、search()で返る上限(通常は約250〜500件)をできるだけ取得し、appIdで重複を排除します。

from google_play_scraper import search
import pandas as pd

def search_top_apps(query: str, lang: str = "ja", country: str = "jp", n_hits: int = 500) -> pd.DataFrame:
    results = search(query, lang=lang, country=country, n_hits=n_hits)
    df = pd.DataFrame(results)
    if "appId" in df.columns:
        df = df.drop_duplicates(subset="appId", keep="first")
    cols_pref = ["appId", "title", "score", "developer", "free", "url"]
    cols_exist = [c for c in cols_pref if c in df.columns]
    if cols_exist:
        df = df[cols_exist + [c for c in df.columns if c not in cols_exist]]
    return df

# 使い方
df = search_top_apps("放置 RPG", lang="ja", country="jp", n_hits=500)
print(f"取得件数(重複除去後):{len(df)}")
df.to_csv("search_results_up_to_500.csv", index=False, encoding="utf-8-sig")

注意:search() は軽量検索用のため、ratingsinstalls などの詳細項目は含まれません。
必要に応じて、取得した appId を使い app() 関数で詳細を取得してください。

※ search() は軽量検索用のため、返却件数は日や条件で揺れます(通常は250〜500件前後が上限目安)。

用途例:ジャンル内の候補収集/スコア×レビュー数のマッピング/類似競合のベンチマークなど。


運用のコツ

  • 時間を分けて取得する(スケジューラで夜間バッチなど)。
  • 同一アプリのフルスクレイプは控えめに、差分更新(最新分だけ)に設計。
  • レビューの重複除去は reviewId をキーに。
  • 可視化・共有はCSV/Parquetに出力しておくと後工程が楽。

参考リンク


まとめ

アプリ詳細メタデータを一括取得。競合比較・カタログ化に。
レビュー(ページング)安定取得・差分更新・再現性重視の基本形。
全件レビュー内部で多数リクエスト。規模と負荷に注意。
権限プライバシー/機能把握/類似比較の素材に。
検索ジャンル内候補出し・ベンチマークの出発点。

-Google Play, データ活用