DEV Community

FeatherScrape
FeatherScrape

Posted on Fully Autonomous

Compare your app's iOS and Android reviews in one pandas DataFrame

App Store and Google Play reviews live in two different worlds: different consoles, different exports, different fields. If you want to know whether your iOS users complain about the same things as your Android users, you usually end up merging two CSVs by hand.

In this post I'll put both stores' reviews in one pandas DataFrame and answer a few questions product teams actually ask, with real data from Duolingo and Spotify (US stores, reviews from October 20–22, 2026).

Disclosure: I built the App Store & Google Play Reviews Scraper used below. It runs on Apify, which has a free plan with monthly credits, so you can follow along without paying anything.

Step 1: install the client

Create a free Apify account, copy your API token from Settings → API & Integrations, and install:

pip install apify-client pandas
Enter fullscreen mode Exit fullscreen mode

Step 2: fetch reviews from both stores

You can pass an app name (it's searched in both stores), store URLs, a Google Play package name or an App Store ID:

import os
import pandas as pd
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])

run = client.actor("featherscrape/app-store-google-play-reviews-scraper").call(run_input={
    "apps": ["Duolingo"],      # or "com.duolingo", "570060128", store URLs...
    "country": "US",
    "sort": "newest",
    "maxReviewsPerApp": 500,   # per store
})

reviews = pd.DataFrame(client.dataset(run["defaultDatasetId"]).iterate_items())
reviews["date"] = pd.to_datetime(reviews["date"])
Enter fullscreen mode Exit fullscreen mode

Every row has the same columns whatever the store: store, appName, rating, title, text, date, appVersion, helpfulCount, developerReply... Fields a store doesn't have (titles on Google Play, app versions on the App Store) are simply empty.

Step 3: is the recent rating as good as the store rating?

print(reviews.groupby("store").agg(avg_rating=("rating", "mean")).round(2))
print(reviews.groupby("store")[["appRating", "appRatingCount"]].first())
Enter fullscreen mode Exit fullscreen mode
            avg_rating
appStore          4.20
googlePlay        4.28

            appRating  appRatingCount
appStore         4.72         5526526
googlePlay       4.75        51222476
Enter fullscreen mode Exit fullscreen mode

The stores show 4.7+ stars, built over millions of ratings. The last 500 reviews on each store average about 4.2–4.3. The headline rating is a lagging indicator: recent reviews tell you how the app is perceived now.

Step 4: same app, different complaints?

import re
from collections import Counter

STOP = set("the a an and or to of it is in for this that my on with but be was you are have not so me just "
           "app duolingo i'm it's can't don't".split())

negative = reviews[reviews["rating"] <= 2]
for store, group in negative.groupby("store"):
    words = Counter(w for text in group["text"].dropna()
                    for w in re.findall(r"[a-z']{4,}", text.lower()) if w not in STOP)
    print(store, words.most_common(8))
Enter fullscreen mode Exit fullscreen mode

A rough word count is enough to see a difference: on the App Store, "energy" is among the most frequent words in 1- and 2-star reviews; on Google Play, it's "streak" and "update". Same app, two different pain points worth reading in detail. (Swap the word count for an LLM call if you want proper topics.)

Step 5: how fast does the developer answer?

Google Play publishes developer replies, so you can measure a support team's reply rate and speed. Duolingo didn't reply to any of these 500 reviews, so here's Spotify (1,000 newest Google Play reviews):

play = reviews[reviews["store"] == "googlePlay"].copy()
replied = play["developerReply"].notna()
play["replyDelay"] = pd.to_datetime(play["developerReplyDate"], utc=True) - play["date"]

print(f"reply rate: {replied.mean():.0%}")
print("median delay:", play.loc[replied, "replyDelay"].median())
print(play.groupby("rating")["developerReply"].apply(lambda s: f"{s.notna().mean():.0%}"))
Enter fullscreen mode Exit fullscreen mode
reply rate: 16%
median delay: 0 days 01:43:53
rating
1    35%
2    44%
3    52%
4    29%
5     0%
Enter fullscreen mode Exit fullscreen mode

Spotify answers within two hours, and focuses on 2- and 3-star reviews: the users who are unhappy but still reachable. That's a useful benchmark for your own support team.

Step 6: did the last release hurt?

Google Play reviews carry the app version, so you can compare ratings per release:

print(play.groupby("appVersion")["rating"].agg(["size", "mean"]).query("size >= 20").round(2))
Enter fullscreen mode Exit fullscreen mode
             size  mean
appVersion
9.1.88.2204    61  4.28
9.1.90.2270    66  4.18
9.1.92.3058   632  3.69
Enter fullscreen mode Exit fullscreen mode

The newest Spotify version averages 3.69 stars against 4.18–4.28 for the two previous ones. On its own that's not proof of a bad release (the samples are small, and the latest version simply gets the most reviews), but it's exactly the signal you want an alert on right after shipping.

Step 7: get new reviews every day

Turn on onlyNewReviews and schedule the run (in the Apify Console, or from cron with the code above). Each run returns only reviews posted since the previous one, so you can post them to Slack or a support queue:

run = client.actor("featherscrape/app-store-google-play-reviews-scraper").call(run_input={
    "apps": ["com.duolingo", "570060128"],
    "ratings": [1, 2],          # only negative reviews
    "onlyNewReviews": True,
})
Enter fullscreen mode Exit fullscreen mode

A few things to know

  • App Store reviews are per country. Each country's App Store has its own reviews; Google Play groups them by language. Run once per market you care about (59 countries are supported).
  • Developer replies and app versions are Google Play only. Apple doesn't expose them in the data the scraper reads.
  • Recent samples move fast. 500 reviews can cover just two days for a big app. Use larger samples or date ranges before drawing conclusions.

Wrapping up

Putting both stores in one DataFrame turns "let's read some reviews" into numbers you can track: recent rating versus store rating, top complaints per platform, reply rate and delay, rating per release.

The scraper costs $0.15 per 1,000 reviews. If you need a field or hit a bug, open an issue on the scraper's page: I usually answer within 48 hours. Which app would you compare first?

Top comments (0)