アークナイツのレビューを分析してみた
目次
1. はじめに
本記事では、Google Playに投稿された「アークナイツ」のレビューを対象に、自然言語処理を用いたテキスト分析を行い、プレイヤーの関心や不満点を可視化してみました。
2 分析対象と使用環境・ライブラリ
分析対象
- アプリ名:アークナイツ(Arknights)
- プラットフォーム:Google Play
- レビュー件数:約4600件(日本語レビューのみ、短文・重複を除外)
使用環境
Google Colaboratory を使用します。
使用ライブラリ
# ── Google Play からレビューやアプリ情報をスクレイピングするため
!pip install google-play-scraper
# ── 日本語の形態素解析エンジン SudachiPy 本体 + その完全版辞書
!pip install sudachipy sudachidict_full
# ── Matplotlib で日本語を崩れず表示するためのフォントラッパー
!pip install japanize-matplotlib
# ── 進捗バー表示ユーティリティ
!pip install --quiet tqdm
# ── 自然言語処理ライブラリ
!pip install transformers
# ── 日本語の形態素解析器
!pip install fugashi
# ── fugashi用の辞書
!pip install ipadic
# グラフ描画ライブラリ
!pip install matplotlib
# データ操作ライブラリ
!pip install pandas
# 処理の進捗を可視化するライブラリ
!pip install tqdm
# fugashi用の別の辞書
!pip install unidic-lite
分析結果の可視化に棒グラフを使用するのですが、そのまま実行すると文字化けするので、日本語フォントのインストールも行います。
何故かインストールだけではうまく行かずに文字化けしてしまい、キャッシュの削除も必要になったので、その時に使用したものも載せておきます。
# 日本語フォントのインストールと設定(Colab用)
!apt-get -y install fonts-ipaexfont
import shutil
import os
font_cache = os.path.expanduser("~/.cache/matplotlib")
if os.path.exists(font_cache):
shutil.rmtree(font_cache)
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'IPAexGothic'
3. データ収集
データ収集にはスクレイピングツールを使用するつもりだったのですが、無償版のツールだと取得件数などに制限がかかっていたため、ChatGPTにお願いしてみたら無事取得できました。
データ分析に必要な項目のみを取得するように少しカスタマイズするだけで済みました。
3.1 Google Play よりレビューデータを取得
# google play よりレビューデータを取得
from google_play_scraper import Sort, reviews
import pandas as pd
from datetime import datetime
import os
# ───────────────────────────────────────
# 保存用ディレクトリ
DATA_DIR = "./data"
os.makedirs(DATA_DIR, exist_ok=True)
# ───────────────────────────────────────
# レビュー収集設定
APP_ID = "com.YoStarJP.Arknights"
all_reviews, continuation_token, count = [], None, 0
MAX_COUNT = 10_000
while True:
result, continuation_token = reviews(
APP_ID,
lang="ja",
country="jp",
sort=Sort.NEWEST,
count=500,
continuation_token=continuation_token,
)
if not result:
print("⚠️ 取得結果が空になりました。打ち切ります。")
break
all_reviews.extend(result)
count += len(result)
print(f"{count} 件取得済み")
if not continuation_token or count >= MAX_COUNT:
break
# ───────────────────────────────────────
# DataFrame 化
df = pd.DataFrame(all_reviews)
# 必要な列だけ抽出
df = df[
[
"score",
"content",
]
]
# カラム名を日本語に変換
df.columns = ["スコア","レビュー内容"]
# ───────────────────────────────────────
# CSV保存
output_path = os.path.join(DATA_DIR, "arknights_reviews_raw.csv")
df.to_csv(output_path, index=False, encoding="utf-8-sig")
print(f"✅ {len(df):,} 件のレビューを保存しました → {output_path}")
3.2 データの前処理
「アークナイツ」「アークナイツ」の様に全角・半角の違いにより別の意味を持つ単語として抽出される事を防ぐため、正規化により全角・半角を統一します。
それと、余計な空白の除去、一言レビューも除去します。
- 全角・半角の統一(NFKC正規化)
- 連続スペースの削除
- 文頭・文末の余分なスペース除去
- 短すぎる or 意味が薄いレビューを除去
# レビューデータを正規化&フィルタリング
import os
import pandas as pd
import re
import unicodedata
# Colab環境向け:データフォルダパス
DATA_DIR = "./data"
input_path = os.path.join(DATA_DIR, "arknights_reviews_raw.csv")
output_path = os.path.join(DATA_DIR, "processed_reviews.csv")
# ─────────────────────────────
# データ読み込み
df = pd.read_csv(input_path)
# ─────────────────────────────
# レビューの正規化処理関数
def normalize_text(text):
if pd.isna(text):
return ""
# Unicode正規化(NFKC)
text = unicodedata.normalize("NFKC", text)
# 半角カナ→全角、全角英数字→半角など
text = re.sub(r"[ \u3000]+", " ", text) # 複数スペースを1つに
text = text.strip()
return text
# レビュー本文の正規化
df["レビュー内容"] = df["レビュー内容"].apply(normalize_text)
# ─────────────────────────────
# フィルタリング:短すぎる or 意味が薄いレビューを除去
def is_valid_review(text):
if not text or len(text) < 5:
return False
if text in ["神ゲー", "良い", "まあまあ", "普通", "面白い"]:
return False
return True
df = df[df["レビュー内容"].apply(is_valid_review)].copy()
# ─────────────────────────────
# 出力保存
df.to_csv(output_path, index=False, encoding="utf-8-sig")
print(f"✅ 正規化・フィルタ済みレビューを {output_path} に保存しました ({len(df):,} 件)")
参考までに正規化・フィルタ前のものをもう一度載せて置きます。
短文レビューが消えているのが分かると思います。
(再掲)正規化・フィルタ前

4. 形態素解析 + ストップワード除去
4.1 形態素解析
レビュー文を形態素解析し、意味のある語句(名詞・動詞・形容詞)のみを抽出します。形態素解析とはある文章を単語や品詞の単位で分割することです。
例えば、
「私は今日、大阪で友達と会います。」という文章を形態素解析すると、
「私/は/今日/、/大阪/で/友達/と/会/い/ます/。」となります。
ここに更に品詞の情報を付加して、
「私(代名詞)/は(助詞)/今日(名詞)/、(読点)/大阪(名詞)/で(助詞)/友達(名詞)/と(助詞)/会(動詞)/い(助動詞)/ます(助動詞)/。」
と言う感じで、文章を細切れにしてカテゴリー分けします。
何でこんな事するのかと言うと、特定の品詞(名詞・動詞・形容詞)に属する単語のみを抽出するのに使えるからです。
4.2 ストップワードリストの作成
ゲームのレビューなら大体入っていて、ゲームの評価にはほぼ関係しないであろうと思われる情報価値の低い単語を除去します。(例:アプリ、ゲーム、スマホ など)
これを「stopword.txt」として保存し、google colabにアップロードします。

4.3 ユーザー辞書の作成
形態素解析に使用される日本語辞書として有名なものは大体以下になると思います。
| 形態素解析エンジン | 開発元 / ライセンス | 対応辞書・モード | 主な特徴 | 代表的な用途 |
|---|---|---|---|---|
| MeCab | 東京大学 & NAIST / BSD 2-Clause | IPADIC(標準)、UniDic、NEologd ほか | 超高速・軽量 (C++ 実装)、辞書を差し替えるだけで精度調整、Python/Ruby/Java などバインディング豊富 | 大規模テキスト処理、検索インデックス生成、機械学習前処理 |
| Sudachi / SudachiPy | Works Applications / Apache 2.0 | sudachidict_small・core・full、分割モード A/B/C | 複合語を用途別に「まとめる/細分化」切替可、固有名詞を長い形で保持でき NER に強い、Python 版は辞書自動DL対応 | BERT 等の前処理、業務文書解析、研究用途 |
| JUMAN++ | 京都大学 NLP 研究室 / GPL v2 | JUMANdic | 活用形・語義タグが詳細、接続制約が厳格で誤判定少なめ、係り受け解析器 KNP と直結可能 | 学術コーパス解析、法律・古典文、深い構文解析 |
sudachipyを使用していますが、当初は有名なMeCabにwikipedhiaのタイトルリストをユーザー辞書として追加したものを使用するつもりでした。
Mecab単体で使用しないのは、ゲームレビューだとネットスラングやそのゲーム内の用語などが多用されるため、通常の辞書では形態素解析の精度が落ちてしまう可能性があると考えたからです。
参考URL:https://qiita.com/ayaniimi213/items/94bfa86ec73d7625aef4
ですが何度やっても「mecab-dict-index : command not found」のエラーが出てユーザー辞書の作成が出来ず。。。
一応、解決方法らしきものが載っているページ (https://haruka0000.hatenablog.com/entry/2018/01/24/165422) も見つけたのですが、google colab環境上での解決方法が分からず諦めました。
とはいえ、先述の通りゲームレビューにはネットスラングやゲーム内の用語が多数出てきます。
なので、ユーザー辞書は是非追加したい、という事でsudachipy+自作ユーザー辞書を使いました。
ユーザー辞書の内容はレビューに出てきそうな、アークナイツのキャラクター・育成素材・拡張ストーリーのタイトルなどです。
4.3.1 ゲーム特有の固有名詞を収集
大体どのゲームにも攻略wikiが存在しますが、当然アークナイツにも複数存在します。
今回はその中から1つを選び、chatGPTにお願いしてキャラクター・育成素材・拡張ストーリーのタイトルなどを収集してもらいました。
収集した結果に自分でも数語付け足した結果、702語のリストが出来ました。

4.3.2 ユーザー辞書のフォーマットに変換
本来であれば1語ずつ適切な品詞などを選択して設定すべきなのでしょうが、今回は全て固有名詞・一般語扱いとして作成します。
ユーザー辞書のフォーマットの説明についてはこちらを参照。https://github.com/WorksApplications/Sudachi/blob/develop/docs/user_dict.md
単語リスト→sudachipyユーザー辞書へのフォーマット
import pandas as pd
# 入力: 表層形だけが1列に並んだCSV(UTF-8で保存)
input_path = "./data/arknight_dic_sorce.csv"
output_path = "./data/arknight_userdict_18col.csv"
# デフォルト値
LEFT_ID = 1285
RIGHT_ID = 1285
COST = 5000
POS1 = "名詞"
POS2 = "固有名詞"
POS3 = "一般"
POS4 = "*"
CONJ_TYPE = "*"
CONJ_FORM = "*"
DICT_FORM_ID = "*"
SPLIT_TYPE = "A"
SPLIT_A = "*"
SPLIT_B = "*"
UNUSED = ""
# 単語リスト読み込み
words = pd.read_csv(input_path, header=None, names=["word"])
# 18列構成に整形
rows = []
for word in words["word"]:
word = str(word).strip()
if word == "":
continue
row = [
word, # 見出し (検索用)
LEFT_ID, # 左ID
RIGHT_ID, # 右ID
COST, # コスト
word, # 表示用見出し
POS1, # 品詞1
POS2, # 品詞2
POS3, # 品詞3
POS4, # 品詞4
CONJ_TYPE, # 活用型
CONJ_FORM, # 活用形
word, # 読み
word, # 正規化表記
DICT_FORM_ID, # 辞書形ID
SPLIT_TYPE, # 分割タイプ
SPLIT_A, # A単位
SPLIT_B, # B単位
UNUSED # 未使用
]
rows.append(row)
# 書き出し
df_out = pd.DataFrame(rows)
df_out.to_csv(output_path, index=False, header=False, encoding="utf-8", quoting=1)
print(f"✅ SudachiPy形式のユーザー辞書CSVを出力しました → {output_path}")
4.3.3 ビルド
作成したユーザー辞書CSVをビルドして.dicファイルを作成します。
sudachipy ubuild -s <system.dicのパス> <ユーザー辞書CSV> -o <出力先ユーザー辞書.dic>
!sudachipy ubuild \
-s /usr/local/lib/python3.11/dist-packages/sudachidict_full/resources/system.dic \
./data/arknight_userdict_18col.csv \
-o ./data/arknight_user.dic
こうして出来上がった.dicファイルをgoogle colabにアップロードしてsudachipyの辞書ファイルと一緒に読み込ませる様に設定ファイル(json形式)を作成します。
今回はdataフォルダを作成し、その中に作成した.dicファイル(arknight_user.dic)を置いておき、それを形態素解析の時に使用する様にjsonファイルを設定します。
import json
import os
import importlib.util
# sudachidict_full モジュールのパスを取得
spec = importlib.util.find_spec("sudachidict_full")
sudachi_dict_path = os.path.join(os.path.dirname(spec.origin), "resources", "system.dic")
# 設定ファイルを生成(ユーザー辞書のパスを指定)
config = {
"systemDict": sudachi_dict_path,
"userDict": ["data/arknight_user.dic"]
}
# 設定ファイルをjson形式で保存
with open("data/sudachi.json", "w") as f:
json.dump(config, f, indent=2)
print("✔ sudachi.json を作成しました")
print(f"systemDict path: {sudachi_dict_path}")
上のコードで作成した設定ファイル(sudachi.json)を使って形態素解析を行います。
# -*- coding: utf-8 -*-
# Colab対応版:transform_dataframe.py
import pandas as pd
import re
import os
from sudachipy import tokenizer, dictionary
# ───────────────────────────────
# Colab用のデータディレクトリ
DATA_DIR = "./data"
os.makedirs(DATA_DIR, exist_ok=True)
# ───────────────────────────────
# 入力ファイル(明示指定)
input_path = os.path.join(DATA_DIR, "processed_reviews.csv")
print(f"▶ 入力ファイル: {input_path}")
# ───────────────────────────────
# ストップワード読み込み
STOPWORDS_PATH = "./data/stopwords.txt"
def load_stopwords(path):
if not os.path.isfile(path):
raise FileNotFoundError(f"ストップワードファイルが見つかりません: {path}")
with open(path, "r", encoding="utf-8") as f:
return set(line.strip() for line in f if line.strip())
custom_stopwords = load_stopwords(STOPWORDS_PATH)
# ───────────────────────────────
# 正規化・形態素解析の設定
# sudachipy tokenizer 設定(sudachi.json で systemDict / userDict 指定済み)
tokenizer_obj = dictionary.Dictionary(config_path="data/sudachi.json").create()
mode = tokenizer.Tokenizer.SplitMode.B
normalize_dict = {
"アプデ": "アップデート",
"無課金": "無料",
"課金勢": "課金ユーザー",
"落ちる": "クラッシュ",
"推し": "お気に入り",
"ガシャ": "ガチャ",
"体力": "スタミナ",
"理性": "スタミナ",
"原石": "源石",
"石": "源石",
"キャラ": "キャラクター",
"プレー": "プレイ",
"楽しむ": "楽しい",
}
# ───────────────────────────────
# 前処理(正規化+ストップワード除去+トークン化)
def preprocess(text):
for key, val in normalize_dict.items():
text = text.replace(key, val)
text = re.sub(r"[!-/:-@[-`{-~0-9A-Za-z]", "", text)
text = re.sub(r"[「」、。・♪☆★■※]", "", text)
tokens = []
for m in tokenizer_obj.tokenize(text, mode):
if m.part_of_speech()[0] in ["名詞", "動詞", "形容詞"]:
surface = m.normalized_form()
if len(surface) > 1 and surface not in custom_stopwords:
tokens.append(surface)
return tokens
# ───────────────────────────────
# レビューの読み込みと前処理
df = pd.read_csv(input_path)
reviews = df["レビュー内容"].dropna().tolist()
# トークン化実行
tokenized_reviews = [preprocess(review) for review in reviews]
joined_reviews = [" ".join(tokens) for tokens in tokenized_reviews]
# 元データに「トークン列」を追加して保存
df["トークン"] = joined_reviews
# 保存:レビュー情報+トークン列つき
tokenized_output_path = os.path.join(DATA_DIR, "tokenized_reviews.csv")
df.to_csv(tokenized_output_path, index=False, encoding="utf-8-sig")
print(f"✅ トークン化済みレビュー(全情報付き)を保存しました → {tokenized_output_path}")
5. データ分析・可視化
形態素解析で単語の集合になったユーザーレビューを スコア別(ポジティブ:★4以上/ネガティブ:★2以下) に分け、1-gram・2-gram・3-gramの頻出語を可視化します。
n-gramの違い
例えば以下の様なユーザーレビューがあった場合、n-gramの設定によって得られる語が大きく異なります。
例:「キャラが魅力的で、ストーリーも面白く、暇つぶしにちょうどいい。」
| n | 抽出結果例 | 説明 |
|---|---|---|
| 1-gram | キャラ、魅力的、ストーリー、面白く、暇つぶし、ちょうどいい | 単語単体の頻度を見る。文脈は不明だが注目語を把握できる。 |
| 2-gram | キャラ 魅力的、ストーリー 面白く、暇つぶし ちょうどいい | どの要素がどう評価されているかが見えてくる。 |
| 3-gram | キャラ 魅力的 ストーリー、ストーリー 面白く 暇つぶし | 自然な感想に近づくが、出現頻度はかなり下がる。 |
nの数が少なければより多くのサンプルを得られますが、文脈は見えにくくなります。逆にnの数を増やすと文脈は見えてきますが、サンプル数が落ちてしまいます。
2-gramあたりで十分なサンプル数が取れれば良いのですが、、、結果は以下の通りでした。
5.1 1-gramの結果から見えること
1-gramは、レビュー内で頻繁に登場する単語単体を可視化しています。
文脈は読み取れませんが、ユーザーが何に注目しているのかという関心の焦点は浮かび上がります。
-
ポジティブレビューでは、「キャラクター」「レア」「ストーリー」「面白い」などが上位を占めており、キャラクターやストーリーに対する肯定的な評価が読み取れます。
-
また「課金」や「育成」といった負担要素も含まれていますが、ポジティブな評価で頻出しているということは、相対的に負担が低く、ユーザーに受け入れられている可能性があると考えられます。
-
「ステージ」「プレー」「楽しむ」「攻略」といったワードからゲーム性に対する評価が高いものと考えられます。
-
一方、ネガティブレビューでも「キャラクター」「ストーリー」「ガチャ」「課金」などが頻出しており、評価対象はポジティブと類似しています。ただし、「時間」「面白い」「出る」などの語が含まれている点から、**「時間がかかりすぎる」「面白くない」「ガチャが出ない」**といった否定的な文脈が背景にある可能性が高いと推測されます。
1-gramは言ってしまえば「よく出てくる単語ランキング」です。
「キャラクターは良いんだけど。。。」と言うネガティブレビューや、「キャラクターが良い!」と言うポジティブレビューのどちらもカウントされます。
ここでは推測に留め、評価は一旦保留します。
5.2 2-gram:評価の文脈が見え始める
2-gramでは、連続した2語の組み合わせを抽出することで、具体的な評価の文脈を可視化できます。
出現回数は1-gramに比べて1桁程度減少しますが、意味の明確さは飛躍的に向上します。
-
ポジティブレビューでは、「時間 掛かる」「キャラクター 可愛い」「難易度 高い」など、評価語と対象語のペアが出現しやすくなっています。これは、ユーザーの評価の根拠を的確に反映しており、「何がどう良いのか」を読み解く手がかりとなります。
-
ネガティブレビューでは、「時間 掛かる」「スキップ 機能」「キャラクター 出る」など、具体的な不満点が明確に表れています。
※「ガチャで欲しいキャラクターが出ない」を形態素解析すると、最後の「出ない」が「出る」「ない」に分割してトークン化されます。そのため「キャラクター 出る」と表示されます。
5.3 結果と考察
ユーザーが関心を持っている要素、不満を持っている要素がなんとなく見えてきました。ですが、要素だけでは評価が出来ません。そして、文脈を評価できる2-gramは件数が少なすぎます。
今回のデータセットでは2-gramの出現頻度が極端に低く、十分な分析には至りませんでした。
最後のまとめでも触れますが、今回は前処理におけるデータの正規化・表記揺れの対処が甘かったため、十分な件数が取れませんでした。そのため、分析対象から除外することにします。
5.4 感情傾向の分析
2-gramで見たかった内容を可視化するため、1-gramのカウント結果に表示されたワードから、キーワード(例:「キャラクター」「ガチャ」「イベント」など)を抜き出して感情傾向の分析(ネガポジ分析)を行います。
具体的には
- レビュー全文を「。!??!」の文章の切れ目となっていそうな箇所で区切る。
- 区切った文章を感情分類モデルで分析する。
と言う流れになります。
感情分類モデルによる分析とは、簡単に言うと
「楽しい」「面白い」→ポジティブ
「だるい」「つまらない」→ネガティブ
と言う感じで単語ごとにネガ・ポジを判定して行き、文全体の印象を分類します。
感情分類にはBERTベースの日本語モデル(LoneWolfgang/bert-for-japanese-twitter-sentiment)を使用しました。文中にキーワードが含まれていれば、その文に対してモデルを適用し、感情を判定・カウントしています。
分析対象は以下の3パターンです。
- 全レビュー
- スコア4以上のレビュー
- スコア3以下のレビュー
分析結果をまとめた棒グラフを以下に掲載します(上から順に:全体/高評価/低評価)。
※低評価については星3以下を対象としています。これは、星4以上のレビューが3,512件、星3以下のレビューが1,248件だった為、少しでもバランスを取るためです。
5.4.1 結果と考察
関心が高いのは「キャラクター」と「課金・ガチャ」
ポジティブ・ネガティブ問わず、**「キャラクター」「課金・ガチャ」**に言及するユーザーは圧倒的に多く、どのスコア帯においても頻出しています。これはスマホゲームにおいて共通する傾向で、予想通り。
ネガティブ感情が強く表れるのは「時間」と「難易度」
「時間」に関しては、スコアが低くなるほどネガティブな感情の比率が高まっており、特にスコア3以下では半数以上がネガティブという結果でした。
全体の結果で見ても圧倒的にネガティブな評価が出ています。
アークナイツはテキスト量が非常に多く、イベントストーリーでも真面目に読もうとすると数時間かかることも珍しくないため、ストレスの要因になっていると思われます。
ストーリーに対してのポジティブがネガティブの2倍程度あるので、全体としては否定的な意見がありつつも、おおむね好評なのだと評価できます。
他に時間が掛かる要素としてはステージ攻略・育成などがあると思いますが、星3以下のネガポジ分析結果をみると育成、ステージ、攻略、スタミナ、難易度でネガティブ要素が一定数出ています。いずれも件数は50~90件と少ないですが、
- 育成に時間が掛かる
- ステージ攻略に時間が掛かる
- 難易度が高く、レアキャラが居ないと(課金しないと)時間が掛かる
などがありそうです。
6. まとめ
時間が掛かる要因について掘り下げる、と言う観点で改めてデータ分析を行うとまた気付きありそうですが、いくつかのデータ分析を行って色々足りていない点が浮き彫りになったので、それをまとめて終わりにしたいと思います。
- 1-gramでは、注目されているワードやユーザーの関心の焦点を把握することができた。最初のキーワードを探すツールとして有用。
- 2-gramでは、ユーザーがどのように評価しているか(評価の文脈)を具体的に読み解くことがで出来るが、表記ゆれ・類似語・ストップワードなど正規化・フィルタリングを丁寧に行わないと件数が少な過ぎて参考に出来ない。
- ゲームレビューに一般的な日本語辞書を適用するのが難しい。そのゲーム内での造語や一般世界での言葉と異なる意味を持つ場合が多い。特に「石」とか。「詫び石ありがたい」とかネガポジに掛けると謝ってるのか感謝しているのか意味不明。
- 結局ソース(元となるデータ)の品質が高くないとどうにもならない。ニュース記事の分析なら問題無いが、独特の言い回し・単語が入る文章の分析には前準備の重要性が非常に高くなる。
NG集
- 最初はFF14+steamだった→元のレビュー件数が少な過ぎてNG。特定のレビューの単語が拾われてしまい、どの手法を使っても評価が偏る。件数の多い公式フォーラムはAPIなど収集に使えるものが無く、FF14のレビューは断念。
- FGOは専門用語が多過ぎてNG。サービス開始から時間が経ち過ぎていてFGO独自の用語が多すぎる。ユーザー辞書を作成し始めたら終わりが見えなくなり、これも断念。





