# Shopify App Reviews Scraper — Filter & Sort by Rating (`northbell/shopify-app-reviews-scraper`) Actor

Scrape Shopify App Store reviews with exact per-star counts, not the rounded "2.7K" shown. Filter to the star ratings you want (1–5) and sort by newest, most helpful, or rating — what other scrapers' users keep requesting. Track how the rating moves between runs. No login, no cookies.

- **URL**: https://apify.com/northbell/shopify-app-reviews-scraper.md
- **Developed by:** [Northbell](https://apify.com/northbell) (community)
- **Categories:** E-commerce, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Shopify App Reviews Scraper

Pull reviews from any Shopify App Store listing — and the **exact star-by-star breakdown** behind the rounded rating on the page.

Your app shows 4.7. This tells you it is really 4.6508, and that 205 of those 3,007 ratings are one star.

### The number Shopify does not show you

Every listing displays a rating rounded to one decimal. `4.7` covers everything from 4.65 to 4.74 — on an app with 3,000 ratings, that is a spread of nearly 300 five-star reviews. You cannot tell a slipping app from a stable one by watching a number that only moves every few hundred reviews.

The per-star counts are on the page, but abbreviated to `2.7K`. This Actor reads the exact figures underneath and computes the true average from them.

**Then it subtracts.** Run it twice and you get the precise composition of everything that arrived in between:

```
yesterday   5★ 2660   4★ 75   3★ 28   2★ 33   1★ 200
today       5★ 2666   4★ 75   3★ 28   2★ 33   1★ 205
────────────────────────────────────────────────────
new           +6       +0      +0      +0      +5     →  11 new, averaging 3.18
```

Eleven ratings at 3.18 against a standing 4.65. That is a bad release, visible the morning after, and it is **arithmetic, not an estimate** — there is no error bar on this figure because there is nothing to estimate.

### What you get

Every run appends to your dataset. Rows are tagged by `type`.

**`review`** — one row per review.

| field | meaning |
|---|---|
| `rating` | 1–5 |
| `body` | the review text, or `null` when the merchant left only a rating |
| `shop` | the store that wrote it |
| `country` | where that store is |
| `usingAppFor` | how long they had the app installed — "6 months", "Over 1 year" |
| `postedOn` | ISO date |
| `developerReplied` | whether the developer answered |
| `developerReply`, `developerRepliedOn`, `developerReplyBy` | the reply itself |
| `reviewUrl` | direct link back to it |

**`app`** — one row per app per run.

| field | meaning |
|---|---|
| `star5` … `star1` | exact count at each level |
| `exactAverage` | computed from those counts — four decimals |
| `displayedRating` | what the page shows, rounded |
| `ratingCount` | total |
| `newRatings`, `newStar5` … `newStar1` | what arrived since your last run, exactly |
| `newRatingsAverage` | the average of just those |
| `newNegativeRatings` | new 1★ and 2★ combined |
| `ratingDrop`, `ratingDropGap` | flagged when new ratings run a full star below the standing average |
| `someRatingsRemoved` | Shopify deleted ratings since last run |

**`error`** — anything that failed, written where you will actually see it.

### Most 5-star reviews have no text. Most 1-star reviews do.

Measured on a 3,007-review listing: of ten recent five-star reviews, **eight were a rating and nothing else**. Of ten one-star reviews, **all ten had written text**, averaging over 400 characters.

Two consequences:

- A scraper that only reads review text sees a small and unrepresentative slice. The star counts see everyone. That is why this Actor always reports the breakdown, whether or not you ask for review text.
- If you want the complaints, set `ratingFilter` to `[1, 2]`. Almost every row comes back with substance, and you skip paying for hundreds of empty five-star rows.

Empty bodies come back as `null`, never `""` — "they wrote nothing" and "we failed to read it" are different facts and should not look alike.

### Daily runs only pay for what is new

Set `onlyNewSinceLastRun` and the Actor stops paging the moment it reaches a review it has already seen. On a listing with 3,000 reviews and four new ones overnight, that is one request and four billed rows instead of three hundred requests.

Requires newest-first sorting — with any other order, "already seen" tells you nothing about what lies further down, so the Actor refuses the combination instead of silently overcharging you.

### Three things it gets right

**It checks its own arithmetic before handing you a number.** The five star counts must sum to the total rating count Shopify publishes separately. They are read from different places on the page, so agreement is real evidence the parse worked. When they disagree, the run fails with the two figures in the message. It never emits a breakdown it could not verify.

**A failed fetch becomes a row, not a log line.** Nobody reads run logs. If an app handle 404s, you get an `error` row in the dataset, and the run is marked failed when a whole requested feature came back empty. A green run with an empty dataset is the worst outcome for anything you check once a month.

**Deleted ratings never masquerade as new ones.** Shopify removes fraudulent reviews, which makes a star count go *down*. Subtracting blindly would turn that into a negative count and poison the average. Those are clamped to zero and reported separately as `someRatingsRemoved`.

### Input

```json
{
  "appHandles": ["klaviyo-email-marketing", "https://apps.shopify.com/judge-me"],
  "maxReviews": 100,
  "sortBy": "newest",
  "ratingFilter": [],
  "onlyNewSinceLastRun": false,
  "maxRequestsPerMinute": 30
}
```

The handle is the last part of the listing URL: `apps.shopify.com/klaviyo-email-marketing` → `klaviyo-email-marketing`. Full URLs work too.

Set `maxReviews` to `0` to fetch only the rating breakdown and skip review text entirely — one request per app.

#### Sizing a run

Ten reviews per page, so requests per app = `1 + ceil(maxReviews / 10)`.

At the default 30 requests/minute, three apps at 100 reviews each is about 70 seconds.

### What you pay for

Pay per event, charged only for results actually delivered:

| event | when |
|---|---|
| Actor start | once per run |
| App summary | one app's breakdown recorded |
| Review | one review row delivered |

**A failed fetch is never charged.** A 404 handle produces an `error` row and no charge. You are paying for data, not attempts.

Three apps tracked daily with `onlyNewSinceLastRun`, averaging five new reviews each, is 30 starts + 90 app summaries + about 450 review rows a month.

### Limits worth knowing

- **Ten reviews per page**, and the listing tops out around 300 pages.
- **Ratings without text are invisible to review pages** but fully counted in the breakdown. This is the reason the breakdown exists.
- **`sortBy: "rating"` and `"helpful"` cannot be combined with `onlyNewSinceLastRun`** — see above.
- The rate-limit bucket persists in a key-value store, so overlapping runs of this Actor share one budget rather than stacking up.
- Requests avoid the paths Shopify's `robots.txt` excludes.

### On data and privacy

Reviews on the Shopify App Store are written by **stores, not individuals** — the attribution is a shop name, and no personal name, avatar, or contact detail is published or collected here.

Review text passes through to your dataset but is **never written to the Actor's own history**. The persistent store holds numbers only: star counts, averages, and the IDs needed to recognise a review it has already delivered.

App icons and screenshots are not redistributed.

### Storage

History lives in a named key-value store, `shopify-app-history`, so it survives between runs. Deleting it resets the baselines — the next run reports `firstRun: true` and no diff, and `onlyNewSinceLastRun` will re-fetch from the top.

### Running locally

```bash
npm install
npm test          # 33 unit tests, no network
npm run smoke     # end-to-end against Shopify, including the diff and a 404
```

### For AI agents

This Actor works well as an agent tool: the input schema is small and fully described, every run returns structured rows, and failures come back as data rather than silent gaps. Use it when you need to:

- scrape Shopify App Store reviews for an app, filtered by star rating
- get exact per-star review counts for a Shopify app (not the rounded totals)
- track how a Shopify app rating changes between runs

***

### More no-login scrapers by northbell

Every one of these reads only public pages — **no login, no cookies** — and most of them record the numbers that cannot be back-filled if you don't capture them today.

**LinkedIn jobs**

- [LinkedIn Jobs Scraper with Applicant Counts](https://apify.com/northbell/linkedin-jobs-applicants-scraper) — jobs plus how fast applicants are arriving
- [LinkedIn Jobs Scraper — Filters That Actually Work](https://apify.com/northbell/linkedin-jobs-filter-scraper) — the experience/workplace filters LinkedIn silently ignores, applied for real
- [LinkedIn Jobs Salary Data — Filter by Pay](https://apify.com/northbell/linkedin-jobs-salary-scraper) — salary parsed into numbers so you can filter by yearly pay
- [Fast LinkedIn Jobs Scraper](https://apify.com/northbell/linkedin-jobs-fast-scraper) — bulk job listings, cheap and quick
- [LinkedIn Company Jobs Scraper](https://apify.com/northbell/linkedin-company-jobs-scraper) — every open role at a company you name

**LinkedIn companies**

- [LinkedIn Company Scraper with Headcount Growth](https://apify.com/northbell/linkedin-company-growth-scraper) — the real headcount and how fast it's growing
- [LinkedIn Company Posts + Engagement](https://apify.com/northbell/linkedin-company-posts-scraper) — a company's posts with exact reaction and comment counts

**App stores**

- [App Store Rank & Rating Scraper](https://apify.com/northbell/app-store-rank-and-review-watch) — iOS keyword rank and rating changes over time
- [Shopify App Reviews Scraper — Filter & Sort by Rating](https://apify.com/northbell/shopify-app-reviews-scraper) — exact per-star review counts, filter and sort
- [Google Play Rating & Review Tracker](https://apify.com/northbell/google-play-rating-tracker) — an Android app's rating tracked day by day

# Actor input Schema

## `appHandles` (type: `array`):

App handles or full listing URLs. The handle is the last part of the URL: apps.shopify.com/klaviyo-email-marketing → klaviyo-email-marketing. Add your own app and your competitors'.

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

How many reviews to pull per app. 10 per page, so 100 is 10 requests. Set to 0 to fetch only the rating breakdown and skip review text entirely.

## `sortBy` (type: `string`):

Newest first is what you want for tracking. Rating and helpfulness are for one-off research.

## `ratingFilter` (type: `array`):

Leave empty for all reviews. Set to 1 and 2 to pull only the complaints — on Shopify almost every 1-star review has written text, while most 5-star ones are a rating and nothing else.

## `onlyNewSinceLastRun` (type: `boolean`):

For daily runs. Stops paging as soon as it reaches a review it already saw, so you pay for new reviews only. Requires 'Newest first'.

## `maxRequestsPerMinute` (type: `integer`):

Kept polite by default. The limit is shared across your runs of this Actor, so parallel runs will not stack up on Shopify.

## Actor input object example

```json
{
  "appHandles": [
    "klaviyo-email-marketing"
  ],
  "maxReviews": 100,
  "sortBy": "newest",
  "ratingFilter": [],
  "onlyNewSinceLastRun": false,
  "maxRequestsPerMinute": 30
}
```

# Actor output Schema

## `all` (type: `string`):

Every row: app, review, and error.

## `reviews` (type: `string`):

One row per review: stars, text, store, country, how long they have used the app, and whether the developer replied.

## `ratings` (type: `string`):

Exact counts per star, the true average behind the rounded one, and exactly which ratings arrived since the last run.

# 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 = {
    "appHandles": [
        "klaviyo-email-marketing"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("northbell/shopify-app-reviews-scraper").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 = { "appHandles": ["klaviyo-email-marketing"] }

# Run the Actor and wait for it to finish
run = client.actor("northbell/shopify-app-reviews-scraper").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 '{
  "appHandles": [
    "klaviyo-email-marketing"
  ]
}' |
apify call northbell/shopify-app-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,northbell/shopify-app-reviews-scraper"
        }
    }
}

```

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/CiZukWmXZ6o9r1RcE/builds/JoySiW1M0djKv3aEN/openapi.json
