If you build Shopify apps — or research the market — the reviews are the most valuable data on each listing: the star rating, the actual complaint text, which country the merchant is in, how long they used the app, and whether the developer replied. But there's no official Shopify App Store API, and the review HTML is a moving target. This post shows how to pull reviews for any app — or a whole set of competitors — and export them straight to CSV for analysis, in a few lines of Python. No API key for Shopify, no login, no headless browser.
We'll use the hosted Shopify App Store Scraper actor on Apify, which handles pagination and the brittle parsing for you. If you'd rather not write any code at all, there's a one-click Shopify App Reviews Export example that returns the same data as JSON/CSV straight from the browser.
What we'll build
- Export one app's reviews to a CSV.
- Pull reviews for several competing apps in a single run.
- A quick analysis: rating distribution, developer-reply rate, and the words that show up most in 1–2★ reviews.
Prerequisites
- Python 3.8+
- The client and pandas:
pip install apify-client pandas - A free Apify token (Console → Settings → Integrations), kept out of source control in an env var:
export APIFY_TOKEN="apify_api_xxx"
1. Pull reviews for one app
The review handle is the slug at the end of a listing URL — https://apps.shopify.com/klaviyo-email-marketing → klaviyo-email-marketing. Set mode to reviews, pass one or more handles in appHandles, and cap how many reviews per app:
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
"mode": "reviews",
"appHandles": ["klaviyo-email-marketing"],
"maxReviewsPerApp": 200,
}
run = client.actor("freshactors/shopify-app-store-scraper").call(run_input=run_input)
reviews = list(client.dataset(run["defaultDatasetId"]).iterate_items())
print(f"pulled {len(reviews)} reviews")
print(reviews[0])
Each review record looks like this:
{
"handle": "klaviyo-email-marketing",
"reviewId": "123456789",
"rating": 5,
"storeName": "Acme Coffee Co.",
"country": "United States",
"usedAppFor": "About 2 years using the app",
"date": "June 12, 2026",
"body": "Great app — support helped us migrate our flows in a day.",
"developerReply": "Thanks so much for the kind words! ...",
"developerReplyDate": "June 13, 2026"
}
One thing worth knowing up front: some reviews are rating-only (a star with no text), so body is legitimately empty on those — that's real data, not a scrape failure. The actor also retries transparently when Shopify throttles a page, so you get the complete set instead of a silent partial one.
2. Export to CSV
pandas makes this a one-liner (the built-in csv module works too if you'd rather skip the dependency):
import pandas as pd
df = pd.DataFrame(reviews)
cols = ["date", "rating", "country", "usedAppFor", "storeName",
"body", "developerReply", "developerReplyDate"]
df[cols].to_csv("klaviyo_reviews.csv", index=False)
Open that in Excel or Google Sheets and every review is a row.
3. Several competitors in one run
reviews mode takes a list, so pass a whole competitive set at once — the actor fetches each app and tags every record with its handle so you can group them afterward:
run_input = {
"mode": "reviews",
"appHandles": [
"klaviyo-email-marketing",
"privy",
"omnisend",
],
"maxReviewsPerApp": 300,
}
run = client.actor("freshactors/shopify-app-store-scraper").call(run_input=run_input)
df = pd.DataFrame(client.dataset(run["defaultDatasetId"]).iterate_items())
df.to_csv("email_apps_reviews.csv", index=False)
4. A five-minute analysis
Now that it's a DataFrame, a couple of aggregations say a lot about how each competitor is doing:
# average rating + review volume per app
print(df.groupby("handle")["rating"].agg(["mean", "count"]))
print(df.groupby("handle")["rating"].agg(["mean", "count"]))
# developer responsiveness: share of reviews they replied to
df["has_reply"] = df["developerReply"].notna()
print(df.groupby("handle")["has_reply"].mean().round(2))
# what unhappy merchants complain about: top words in 1–2★ reviews
from collections import Counter
import re
low = df[df["rating"] <= 2]["body"].dropna()
words = re.findall(r"[a-z]{4,}", " ".join(low).lower())
stop = {"this", "that", "with", "have", "they", "your", "would", "were",
"when", "from", "them", "just", "been", "very", "will", "which"}
common = Counter(w for w in words if w not in stop).most_common(15)
print(common)
That last snippet is a cheap, surprisingly effective product-gap map: when words like support, billing, sync, or charges bubble to the top of a competitor's 1★ reviews, you're looking at exactly where merchants are churning.
Pricing & why "hosted" matters
The actor is Pay-Per-Event: $0.0001 per review with no start or subscription fee, so a 300-review pull runs about $0.03. It's pure HTTP/JSON — no headless browser to babysit — and it's monitored every day with a public "last verified working" badge, because the entire point of a hosted scraper is that it keeps working when the site's markup shifts underneath it.
Try it
- Actor: Shopify App Store Scraper on Apify
- One-click, no code: Shopify App Reviews Export
Have a Shopify-reviews question or hit an edge case? Drop it in the comments — happy to help.
Top comments (0)