如何用 Python 和 pandas 比較 iOS App 發佈後不同國家的 App Store 評論
How to Compare iOS App Store Reviews Across Countries After a Release (Python + pandas)
教學示範如何使用 Apify 的 App Store Reviews Scraper,收集不同國家的 App Store 評論,再用 Python 和 pandas 按國家及 App 版本比較。工具每 1,000 則評論收費 $0.25;Apple 每個 App、國家及排序方式最多提供 500 則評論。
After an iOS release, the App Store shows one star average per country, and it moves slowly because it includes years of old ratings. What you want to know is more specific: are users in Germany suddenly angrier than in the US? Did the new version bring a new complaint, or the same old one in more languages?
Apple doesn't make that easy. App Store Connect and its API show reviews only for apps you own. For anyone else's app you get the store page, which shows a handful of reviews for one country.
This tutorial shows how to collect reviews from several App Store countries into a single table and compare them by country and app version.
Disclosure: I built and maintain this Actor. The tool used below is my own App Store Reviews Scraper on Apify, a paid tool ($0.25 per 1,000 reviews). I'll try to be clear about what it can and can't do.
Why countries matter on iOS
Apple stores reviews per country. A review written in the German store appears in the German store, not in the US one. Apple also publishes at most 500 reviews per app, country and sort order. Two consequences:
- If you only read the US store, you miss most non-English feedback.
- If you want more than 500 recent reviews of an app, the way to get them is to add more countries, not to page further back.
Every review the Actor saves has a country field, so you can group by it directly.
Step by step in Apify Console
- Open the Actor page and click Try for free (you need an Apify account).
- In What to scrape, keep Reviews of apps.
- In Apps, paste App Store links or app ids. The id is the number at the end of the link: for
https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580it is324684580. - In Countries, add two-letter codes such as
us,gb,de,jp. You can also typeallto go through every country. - Set Max results, and Max reviews per app and country if you want an even sample, so the US doesn't fill the whole run.
- Choose Sort reviews by: Newest (for release monitoring), Most helpful, or Both, which merges the two lists and gives up to 1,000 reviews per country.
- Click Start. When the run finishes, open the Output tab and export as JSON, CSV or Excel.
No Apple account, API key or proxy is needed.
For this article I ran the newest five Spotify reviews from four countries:
{
"mode": "reviews",
"appIds": ["324684580"],
"countries": ["us", "gb", "de", "jp"],
"maxItemsPerCountry": 5,
"maxItems": 20,
"sort": "mostRecent"
}
It finished in about 6 seconds and saved 20 reviews, five per country.
What the output looks like
Here are two of those 20 reviews, one from Germany and one from Japan. I redacted the user names and the links to the users' review profiles; everything else is unchanged:
[
{
"type": "review",
"reviewId": "14713458923",
"appId": "324684580",
"appName": "Spotify: Music and Podcasts",
"country": "de",
"userName": "(redacted)",
"userUrl": "(redacted)",
"rating": 1,
"title": "Werbung trotz Premium",
"text": "Bei Podcasts kommt Werbung, obwohl ich das Premiumabo habe. Pro Podcast sind es 1-2 Blöcke mit jeweils 3-5 seperaten Werbeanzeigen. Regelmäßig erschrecke ich mich beim Podcast hören weil plötzlich eine extrem laute KI Stimme Werbung durch die Lautsprecher brüllt. Ab und zu kommt es vor dass wenn man versucht die Werbung zu überspringen, der Podcast von vorne beginnt. Dann spult man nach vorne und versucht die Stelle zu finden wo man aufgehört hat und dann kommt an genau dieser Stelle erneut ein Werbeblock, weil spotify entweder dreist oder zu dumm ist um zu erkennen, dass ich den Werbeblock bereits gehört habe und mich innerhalb dieser Minute erneut mit Werbung bombardiert.\nUnfassbar dreist und nervenaufreibend.\nKein Wunder dass bei so einem Verhalten die Piraterie wieder steigt. Spotify ist es selbst schuld",
"appVersion": "9.1.94",
"voteCount": 0,
"voteSum": 0,
"postedAt": "2026-10-26T12:33:04Z",
"sortedBy": "mostRecent",
"url": "https://apps.apple.com/de/app/id324684580?see-all=reviews",
"scrapedAt": "2026-10-27T21:24:45Z"
},
{
"type": "review",
"reviewId": "14713709598",
"appId": "324684580",
"appName": "Spotify: Music and Podcasts",
"country": "jp",
"userName": "(redacted)",
"userUrl": "(redacted)",
"rating": 1,
"title": "広告が多い",
"text": "広告が多い",
"appVersion": "9.1.94",
"voteCount": 0,
"voteSum": 0,
"postedAt": "2026-10-26T13:45:56Z",
"sortedBy": "mostRecent",
"url": "https://apps.apple.com/jp/app/id324684580?see-all=reviews",
"scrapedAt": "2026-10-27T21:24:46Z"
}
]
The German review complains about ads in podcasts despite Premium. The Japanese one says, in full, "too many ads". In the same small sample, a US review said "Hella ads but good tho" and a UK review said the ads are "extremely repetitive". Four countries, four languages, one theme. That's the point of pulling several countries: an English-only keyword search would have missed the German and Japanese ones.
Five reviews per country is far too few to compare average ratings, so treat this only as an example of the format. All 20 reviews were on version 9.1.94, and the five US reviews were written within 15 minutes of each other. For an app this big, the newest 500 reviews in one country may cover only a short period. Also, the newest reviews in the feed were about 31 hours old when I ran it, so don't expect reviews from the last few hours.
voteCount and voteSum (helpful minus not-helpful votes) start at 0 on new reviews, so they matter mostly with the Most helpful sort.
Doing the same from Python
Install the official client and pandas (pip install apify-client pandas) and get your API token from the Apify Console settings.
from apify_client import ApifyClient
import pandas as pd
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("pipiagent/app-store-reviews-scraper").call(run_input={
"mode": "reviews",
"appIds": ["324684580"],
"countries": ["us", "gb", "de", "fr", "jp", "br"],
"sort": "mostRecent",
"maxItemsPerCountry": 500,
"maxItems": 3000,
})
items = list(client.dataset(run.default_dataset_id).iterate_items())
df = pd.DataFrame(items)
df.to_csv("spotify_reviews_6_countries.csv", index=False)
First, a per-country overview. The oldest column tells you how far back the newest 500 reviews go in each country, which you need to know before comparing anything:
df["postedAt"] = pd.to_datetime(df["postedAt"], utc=True)
df["appVersion"] = df["appVersion"].fillna("unknown")
df["low"] = df["rating"].le(2) # 1 or 2 stars
by_country = df.groupby("country").agg(
reviews=("reviewId", "count"),
avg_rating=("rating", "mean"),
share_low=("low", "mean"),
oldest=("postedAt", "min"),
)
print(by_country.round({"avg_rating": 2, "share_low": 2}))
Then ratings by version and country. Only versions with at least 30 reviews in a country are kept, so one or two reviews don't decide the result:
grouped = df.groupby(["appVersion", "country"])["rating"]
table = grouped.mean().where(grouped.size() >= 30).unstack().round(2)
print(table.dropna(how="all"))
Finally, a rough multilingual topic count. Write the keywords per language yourself; these are just examples:
topics = {
"ads": r"\bads?\b|\badds\b|advert|werbung|publicité|\bpubs?\b|広告|anúncio",
"price": r"premium|subscription|\babo|abonnement|prix|price|料金|有料|assinatura",
"bugs": r"crash|bug|absturz|stürzt|plante|落ちる|バグ|trava",
}
text = (df["title"].fillna("") + " " + df["text"].fillna("")).str.lower()
for name, pattern in topics.items():
df[name] = text.str.contains(pattern)
print(df.groupby("country")[list(topics)].mean().round(3))
For anything you present to a team, translate the reviews first; hand-written regexes per language miss a lot.
What it costs
The Actor is pay per result: $0.25 per 1,000 reviews, with no start fee or monthly rental.
- The 20-review test above: $0.005
- 500 newest reviews in each of 6 countries (3,000 reviews): $0.75
- A daily check of the newest 100 reviews in 5 countries for a month (15,000 reviews): $3.75
You can set a maximum charge per run in the run options, and the run stops when it reaches it.
Limitations
- At most 500 reviews per app, country and sort order. That's Apple's limit. Both sort orders give up to 1,000 per country. Older reviews can't be reached, so start collecting before a release if you want a "before" picture.
-
No star filter in the input. Unlike some stores, Apple's feed has no rating filter, so filter by
ratingafterwards, as above. - Only reviews with text. Star-only ratings count in the store average but can't be collected.
- No developer replies. Apple's public review feed doesn't include them.
-
No link to a single review.
urlopens the app's review page in that country. - Occasional empty pages. Apple sometimes returns an empty page; the Actor retries up to five times. If one stays empty, the log says so and up to 50 reviews can be missing.
-
appNamecomes from the first country in your list, even where the app has a translated name. - Small countries have few reviews or none.
- Personal data. Results include public user names and profile links. If you store them, make sure you have a legitimate reason under GDPR or similar laws, or drop those columns.
The Actor also has app details and keyword search modes. App details across several countries shows each store's price, rating count and current version, which is a quick way to check when a release reached each store.
If something is broken or missing, the Issues tab on the Actor page is the best place to report it.
來源:DEV Community · dev.to