# Review Theme Analyzer: Praise and Complaint Themes (`delta_tools/review-theme-analyzer`) Actor

Turn any review dataset into ranked praise and complaint themes with counts, representative quotes, and per location or per product breakdowns. Works on the output of the big review scrapers. Deterministic local analysis, no AI API cost.

- **URL**: https://apify.com/delta_tools/review-theme-analyzer.md
- **Developed by:** [Delta Tools](https://apify.com/delta_tools) (community)
- **Categories:** Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 reviews

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Review Theme Analyzer

Your review scraper hands you rows. This Actor hands you the story inside them.

Point it at any review dataset, for example the output of a Google Maps, Amazon, TripAdvisor, or app store review scraper, or paste reviews directly. It reads every review and returns what customers actually praise and what they actually complain about, ranked, counted, and quoted.

### What you get

**One summary record** with the totals: reviews analyzed, positive and negative counts, average rating, and the single biggest praise theme and complaint theme.

**One record per theme** such as Food and drink, Service and staff, Speed and waiting, Cleanliness, Price and value, Quality and durability, and Delivery and packaging. Each theme record carries its review count, its share of all reviews, its praise or complaint bucket, its average score and rating, and up to three representative quotes taken word for word from your reviews. Frequent two word phrases from your own reviews also surface as themes, so product specific topics such as battery life appear even though they are on no fixed list.

**One breakdown record per group** when your records carry a group field such as a location, a store, or a product name. Each group gets its own counts, average rating, and top praise and complaint themes, so you can see which location struggles with waiting times and which one wins on service.

### Why it is different

Sentiment tools on the Store label one comment at a time, or scrape a single profile and label what they find. None of them accepts an arbitrary review dataset and returns ranked themes. This Actor is the downstream step the big review scrapers leave undone: synthesis you can act on, in structured records you can sort and chart.

### How the analysis works

The scoring is deterministic and local. A transparent word and phrase lexicon reads each review clause by clause, so a complaint about damaged packaging inside a happy review still lands on the complaint side, and each theme is judged by the words actually said about it. Star ratings anchor the overall positive and negative sorting when the dataset carries them, and negation is handled, so a review that says the room was not clean counts against Cleanliness rather than for it. The same input always produces the same output, and no review text ever leaves the run for an outside AI service.

### Input

**Dataset ID with reviews.** The output dataset of any review scraper. Or use **Inline reviews** and paste a JSON array of records, or plain text strings.

**Your Apify API token.** Only for dataset ID input. Apify runs cannot always read a dataset on their own: on accounts with restricted storage access the platform refuses the run, and your own token is the key that opens your dataset. It is a secret input, used once to read that one dataset, and never written to the output. Inline reviews never need it.

**Review text field.** The field that holds the review text. Common field names are found automatically when a record leaves it empty.

**Group field.** Optional. Names the field that groups reviews, such as location or product.

**Rating field** and **Max reviews to analyze.** Ratings anchor the sorting when present. The cap is also a spend cap, because you are charged only for reviews actually analyzed.

### Pricing

Pay per event: $0.001 Actor start + $0.002 per review analyzed. Records without usable review text are skipped and never charged, and an empty or failed input charges no review events at all. Analyzing 500 reviews costs about one dollar.

### Notes

Theme finding is lexicon based, so it is fast, repeatable, and honest about its method, but it will not catch sarcasm or themes expressed in unusual wording. Quotes are shortened to 240 characters. Ratings are read from the first field that carries a value from 1 to 5 among the rating field you name and the common rating field names.

# Actor input Schema

## `apifyApiToken` (type: `string`):

Your own Apify API token, needed only when you use a dataset ID and your account restricts storage access (the run then cannot read the dataset on its own). The token is a secret input, is used once to read that one dataset from api.apify.com, and is never stored in the output. Leave empty when you paste reviews inline.

## `datasetId` (type: `string`):

The Apify dataset that holds the reviews, for example the output dataset of a Google Maps or Amazon review scraper. Paste its dataset ID. Leave empty when you paste reviews below instead.

## `groupField` (type: `string`):

Optional record field that names a group, such as a location, a store, or a product name. When set, the output adds one breakdown record per group with its own counts and top themes. The sample input uses location.

## `inlineReviews` (type: `array`):

Reviews pasted directly as a JSON array. Each item is an object with a review text field, or a plain text string. The sample below analyzes in seconds and shows the full output shape.

## `maxReviews` (type: `integer`):

Maximum number of reviews to analyze in one run. You are charged only for reviews actually analyzed, so a cap is also a spend cap.

## `ratingField` (type: `string`):

Record field that holds a star rating from 1 to 5, when the dataset has one. Ratings anchor the praise and complaint sorting. Records without a rating are scored from their words alone.

## `textField` (type: `string`):

Record field that holds the review text. Common names such as review, comment, and feedback are also found automatically when this field is empty on a record.

## Actor input object example

```json
{
  "groupField": "location",
  "inlineReviews": [
    {
      "location": "Downtown",
      "rating": 5,
      "text": "The food was delicious and the coffee was fresh. Friendly staff and a cozy atmosphere. Prices felt fair for the quality."
    },
    {
      "location": "Downtown",
      "rating": 5,
      "text": "Great food and very friendly service. The wait was short and the room was spotless."
    },
    {
      "location": "Riverside",
      "rating": 1,
      "text": "Terrible service. We waited forever, the staff were rude, and the food arrived cold and bland."
    },
    {
      "location": "Riverside",
      "rating": 3,
      "text": "The food was good but the wait was slow and the music was too loud. Parking was difficult."
    },
    {
      "location": "Riverside",
      "rating": 5,
      "text": "Excellent quality and a beautiful, clean space. The staff were attentive and the dessert was amazing."
    },
    {
      "location": "Downtown",
      "rating": 2,
      "text": "Overpriced and disappointing. The portion was tiny, the bathroom was dirty, and the service was slow."
    }
  ],
  "maxReviews": 2000,
  "ratingField": "rating",
  "textField": "text"
}
```

# Actor output Schema

## `averageRating` (type: `string`):

Average star rating across the records that carry one, on summary, group, and theme records.

## `averageScore` (type: `string`):

Average sentiment score of the sentences that mention this theme, from minus 3 to plus 3.

## `complaintThemeCount` (type: `string`):

Number of complaint themes found, on the summary record.

## `error` (type: `string`):

Present only on an error record. Error records are never charged.

## `group` (type: `string`):

Group value, such as a location or a product name, on a group breakdown record.

## `groupField` (type: `string`):

The group field used by this run, on the summary record.

## `negativeMentions` (type: `string`):

Reviews mentioning this theme that read as negative.

## `negativeReviews` (type: `string`):

Reviews that read as negative overall.

## `neutralMentions` (type: `string`):

Reviews mentioning this theme that read as neutral overall.

## `neutralReviews` (type: `string`):

Reviews that read as neutral overall.

## `positiveMentions` (type: `string`):

Reviews mentioning this theme that read as positive.

## `positiveReviews` (type: `string`):

Reviews that read as positive overall.

## `praiseThemeCount` (type: `string`):

Number of praise themes found, on the summary record.

## `quote1` (type: `string`):

Most representative review quote for this theme.

## `quote2` (type: `string`):

Second representative review quote for this theme.

## `quote3` (type: `string`):

Third representative review quote for this theme.

## `recordType` (type: `string`):

The exact record kind: summary, theme, group-breakdown, or error.

## `reviewCount` (type: `string`):

Reviews counted in this theme or group record.

## `reviewsAnalyzed` (type: `string`):

Reviews actually analyzed and charged, on the summary record.

## `reviewsSkipped` (type: `string`):

Records skipped because they carried no usable review text. Skipped records are never charged.

## `runAt` (type: `string`):

UTC timestamp of the run.

## `sentiment` (type: `string`):

Theme bucket: praise, complaint, or mixed.

## `sharePercent` (type: `string`):

Share of analyzed reviews that mention this theme, in percent.

## `source` (type: `string`):

Where the reviews came from in this run: dataset, inline, or both.

## `textField` (type: `string`):

The review text field used by this run, on the summary record.

## `theme` (type: `string`):

Theme name, such as Food and drink or Speed and waiting. Frequent two word phrases from your own reviews also appear as themes.

## `topComplaintTheme` (type: `string`):

Largest complaint theme overall, or for this group.

## `topPraiseTheme` (type: `string`):

Largest praise theme overall, or for this group.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "groupField": "location",
    "inlineReviews": [
        {
            "location": "Downtown",
            "rating": 5,
            "text": "The food was delicious and the coffee was fresh. Friendly staff and a cozy atmosphere. Prices felt fair for the quality."
        },
        {
            "location": "Downtown",
            "rating": 5,
            "text": "Great food and very friendly service. The wait was short and the room was spotless."
        },
        {
            "location": "Riverside",
            "rating": 1,
            "text": "Terrible service. We waited forever, the staff were rude, and the food arrived cold and bland."
        },
        {
            "location": "Riverside",
            "rating": 3,
            "text": "The food was good but the wait was slow and the music was too loud. Parking was difficult."
        },
        {
            "location": "Riverside",
            "rating": 5,
            "text": "Excellent quality and a beautiful, clean space. The staff were attentive and the dessert was amazing."
        },
        {
            "location": "Downtown",
            "rating": 2,
            "text": "Overpriced and disappointing. The portion was tiny, the bathroom was dirty, and the service was slow."
        }
    ],
    "maxReviews": 2000,
    "ratingField": "rating",
    "textField": "text"
};

// Run the Actor and wait for it to finish
const run = await client.actor("delta_tools/review-theme-analyzer").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "groupField": "location",
    "inlineReviews": [
        {
            "location": "Downtown",
            "rating": 5,
            "text": "The food was delicious and the coffee was fresh. Friendly staff and a cozy atmosphere. Prices felt fair for the quality.",
        },
        {
            "location": "Downtown",
            "rating": 5,
            "text": "Great food and very friendly service. The wait was short and the room was spotless.",
        },
        {
            "location": "Riverside",
            "rating": 1,
            "text": "Terrible service. We waited forever, the staff were rude, and the food arrived cold and bland.",
        },
        {
            "location": "Riverside",
            "rating": 3,
            "text": "The food was good but the wait was slow and the music was too loud. Parking was difficult.",
        },
        {
            "location": "Riverside",
            "rating": 5,
            "text": "Excellent quality and a beautiful, clean space. The staff were attentive and the dessert was amazing.",
        },
        {
            "location": "Downtown",
            "rating": 2,
            "text": "Overpriced and disappointing. The portion was tiny, the bathroom was dirty, and the service was slow.",
        },
    ],
    "maxReviews": 2000,
    "ratingField": "rating",
    "textField": "text",
}

# Run the Actor and wait for it to finish
run = client.actor("delta_tools/review-theme-analyzer").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "groupField": "location",
  "inlineReviews": [
    {
      "location": "Downtown",
      "rating": 5,
      "text": "The food was delicious and the coffee was fresh. Friendly staff and a cozy atmosphere. Prices felt fair for the quality."
    },
    {
      "location": "Downtown",
      "rating": 5,
      "text": "Great food and very friendly service. The wait was short and the room was spotless."
    },
    {
      "location": "Riverside",
      "rating": 1,
      "text": "Terrible service. We waited forever, the staff were rude, and the food arrived cold and bland."
    },
    {
      "location": "Riverside",
      "rating": 3,
      "text": "The food was good but the wait was slow and the music was too loud. Parking was difficult."
    },
    {
      "location": "Riverside",
      "rating": 5,
      "text": "Excellent quality and a beautiful, clean space. The staff were attentive and the dessert was amazing."
    },
    {
      "location": "Downtown",
      "rating": 2,
      "text": "Overpriced and disappointing. The portion was tiny, the bathroom was dirty, and the service was slow."
    }
  ],
  "maxReviews": 2000,
  "ratingField": "rating",
  "textField": "text"
}' |
apify call delta_tools/review-theme-analyzer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,delta_tools/review-theme-analyzer"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/kydX3MnxHjVfW4sRl/builds/EhEy1FEO4PNmyFQJI/openapi.json
