TikTok Shop reviews scraper: export reviews with Python
Pull the reviews of any US TikTok Shop product into a CSV file: star rating, review text, the SKU the buyer chose, country, and verified-purchase and incentivized flags. You also get the product's 1★–5★ breakdown. A product with 400 reviews takes 9 requests.
TikTok Shop loads reviews after the page renders, so a plain HTTP scraper doesn't get them and a browser scraper has to click through pages. The TikTok Shop API returns up to 50 reviews per request as JSON.
Setup
You need Python 3, requests (pip install requests) and a key from the RapidAPI listing; the free plan's 100 requests are enough to try it. Put the key in an environment variable called RAPIDAPI_KEY.
import csv
import os
import re
import time
from collections import Counter
import requests
BASE = "https://tiktok-shop-api5.p.rapidapi.com"
HEADERS = {
"X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"],
"X-RapidAPI-Host": "tiktok-shop-api5.p.rapidapi.com",
}
def get(path, **params):
"""GET a path and return the JSON. Retries rate limits and upstream errors."""
for attempt in range(4):
response = requests.get(BASE + path, headers=HEADERS, params=params, timeout=30)
if response.status_code in (429, 502, 503) and attempt < 3:
time.sleep(2 ** attempt)
continue
response.raise_for_status()
return response.json()
Get the product ID
The product ID is the long number at the end of a TikTok Shop product link, such as https://shop.tiktok.com/us/pdp/1731016350288417305. You can also take id from a product search result.
def product_id(link_or_id):
match = re.search(r"(\d{15,20})/?(?:\?|$)", link_or_id.strip())
if not match:
raise ValueError(f"no product ID in {link_or_id!r}")
return match.group(1)
pid = product_id("https://shop.tiktok.com/us/pdp/1731016350288417305")
Read one page
GET /v1/products/{id}/reviews takes page (from 1) and page_size (up to 50).
curl --request GET \
--url 'https://tiktok-shop-api5.p.rapidapi.com/v1/products/1731016350288417305/reviews?page=1&page_size=20' \
--header "X-RapidAPI-Key: $RAPIDAPI_KEY" \
--header 'X-RapidAPI-Host: tiktok-shop-api5.p.rapidapi.com'
Real response for an office chair, shortened to one review
{
"has_more": true,
"total": "409",
"summary": {
"count": "409",
"score": 4.6,
"breakdown": { "1": 22, "2": 6, "3": 12, "4": 33, "5": 336 }
},
"reviews": [
{
"id": "7685937594455688974",
"rating": 5,
"text": "This little chair is not only cute, I chose the blue color, but also very comfortable.",
"author": "c**7",
"time": "2026-09-16T01:23:26Z",
"sku": "Blue",
"country": "US",
"verified": true,
"incentivized": false
}
]
}
rating is an integer from 1 to 5, time is UTC, and author comes masked. A product with no reviews returns total: "0", an empty breakdown and reviews: null.
Get every review
Increase page while has_more is true. Stop on has_more, not on total: total can be a few higher than the rows returned.
def all_reviews(pid):
reviews, summary, page = [], None, 1
while True:
data = get(f"/v1/products/{pid}/reviews", page=page, page_size=50)
summary = summary or data.get("summary") or {}
reviews.extend(data.get("reviews") or [])
if not data.get("has_more"):
return summary, reviews
page += 1
summary, reviews = all_reviews(pid)
breakdown = summary.get("breakdown") or {}
print(f'{summary.get("count", "0")} reviews, average {summary.get("score", "-")}')
print(" ".join(f"{star}★ {breakdown.get(star, 0)}" for star in "54321"))
Output for the office chair
409 reviews, average 4.6
5★ 336 4★ 33 3★ 12 2★ 6 1★ 22
Save a CSV
FIELDS = ["id", "rating", "time", "sku", "country", "verified", "incentivized", "text"]
with open(f"reviews_{pid}.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=FIELDS, extrasaction="ignore")
writer.writeheader()
writer.writerows(reviews)
print(f"saved {len(reviews)} reviews")
The row for the review shown above
7685937594455688974,5,2026-09-16T01:23:26Z,Blue,US,True,False,"This little chair is not only cute, I chose the blue color, but also very comfortable."
Find the complaints
Three questions most sellers and product researchers ask of a review export:
# 1. Which variant gets the bad reviews?
low = [r for r in reviews if r["rating"] <= 2]
print(Counter(r.get("sku") or "-" for r in low).most_common(5))
# 2. What is the average without incentivized reviews?
organic = [r["rating"] for r in reviews if not r.get("incentivized")]
if organic:
print("organic average:", round(sum(organic) / len(organic), 2))
# 3. What do unhappy verified buyers say?
for r in low:
if r.get("verified"):
print(r["rating"], r.get("sku"), (r.get("text") or "")[:120])
To compare competing products, run all_reviews on each product ID from a search and compare their breakdowns and low-star SKUs side by side.
Things to know
- Review depth. TikTok limits how far back review pages go. For products with a few hundred reviews you get all of them; for products with thousands you get a large share, not every one.
- Cost. One request per page of up to 50 reviews: 409 reviews take 9 requests.
- Freshness. Responses are cached for up to 7 days, so a review posted today can take up to a week to appear.
- US storefront only. Most reviews carry
country: "US". - Errors:
404for an unknown product ID,429past your plan's rate limit,502when TikTok fails after our retries. - This API is unofficial. It reads public storefront data and isn't affiliated with TikTok.
Next steps
- Search products and rank them by sold count to pick which products to analyze.
- List a seller's whole catalog, then export reviews for their best sellers.
- Full API reference.
Plans
A free plan covers testing. Plans, monthly quotas and rate limits are listed on the RapidAPI listing. For more volume or a custom plan, use Contact provider on the listing.