Анализ отзывов нейросетью: от CSV к выводам на Python
Загружаете CSV с отзывами, дешёвая модель размечает каждый, pandas считает статистику, второй запрос пишет отчёт. Полный цикл от данных к выводам.
3 мин · · разработка · бизнес
Читать сотни отзывов вручную долго, а понять нужно главное: что нравится клиентам, что раздражает и какие проблемы повторяются. Ниже — полный цикл анализа отзывов через API: дешёвая модель размечает каждый отзыв (тональность, темы, цитата), pandas считает статистику, а итоговый отчёт пишет модель подороже по уже агрегированным данным.
Общая схема
Двухэтапный подход экономит деньги:
- Разметка (дешёвая модель): для каждого отзыва — тональность (positive/neutral/negative), темы из фиксированного списка, главная мысль и характерная цитата.
- Отчёт (модель подороже): один запрос со статистикой и примерами цитат, на выходе — выводы и рекомендации.
Основной объём токенов приходится на первый этап, поэтому там берём glm-5.3-flash ($0,04/$0,04 за 1M токенов). Отчёт — один запрос, его можно отдать claude-sonnet-5 ($0,30/$0,30), разница в цене копеечная.
Шаг 1: загрузка CSV
import csv
import json
from collections import Counter
import pandas as pd
from openai import OpenAI
client = OpenAI(
base_url="https://api.zukko.pro/v1",
api_key="fetch_ваш_ключ",
)
def load_reviews(csv_path: str) -> list[dict]:
"""Ожидаемые колонки: id, review_text, rating, date"""Пример CSV:
id,review_text,rating,date
1,"Отличный товар, пришёл быстро",5,2024-01-15
2,"Ужасное качество, швы разошлись через неделю",1,2024-01-16
3,"Нормально, но дорого",3,2024-01-17
Шаг 2: разметка каждого отзыва
Темы задаём фиксированным списком — иначе модель будет называть одно и то же по-разному («доставка», «курьер», «логистика»), и статистика развалится.
TOPICS = ["качество", "цена", "доставка", "упаковка", "размер", "обслуживание", "описание товара", "другое"]
ANALYSIS_PROMPT = f"""Проанализируй отзыв покупателя.
Верни ТОЛЬКО JSON без markdown:
{{"sentiment": "positive|neutral|negative", "score": 0.5, "topics": ["..."], "main_issue": "...", "key_quote": "..."}}
Правила:
- score — тональность от -1 (резко негативно) до 1 (восторженно)
- topics — 1–3 темы только из списка: {', '.join(TOPICS)}
- main_issue — одно предложение: главная проблема или главное достоинство
- key_quote — точная цитата из отзыва, не длиннее 15 слов
"""
def parse_json(text: str):Отзывы со статусом unknown (модель вернула не JSON) стоит пересчитать повторно или посмотреть глазами.
Шаг 3: агрегация в pandas
def aggregate(analyzed: list[dict]) -> dict:
df = pd.DataFrame(analyzed)
sentiment = df["sentiment"].value_counts()
topics = Counter(t for ts in df["topics"] for t in ts)
negative = df[df["sentiment"] == "negative"].sort_values("score")
positive = df[df["sentiment"] == "positive"].sort_values("score", ascending=False)
df.to_csv("analyzed_reviews.csv", index=False)
return {
"total": len(df),
"sentiment": sentiment.to_dict(),
"top_topics": topics.most_common(5),
"negative_quotes": negative["key_quote"].head(5).tolist(),
"positive_quotes": positive["key_quote"].head(5).tolist(),
"negative_by_topic": Counter(t for ts in negative["topics"] for t in ts).most_common(5),
}Полезный срез — темы именно в негативных отзывах (negative_by_topic): он показывает, что чаще всего вызывает недовольство, а не просто о чём чаще пишут.
Шаг 4: итоговый отчёт
В модель уходит не весь массив отзывов, а только статистика и несколько цитат — это короткий запрос:
def generate_report(stats: dict, model: str = "claude-sonnet-5") -> str:
total = stats["total"] or 1
lines = [f"- {k}: {v} ({v / total:.0%})" for k, v in stats["sentiment"].items()]
prompt = f"""Ниже статистика по {stats['total']} отзывам покупателей.
Тональность:
{chr(10).join(lines)}
Частые темы: {stats['top_topics']}
Темы в негативных отзывах: {stats['negative_by_topic']}
Цитаты из негативных отзывов:
{chr(10).join('- ' + q for q in stats['negative_quotes'])}
Полный запуск
def run_pipeline(csv_path: str, report_path: str = "review_report.txt"):
reviews = load_reviews(csv_path)
print(f"Загружено {len(reviews)} отзывов")
stats = aggregate(analyze_all(reviews))
report = generate_report(stats)
with open(report_path, "w", encoding="utf-8") as f:
f.write(report)
print(f"Отчёт сохранён в {report_path}")
return stats, report
if __name__ == "__main__":
run_pipeline("reviews.csv")Для тысяч отзывов запросы первого этапа удобно отправлять параллельно через AsyncOpenAI с ограничением числа одновременных запросов и повтором при ошибке 429.
Стоимость
Расчёт на 1000 отзывов:
Этап 1: glm-5.3-flash ($0.04/$0.04 за 1M)
~300 входных и ~100 выходных токенов на отзыв
(1000 × 300 × 0.04 / 1M) + (1000 × 100 × 0.04 / 1M) = $0.012 + $0.004 = $0.016
Этап 2: claude-sonnet-5 ($0.30/$0.30 за 1M), один запрос
~2000 входных и ~800 выходных токенов
(2000 × 0.30 / 1M) + (800 × 0.30 / 1M) = $0.0006 + $0.00024 ≈ $0.0008
Итого: ≈ $0.017 на 1000 отзывов
Если разметку делать на claude-sonnet-5, первый этап обойдётся в 1000 × 400 × 0.30 / 1M = $0.12. Стоит ли разница того, покажет сверка на выборке (см. последний вопрос ниже).
Актуальный список моделей и цен: GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете.
Настройка ключа
Создайте ключ в кабинете Zukko и разрешите ему обе модели — glm-5.3-flash и claude-sonnet-5. Месячного лимита в $1 хватит на десятки тысяч отзывов по расчёту выше. См. также быстрый старт и примеры на Python.
Частые вопросы
Что если отзывы на разных языках?
Добавьте в промпт: «отзыв может быть на любом языке; темы выбирай из списка на русском, цитату приводи в оригинале». Проверьте качество разметки на нескольких отзывах каждого языка.
Как часто обновлять отчёт?
Зависит от потока. При десятках отзывов в день удобно собирать отчёт раз в неделю по новым отзывам и сравнивать с предыдущей неделей.
Можно ли разделить отчёт по категориям товаров?
Да. Добавьте колонку категории в CSV, сгруппируйте размеченные отзывы в pandas и вызовите generate_report для каждой группы отдельно.
Что если модель ошибается в разметке?
Проверьте вручную случайную выборку из 50–100 отзывов. Если ошибки систематические, уточните промпт (например, определение тем) или сравните на той же выборке другую модель.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Промпт-инъекция: что это и как защитить бота и агентаОбъясняем, как работает промпт-инъекция через пользовательский ввод и чужие документы, и какие меры снижают ущерб: изоляция данных, права инструментов, лимиты ключа.
- Свой прокси к нейросети на Node.js: не показывать API-ключ во фронтендеНебольшой сервер на Express между вашим фронтендом и API нейросети: ключ остаётся на сервере, а модели, длина запроса и частота ограничены.
- DeepSeek API на Python: запросы, стриминг, JSON и обработка ошибокГотовый код для DeepSeek V4.1 Flash и V4 Pro на OpenAI SDK: запрос, стриминг, разбор JSON и аккуратная обработка ошибок.
- Промпт-инжиниринг: примеры и 12 готовых шаблонов для работыДвенадцать шаблонов промптов для рабочих задач: что в них написано, почему именно так и сколько стоит один вызов по API.