活用シーンとソリューション

AI でレビュー分析・口コミ分析:ローカル LLM で感情・評価観点・主な不満を商品別に集計

6 ステップ 15 分で読めます 56 回閲覧 0
目次

商品には Shopee、Lazada、Amazon、TikTok Shop、自社ストアなどで数百件ものレビューが集まり、複数の言語が混在することも少なくありません。星評価を見れば何かが問題になっていることはわかっても、何が問題なのかまではわかりません。この記事では、ローカルの言語モデルですべてのレビューを読み込み、全体的な感情、レビューが評価または批判している商品の観点(アスペクト)、言及されている具体的な問題を記録します。さらに商品ごとのレポートで、最も批判の多い観点、星評価と本文が一致しないレビュー、主な問題と改善案をまとめた AI の要約を確認できます。すべて自社サーバー上で動作するため、顧客の文章が第三者に送信されることはありません。

レビューの収集は、セラーセンターからのエクスポート、公式 API、自社ショップのデータベースなど、プラットフォームが認める方法で行ってください。マーケットプレイスのスクレイピングは利用規約に違反するおそれがあります。分析の前に、不要な個人データは削除してください。

ステップ1:ツールをインストール

apt install -y python3-venv
python3 -m venv /opt/reviews
/opt/reviews/bin/pip install ollama numpy scikit-learn
ollama pull qwen2.5:7b

Ollama のセットアップは Ollama でローカル LLM を動かす方法で解説しています。numpy と scikit-learn はこの記事では使いませんが、関連記事のトピック分析で使用します。

ステップ2:レビューを用意

レビューを UTF-8 の CSV ファイルにエクスポートします。id 列と text 列は必須です。sku、rating(1~5)、date は任意ですが、あるとレポートがより役立つものになります。

id,sku,rating,date,text
R1001,LS-01,2,2026-09-14,"The stand wobbles when I type, screws came loose after a week."
R1002,LS-01,5,2026-09-15,"Sturdy, looks great on my desk, easy to fold."
R1003,BT-07,1,2026-09-15,"左耳机三天就没声音了,客服也没回复。"

ステップ3:分析スクリプトを作成

cat > /opt/reviews/reviews.py <<'EOF2'
import csv, json, os, sys
from collections import Counter, defaultdict
import ollama

MODEL = os.environ.get("MODEL", "qwen2.5:7b")
LANGUAGE = os.environ.get("REPORT_LANG", "English")       # language of issues, summaries and the report
ASPECTS = ["quality", "size_fit", "function", "durability", "ease_of_use",
           "delivery", "packaging", "price_value", "customer_service", "other"]
SENT = {"type": "string", "enum": ["positive", "neutral", "negative"]}
SCHEMA = {
    "type": "object",
    "properties": {
        "sentiment": SENT,
        "aspects": {"type": "array", "items": {"type": "object", "properties": {
            "aspect": {"type": "string", "enum": ASPECTS}, "sentiment": SENT}, "required": ["aspect", "sentiment"]}},
        "issues": {"type": "array", "items": {"type": "string"}},
        "summary": {"type": "string"},
    },
    "required": ["sentiment", "aspects", "issues", "summary"],
}
SYSTEM = ("You analyse customer reviews for an online shop. The review can be in any language. "
          "Give the overall sentiment, and the sentiment of each product aspect the review actually mentions. "
          f"List concrete problems as short phrases in {LANGUAGE} (empty list if there are none) "
          f"and write a one-sentence summary in {LANGUAGE}. Do not invent anything that is not in the review.")

def ask(system, text, schema=None):
    r = ollama.chat(model=MODEL, format=schema, options={"temperature": 0}, messages=[
        {"role": "system", "content": system}, {"role": "user", "content": text}])
    return r.message.content

src, out = sys.argv[1], "reviews_tagged.csv"
done = {}
if os.path.exists(out):
    with open(out, encoding="utf-8-sig") as f:
        done = {r["id"]: r for r in csv.DictReader(f)}
with open(src, encoding="utf-8-sig") as f:
    reviews = [r for r in csv.DictReader(f) if r.get("text", "").strip()]

fields = ["id", "sku", "rating", "sentiment", "aspects", "issues", "summary", "text"]
new_file = not os.path.exists(out)
with open(out, "a", newline="", encoding="utf-8-sig") as f:
    w = csv.DictWriter(f, fieldnames=fields)
    if new_file:
        w.writeheader()
    for r in reviews:
        if r["id"] in done:
            continue
        d = json.loads(ask(SYSTEM, r["text"][:4000], SCHEMA))
        row = {"id": r["id"], "sku": r.get("sku", ""), "rating": r.get("rating", ""), "sentiment": d["sentiment"],
               "aspects": "; ".join(f"{a['aspect']}:{a['sentiment']}" for a in d["aspects"]),
               "issues": "; ".join(i.strip() for i in d["issues"] if i.strip()), "summary": d["summary"], "text": r["text"]}
        w.writerow(row)
        f.flush()
        done[r["id"]] = row
        print(r["id"], d["sentiment"], flush=True)

by_sku = defaultdict(list)
for row in done.values():
    by_sku[row["sku"] or "-"].append(row)
with open("report.md", "w", encoding="utf-8") as f:
    for sku, rows in sorted(by_sku.items(), key=lambda x: -len(x[1])):
        sent = Counter(r["sentiment"] for r in rows)
        neg = Counter(a.split(":")[0] for r in rows for a in r["aspects"].split("; ") if a.endswith(":negative"))
        issues = [i for r in rows for i in r["issues"].split("; ") if i]
        odd = [r["id"] for r in rows if r["rating"].isdigit() and ((int(r["rating"]) >= 4 and r["sentiment"] == "negative")
               or (int(r["rating"]) <= 2 and r["sentiment"] == "positive"))]
        f.write(f"## {sku}: {len(rows)} reviews\n\n")
        f.write(f"- Positive {sent['positive']}, neutral {sent['neutral']}, negative {sent['negative']}\n")
        f.write("- Most criticised aspects: " + (", ".join(f"{a} ({n})" for a, n in neg.most_common(5)) or "none") + "\n")
        f.write("- Rating does not match the text: " + (", ".join(odd) or "none") + "\n\n")
        if issues:
            f.write(ask(f"You are a product manager. Group these customer complaints into the main problems, "
                        f"most frequent first, and suggest one concrete fix for each. Answer in {LANGUAGE}, briefly.",
                        "\n".join(issues[:300])) + "\n\n")
        print("report:", sku, flush=True)
print("done: reviews_tagged.csv, report.md")
EOF2

JSON スキーマにより、モデルは決まった構造で回答します。3 種類の感情のいずれか、指定したリストに含まれる観点、そして問題点のリストです。処理済みのレビューはすぐに保存され、2 回目の実行ではスキップされるため、いつでも中断・再開できます。後から同じ CSV に新しいレビューを追加することも可能です。

ステップ4:分析を実行

cd /data/reviews
REPORT_LANG="Simplified Chinese" nohup /opt/reviews/bin/python /opt/reviews/reviews.py reviews.csv > reviews.log 2>&1 &
tail -f reviews.log

CPU サーバーの場合、7B モデルの処理時間はレビューの長さに応じて 1 件あたり数秒から数十秒です。大量のエクスポートは夜間に実行するか、MODEL=qwen2.5:3b を指定して qwen2.5:3b などの小さいモデルを使ってください。結果は reviews_tagged.csv(レビュー 1 件につき 1 行)と report.md(商品ごとに 1 セクション)に出力されます。

ステップ5:結果を読み解く

  • 最も批判の多い観点は、商品のどこが期待外れなのかを示します。耐久性、サイズ・フィット感、配送、梱包の問題は、それぞれ対策も担当チームも異なります。
  • 評価と本文の不一致には、例えば不満が書かれている星 5 つのレビュー(多くは配送に関するもの)や、実際には好意的な星 1 つのレビューが一覧表示されます。どちらも目を通す価値があります。
  • AI の要約は、不満を主な問題ごとにまとめ、それぞれに改善案を示します。商品開発チームや仕入れ先に共有してください。
  • reviews_tagged.csv を商品とネガティブな感情で絞り込むと、各問題の元になったレビューの原文を読めます。

ASPECTS のリストは、商品カテゴリーに合わせて変更してください。例えば衣料品なら fabric、size_fit、colour、電子機器なら battery、connectivity、sound などです。

ステップ6:定期的に実施する

毎月新しいレビューをエクスポートして同じファイルでスクリプトを実行し、レポートを時系列で比較します。想定していなかった問題を見つけるには、埋め込みで顧客の話題を把握する方法の手順でレビューをトピックに分類してください。レビューへの返信を効率化するには、AI でレビュー返信文を下書きする方法を参照してください。顧客が最も高く評価している点は、商品ページの説明文に使える良い材料にもなります。

よくある質問

分析の精度はどのくらいですか?

内容が明確なレビューであれば、感情と観点の判定は信頼できます。皮肉、賛否が混在したレビュー、「ok」のような非常に短い文章は判定が難しくなります。数値を信頼する前に、ランダムに選んだ 20 件のレビューを結果と照合してください。また、回答を安定させるため temperature は 0 のままにしてください。

サポートチケットやチャットも同じ方法で分析できますか?

できます。各チケットや会話を text 列に入れ、観点を請求、配送、セットアップ、アカウントなどのサポートカテゴリーに置き換えてください。

代わりにクラウドの AI モデルを使えますか?

多くの AI API は構造化出力に対応していますが、その場合レビューはサーバーの外に送信されます。事前に、プライバシーに関する義務、プロバイダーの利用規約、サービス提供地域を確認してください。

上記の手順で解決しない場合は、サポートチケットを送信して IMIDC の 24 時間 365 日対応テクニカルサポートへご連絡ください。サーバー IP、使用しているモデル、個人データを除いた CSV の先頭数行、エラー出力を添えていただくと、より迅速に調査できます。

この回答はお役に立ちましたか?

関連チュートリアル