If you build a Shopify app, your competitors are a few clicks away on the same App Store page as you. You can see when one of them drops a price, adds a free plan, picks up the Built for Shopify badge or starts collecting one-star reviews about a broken feature. The problem is that nobody checks ten listings and three category pages by hand every morning for long, and a single look tells you little. What you want is the change: who moved up, who changed their plans, and what merchants said since yesterday.
App developers, product managers, agencies comparing apps for clients, and support teams watching their own reviews all have this problem. This tutorial shows how to collect that change every day with the Shopify App Store Scraper: Apps, Rankings and Reviews Actor on the Apify Store.
Where the data comes from
The Actor reads public pages on apps.shopify.com without logging in. It checks every link against the site's robots.txt before requesting it, waits at least one second between requests and backs off when the site asks it to slow down. It returns three record types into one dataset:
- App records: name, developer, rating, review count, reviews per star level, pricing plans with prices and features, categories, launch date, languages, integrations ("Works with") and the Built for Shopify badge.
- Ranking records: every app in a category in the order the App Store lists it, with position, rating, review count, pricing summary and badge, plus the position change since your previous run with the same input.
- Review records: date, star rating, review text, the reviewing store's name and country as published, time using the app, and the developer's reply.
Reviewers are identified only by the store name the App Store shows. The Actor returns no personal names, emails or profile links.
The Actor is independent and is not affiliated with, endorsed by or sponsored by Shopify. "Shopify" is a trademark of Shopify Inc.
Step 1: Run it once in the Apify Console
Open the Store page (link at the end) and click Start with the example input. You get two app records, the 20 newest reviews of each, and the top 24 apps in the Product reviews category, in about 10 seconds.
Then fill in your own:
-
Apps: a handle such as
judgemeor a full link such ashttps://apps.shopify.com/klaviyo-email-marketing. The handle is the part of the link afterapps.shopify.com/. -
Categories to rank: a category handle or link. Use subcategories such as
marketing-and-conversion-social-trust-product-reviews. Top-level categories likemarketing-and-conversiononly show curated sections and have no ranked list, and the Actor tells you which subcategories to use instead. -
Keywords (filter rankings): keep only ranked apps whose name, tagline or handle contains a word, for example
loyalty. Positions stay the real positions in the category. - Maximum reviews per app (10 per page) and Maximum ranked apps per category (24 per page).
- Only these star ratings: for example 1 and 2 to collect complaints.
- Maximum records: a total cap across all three record types.
Step 2: Schedule it daily with onlyNew
- Switch on Only new items since the previous run (
onlyNew). The Actor remembers what it returned for this exact input in a key-value store in your account (shopify-app-store-feed-stateby default). Later runs return only reviews it has not seen and apps that newly entered the tracked part of each ranking. App records are always returned, so you can compare ratings and pricing day to day. - Save the input as a task. In Schedules, create a schedule (for example cron
0 7 * * *in your timezone) and add the task. - Connect the dataset to email, Slack, Google Sheets or your own system with an Apify integration or webhook.
The first run returns everything up to your caps. Changing the apps, categories, keywords or review filters starts a fresh history, so settle the lists before you rely on the trend.
One detail matters for rankings. With onlyNew on, apps that were already in the ranking are filtered out, so you only see newcomers. If you want every app's positionChange each day, run a second task with only the category listed and onlyNew off. Keep its app list empty so it has its own history.
Step 3: A daily competitor report in Python
The official apify-client package runs the Actor and reads the results. Install it with pip install apify-client and copy your API token from the Integrations section of the Console.
This script checks three competitors, prints their rating and cheapest paid plan, lists new low-star reviews and shows apps that entered the top 48 of the category.
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run_input = {
"apps": ["judgeme", "loox", "okendo-reviews"],
"categories": ["marketing-and-conversion-social-trust-product-reviews"],
"maxReviewsPerApp": 50,
"maxAppsPerCategory": 48,
"onlyNew": True,
}
run = client.actor("agents001/shopify-app-store-feed").call(
run_input=run_input,
max_total_charge_usd=2,
)
if run is None:
raise SystemExit("Run failed to start or finish")
apps, reviews, rankings = [], [], []
for item in client.dataset(run.default_dataset_id).iterate_items():
{"app": apps, "review": reviews, "ranking": rankings}[item["recordType"]].append(item)
print("Competitors")
for a in apps:
paid = [p for p in a["pricingPlans"] if p.get("priceAmount")]
cheapest = min(paid, key=lambda p: p["priceAmount"])["priceText"] if paid else "none"
print(f" {a['name']}: {a['rating']} stars, {a['reviewCount']} reviews, "
f"cheapest paid plan {cheapest}, Built for Shopify: {a['builtForShopify']}")
print(f"\n{len(reviews)} new reviews")
for r in reviews:
if r["rating"] <= 2:
print(f" {r['appName']} {r['rating']}* {r['storeName']} ({r['storeCountry']}): "
f"{r['reviewText'] or '[no text]'}")
print("\nNew in the top 48")
for r in rankings:
print(f" #{r['position']} {r['appName']} ({r['rating']} stars, {r['reviewCount']} reviews)")
To track movement for every ranked app, run the category on its own with onlyNew off and sort by positionChange, which is positive when an app moved up:
run = client.actor("agents001/shopify-app-store-feed").call(
run_input={
"apps": [],
"categories": ["marketing-and-conversion-social-trust-product-reviews"],
"maxAppsPerCategory": 96,
},
max_total_charge_usd=1,
)
if run is None:
raise SystemExit("Run failed to start or finish")
rows = list(client.dataset(run.default_dataset_id).iterate_items())
movers = [r for r in rows if r.get("positionChange")]
for r in sorted(movers, key=lambda r: abs(r["positionChange"]), reverse=True)[:10]:
print(f"{r['appName']}: #{r['previousPosition']} -> #{r['position']}")
previousPosition and positionChange are empty on the first run of a given input. They fill in from the second run on.
What the output looks like
An app record, trimmed:
{
"recordType": "app",
"appHandle": "judgeme",
"name": "Judge.me Product Reviews App",
"developerName": "Judge.me",
"rating": 5.0,
"reviewCount": 48932,
"ratingDistribution": {"5": 47857, "4": 810, "3": 74, "2": 36, "1": 155},
"pricingSummary": "Free plan available. Free trial available.",
"pricingPlans": [
{"name": "Forever Free", "priceText": "Free", "priceAmount": 0.0},
{"name": "Awesome", "priceText": "$15/month", "priceAmount": 15.0,
"currency": "USD", "billingPeriod": "month", "trial": "15-day free trial"}
],
"launchedDate": "2015-06-25",
"builtForShopify": true
}
A ranking record, trimmed:
{
"recordType": "ranking",
"categoryName": "Product review apps",
"totalAppsInCategory": 486,
"position": 2,
"appName": "Entangle Google Reviews",
"rating": 5.0,
"reviewCount": 601,
"pricingSummary": "Free plan available",
"builtForShopify": true,
"previousPosition": null,
"positionChange": null,
"isNew": true
}
A review record, trimmed:
{
"recordType": "review",
"appName": "Judge.me Product Reviews App",
"rating": 5,
"reviewDate": "2026-10-22",
"reviewText": "I asked the Judge.me support team to help display star ratings on my collection pages, and it's now working perfectly. The support agent was helpful and replied quickly. Great support, thank you!",
"storeName": "ODKcurtains",
"storeCountry": "Hong Kong SAR",
"usageDuration": "6 months using the app",
"developerReply": null,
"isNew": true
}
What it costs
The Actor uses pay-per-event pricing, with platform usage included:
- $2.50 per 1,000 app records
- $2 per 1,000 review records
- $1.50 per 1,000 ranking records
Take the two daily tasks above. The competitor task returns 3 app records every day ($0.0075) plus whatever new reviews appear. If the three apps get 30 new reviews a day between them, that adds $0.06. The ranking task returns 96 ranking records a day ($0.144). Together that is about $0.21 a day, or about $6.40 over 30 days.
The first run of the competitor task returns up to 50 reviews per app, so up to 150 reviews ($0.30). You can set a maximum charge per run, and the Actor stops cleanly when it is reached and keeps what it returned so far. Maximum records is a second cap. Check the Store page for current pricing.
What to do with the data
-
Competitor tracking: store the daily app records and diff
pricingPlans,rating,reviewCount,worksWithandbuiltForShopifyto see when a competitor changes plans, adds an integration or earns the badge. -
Review monitoring: set Only these star ratings to 1 and 2 on a competitor's app to collect complaints, which often point at features merchants want. Run it on your own app to catch bad reviews and check that each one has a
developerReply. -
Category ranking changes: post to Slack when an app enters the top 24 of your category or moves more than a few places in a day, and chart
positionover time in Google Sheets.
Limitations
- There is no keyword search of the App Store. The site's robots.txt asks automated clients not to use the search page, so the Actor does not. Keywords filter category rankings only.
- Star-only reviews have no text.
reviewTextis empty for them. - Rankings are the App Store's default order at the time of the run, and the order can differ slightly between visitors. Treat small moves as noise.
- Speed is about one page per second. Reading every review of an app with tens of thousands of reviews takes hours, so keep Maximum reviews per app modest and use
onlyNewfor ongoing tracking. - Prices are as published, which the App Store states in USD.
- The App Store changes its layout from time to time. If fields that should have values come back empty, report it in the Actor's Issues tab.
Top comments (0)