Steam データ活用

SteamのAppIDを見つける5つの方法【URL/検索/API/Pythonバッチ】

2025年10月17日

この記事でできること:Steamでデータ取得を始める前段として、AppID(数値)を確実に特定する方法をまとめます。URLからの即判定、名前からの公式APIによる照合、appdetails を使った最終確認、さらに Pythonでの一括解決(CSVバッチ) まで、実務フローで解説します。

このガイドは Steamガイド(入口ページ) の「Step1: AppID取得」に対応しています。

そもそもAppIDとは?

AppIDはSteamで各アプリ(ゲーム・DLC・ソフト等)に割り当てられた一意の数値IDです。レビュー取得(/appreviews)、ストア情報(/api/appdetails)、同時接続(GetNumberOfCurrentPlayers)など、ほぼすべての取得でキーとして使います。

  • ゲーム本体:type=game
  • DLC:type=dlc
  • サウンドトラック等:type=music など

注意:記事・分析の対象が「ゲーム本体」かどうかを、後述の appdetails で検証しましょう(DLC/バンドルを誤って選ばない)。

方法1:ストアURLから直取り(最短)

ストアURLが分かっていれば、https://store.steampowered.com/app/730/...太字部分がAppIDです。記事からの引用や検索結果でURLを見つけたら、これが最速。 Pythonワンライナー(URL → AppID抽出)

import re
from urllib.parse import urlparse

def appid_from_url(url: str) -> int | None:
    path = urlparse((url or "").strip()).path
    # /app/<id>/ も /agecheck/app/<id>/ も許容
    m = re.search(r"/(?:(?:agecheck|widget)/)?app/(\d+)(?:/|$)", path, flags=re.IGNORECASE)
    return int(m.group(1)) if m else None

# --- URLを以下に入力 ---
url = "https://store.steampowered.com/app/2246340/_/" 

appid = appid_from_url(url)
print("AppID:", appid) if appid else print("AppIDが見つかりませんでした")
実行結果を確認
AppID: 2246340

方法2:公式APIの全アプリ一覧(ISteamApps)から名前検索

ゲーム名しか分からないときは、公式の全アプリ一覧を取得してローカルで名前マッチさせます。スペル差や表記揺れに備えてあいまい一致が便利。 最小コード(名前 → 候補AppID)

import requests, difflib, unicodedata, re

def load_applist():
    url = "https://api.steampowered.com/ISteamApps/GetAppList/v2/"
    return requests.get(url, timeout=60).json()["applist"]["apps"]

def storesearch(query: str, cc="jp", lang="japanese", n=5):
    r = requests.get(
        "https://store.steampowered.com/api/storesearch/",
        params={"term": query, "l": lang, "cc": cc},
        timeout=30
    ).json()
    items = r.get("items") or []
    out = []
    for it in items:
        aid = it.get("id") or it.get("appid")
        nm  = it.get("name")
        ty  = it.get("type")  # "app", "sub" など。必要なら "app" のみに絞る
        if aid and nm and (ty in (None, "app")):
            out.append((nm, int(aid)))
        if len(out) >= n:
            break
    return out

def appdetails_type(appid: int, cc="jp", lang="japanese"):
    r = requests.get(
        "https://store.steampowered.com/api/appdetails",
        params={"appids": appid, "cc": cc, "l": lang},
        timeout=30
    ).json()
    node = r.get(str(appid), {})
    if node.get("success") and node.get("data"):
        return node["data"].get("type")
    return None

def find_appid_by_name(query: str, apps: list[dict], n=5):
    # 日本語など非ASCIIが含まれるときは storesearch を優先
    if re.search(r"[^\x00-\x7f]", query):
        res = storesearch(query, n=n)
        # 必要なら "game" のみ残す
        filtered = []
        for nm, aid in res:
            ty = appdetails_type(aid)
            if ty == "game":
                filtered.append((nm, aid))
        return filtered or res  # 全滅したら未フィルタを返す
    # ASCII なら従来どおり英語名にあいまい一致
    name_map = {a["name"]: a["appid"] for a in apps if a.get("name")}
    names = list(name_map.keys())
    def _norm(s): return unicodedata.normalize("NFKC", s or "").casefold()
    names_norm = [_norm(x) for x in names]
    target = _norm(query)
    cand_norms = difflib.get_close_matches(target, names_norm, n=n, cutoff=0.6)
    if not cand_norms:
        cand_norms = [nm for nm in names_norm if target in nm][:n]
    out = []
    for cn in cand_norms:
        i = names_norm.index(cn)
        formal = names[i]
        out.append((formal, name_map[formal]))
    return out

# --- ユーザーがここを書き換えるだけ ---
query = "モンスターハンター"

apps = load_applist()  # 英語名用のフォールバックに使う
candidates = find_appid_by_name(query, apps, n=5)
for name, appid in candidates:
    print(appid, "-", name)
# 期待例:
# 2246340 - モンスターハンター ワイルズ
# 582010 - Monster Hunter: World
# ...(環境により順序は前後します)

実行結果を確認
2246340 - モンスターハンターワイルズ
582010 - モンスターハンター:ワールド
1446780 - モンスターハンターライズ
2356560 - モンスターハンターストーリーズ
2852190 - モンスターハンターストーリーズ3 ~運命の双竜~

ポイント:正式名に近い候補を複数出す→ 次の「方法3」で型(game/dlc)や発売日を確認して確定。

方法3:appdetails で最終確認(型/発売日/価格など)

候補のAppIDは appdetails(非公開仕様のストアAPI)で型が game か発売日価格/無料対応OSタグ などを確認し、対象が本体かを見極めます。 最小コード(AppID → メタ確認)

import requests, json

def appdetails(appid: int, cc="jp", lang="japanese") -> dict:
    """Storefront appdetails を取得。success=False のときは空dictを返す。"""
    url = "https://store.steampowered.com/api/appdetails"
    r = requests.get(url, params={"appids": appid, "cc": cc, "l": lang}, timeout=30).json()
    node = r.get(str(appid), {}) or {}
    if not node.get("success"):
        return {}
    return node.get("data") or {}

# --- ユーザーはここだけ差し替えればOK ---
appid = 2246340  # モンスターハンター ワイルズ

meta = appdetails(appid)
if not meta:
    print("データが取得できませんでした(地域/年齢制限・一時障害の可能性)。")
else:
    # 基本情報
    name = meta.get("name")
    app_type = meta.get("type")  # "game", "dlc", "software" など
    release = (meta.get("release_date") or {}).get("date")
    coming_soon = (meta.get("release_date") or {}).get("coming_soon")
    price = meta.get("price_overview") or {}

    print("タイトル:", name)
    print("タイプ:", app_type)
    print("発売日:", release, "(coming_soon:", coming_soon, ")")
    print("price_overview:")
    print(json.dumps(price, ensure_ascii=False, indent=2))  # 見やすく整形
実行結果を確認
タイトル: モンスターハンターワイルズ
タイプ: game
発売日: 2025年2月27日 (coming_soon: False )
price_overview:
{
  "currency": "JPY",
  "initial": 899000,
  "final": 899000,
  "discount_percent": 0,
  "initial_formatted": "",
  "final_formatted": "¥ 8,990"
}

非公開仕様のため将来変更の可能性に注意。実装はタイムアウト・リトライ・バリデーションを入れるのが実務的です。

方法4:名前からの自動解決関数(あいまい一致 → appdetails検証)

方法2と3を統合し、名前→AppID→型検証を一発で返す関数にしておくと再利用が効きます。DLC/サントラの誤選択を防ぐため、type=="game" を既定でフィルタ。 コピペ用(Python)

import re, requests, difflib

def resolve_app(name: str | None = None,
                appid: int | None = None,
                require_game: bool = True,
                cc: str = "jp",
                lang: str = "japanese"):
    def _details(aid: int):
      r = requests.get("https://store.steampowered.com/api/appdetails",
                       params={"appids": aid, "cc": cc, "l": lang}, timeout=30).json()
      node = r.get(str(aid), {}) or {}
      return node.get("data") if node.get("success") else None

    # 1) appid 優先
    if appid:
      d = _details(appid)
      if not d: return appid, None
      return (None, d) if (require_game and d.get("type")!="game") else (appid, d)

    # 2) name から解決(日本語→storesearch、英語→GetAppList)
    if not name: return None, None

    # 日本語等(非ASCII)→ storesearch
    if re.search(r"[^\x00-\x7f]", name):
      s = requests.get("https://store.steampowered.com/api/storesearch/",
                       params={"term": name, "l": lang, "cc": cc}, timeout=30).json()
      for it in (s.get("items") or []):
        aid = it.get("id") or it.get("appid")
        if not aid: continue
        d = _details(int(aid)) or {}
        if not require_game or d.get("type")=="game":
          return int(aid), d
      return None, None

    # 英語系 → GetAppList + difflib
    apps = requests.get("https://api.steampowered.com/ISteamApps/GetAppList/v2/", timeout=60).json()["applist"]["apps"]
    names = [a["name"] for a in apps if a.get("name")]
    cand = difflib.get_close_matches(name, names, n=1, cutoff=0.6)
    if not cand: return None, None
    aid = next(a["appid"] for a in apps if a.get("name")==cand[0])
    d = _details(aid)
    if not d: return aid, None
    return (None, d) if (require_game and d.get("type")!="game") else (aid, d)

# --- ユーザーはここだけ差し替えればOK ---
appid = 2246340            # 例:モンスターハンター ワイルズ
name = None                # appid を使うので None のままでOK

aid, meta = resolve_app(name=name, appid=appid)
if aid is None:
    # type != "game" などのときはこちらに来る
    print("ゲーム本体ではありませんでした:", meta and meta.get("type"))
elif not meta:
    print("appdetails を取得できませんでした(地域/年齢制限・一時障害の可能性)。")
else:
    print("AppID:", aid)
    print("タイトル:", meta.get("name"))
    print("type:", meta.get("type"))
実行結果を確認
AppID: 2246340
タイトル: モンスターハンターワイルズ
type: game

方法5:複数タイトルを一括解決

タイトルリストをリストで一括解決するのが効率的です。

import requests, difflib, unicodedata, re, time

def resolve_list(queries: list[str],
                 require_game=True,
                 top_k=1,
                 sleep_each=0.35) -> list[dict]:
  def _norm(s): return unicodedata.normalize("NFKC", s or "").casefold()

  # 英語用の対応表(フォールバック)
  apps = requests.get("https://api.steampowered.com/ISteamApps/GetAppList/v2/", timeout=60).json()["applist"]["apps"]
  name_map = {a["name"]: a["appid"] for a in apps if a.get("name")}
  names     = list(name_map.keys())
  names_norm= [_norm(x) for x in names]

  def details(aid:int, cc="jp", lang="japanese"):
    r = requests.get("https://store.steampowered.com/api/appdetails",
                     params={"appids": aid, "cc": cc, "l": lang}, timeout=30).json()
    n = r.get(str(aid), {}) or {}
    return n.get("data") if n.get("success") else {}

  out = []
  for q in queries:
    q = (q or "").strip()
    if not q: continue

    cands = []
    if re.search(r"[^\x00-\x7f]", q):  # 日本語など
      s = requests.get("https://store.steampowered.com/api/storesearch/",
                       params={"term": q, "l":"japanese", "cc":"jp"}, timeout=30).json()
      for it in (s.get("items") or []):
        aid = it.get("id") or it.get("appid")
        nm  = it.get("name")
        ty  = it.get("type")
        if aid and nm and (ty in (None, "app")):
          cands.append((nm, int(aid), 1.0, "storesearch"))
        if len(cands) >= max(3, top_k): break
    else:
      t = _norm(q)
      cand_norms = difflib.get_close_matches(t, names_norm, n=max(3, top_k), cutoff=0.6)
      for cn in cand_norms:
        i = names_norm.index(cn)
        nm = names[i]; aid = name_map[nm]
        score = difflib.SequenceMatcher(None, t, cn).ratio()
        cands.append((nm, aid, score, "applist"))

    if not cands:
      out.append({"query": q}); continue

    wrote = 0
    for nm, aid, score, src in cands:
      d = details(aid) or {}
      ty = d.get("type")
      if require_game and ty != "game":
        time.sleep(sleep_each); continue
      out.append({
        "query": q, "matched_name": d.get("name") or nm, "appid": aid,
        "type": ty, "release_date": (d.get("release_date") or {}).get("date"),
        "match_score": round(float(score),3) if score is not None else None,
        "source": src
      })
      wrote += 1
      time.sleep(sleep_each)
      if wrote >= top_k: break

    if wrote==0:
      nm, aid, score, src = cands[0]; d = details(aid) or {}
      out.append({
        "query": q, "matched_name": d.get("name") or nm, "appid": aid,
        "type": d.get("type"),
        "release_date": (d.get("release_date") or {}).get("date"),
        "match_score": round(float(score),3) if score is not None else None,
        "source": src
      })
      time.sleep(sleep_each)

  return out

# 使い方
rows = resolve_list(["HoloCure", "Palworld", "モンスターハンター"])
for r in rows: print(r)
実行結果を確認
{'query': 'HoloCure', 'matched_name': 'LOCURA', 'appid': 2492990, 'type': 'game', 'release_date': '2023年9月6日', 'match_score': 0.714, 'source': 'applist'}
{'query': 'Palworld', 'matched_name': 'Palworld / パルワールド', 'appid': 1623730, 'type': 'game', 'release_date': '2024年1月18日', 'match_score': 1.0, 'source': 'applist'}
{'query': 'モンスターハンター', 'matched_name': 'モンスターハンターワイルズ', 'appid': 2246340, 'type': 'game', 'release_date': '2025年2月27日', 'match_score': 1.0, 'source': 'storesearch'}

よくある落とし穴と対処

  • DLC/サントラを拾ってしまうappdetailstypegame を確認。記事テンプレでは game以外は除外
  • 表記揺れ:記号・サブタイトル違いで一致しない場合は、複数候補を出して人工確認。英語名で試すのも有効。
  • 別プラットフォーム版:一部ツール/ソフトは typesoftware。記事目的に合うか確認。
  • レート制限:短時間に連打しない。タイムアウト・リトライ・スリープを実装。
  • キャッシュGetAppList はサイズが大きいのでローカルにキャッシュして再利用。

次のステップ

  1. 【How-to】公式Web APIの使い方(GetAppList / 同接 / 実績)
  2. 【How-to】appdetailsでストア情報を取る
  3. 【How-to】appreviewsでレビュー本文を取る
  4. 【How-to】同時接続の自前収集と可視化
  5. 【How-to】SteamSpyの推定値の扱い

入口に戻る:Steamガイド

免責とポリシー:
appdetails/appreviews は公式ドキュメント外のエンドポイントを利用します。将来の仕様変更により挙動が変わる可能性があります。利用規約・法令・引用ルールを遵守の上でご利用ください。

-Steam, データ活用
-, , , , ,