How to Scrape Google Maps Reviews in Python (Pagination, Sort & Dedupe)
Method note: Measurements in this article were run against authenticated Serpent API endpoints on August 3, 2026 and re-checked August 7. Sample sizes are stated in the article. Treat the numbers as a dated field check, not a permanent guarantee.
Most reviews tutorials hand you a pagination loop and stop. They never tell you what happens when a page request 503s, what the signed-out view actually hides, or why deduping by reviewer name corrupts your dataset.
I pulled reviews from two businesses through the Serpent Google Maps API in August 2026 and measured all of it. This guide is the Python you can actually run, with the real numbers: 20 reviews a page, duplicate-free pagination, owner replies, and the one field that works when the absolute dates come back null.
Real Data from Two Businesses
I pulled the first page of reviews for two businesses, then followed the next-page token for each. Here is the measured result:
| Business | Page 1 reviews | Next token | Page 2 reviews | Overlapping IDs | Owner replies |
|---|---|---|---|---|---|
| South Austin Dentist | 20 | yes | 0 | 0 | 19 |
| Time Plumbing, Heating & Electric Denver | 20 | yes | 20 | 0 | 38 |
August 3, 2026. The first business returned only one page; the second returned two. Owner replies count across both pages. One second-page request hit a transient 503 on a local run.
Two findings stand out. First, pagination is duplicate-free: zero overlapping review IDs across pages. Second, one second-page request returned a transient 503 on a local run — which is exactly why production jobs should checkpoint page progress instead of assuming the loop always succeeds.
Python Ingest
The core loop is small. Request by place_id, read the reviews, dedupe by review_id, and keep following the token until it is gone:
import requests, sqlite3
BASE = "https://apiserpent.com"
HEADERS = {"X-API-Key": "YOUR_API_KEY"}
def fetch_reviews(place_id, page_token=None, sort="newest"):
params = {"place_id": place_id, "sort": sort}
if page_token:
params["next_page_token"] = page_token
r = requests.get(f"{BASE}/api/maps/reviews", headers=HEADERS, params=params, timeout=60)
r.raise_for_status()
return r.json()
page = fetch_reviews("ChIJ...")
for review in page["results"]["reviews"]:
save_review(review["review_id"], review["rating"], review["text"]["original"])
if page.get("next_page_token"):
next_page = fetch_reviews("ChIJ...", page["next_page_token"])
Note the token parameter is next_page_token, and reviews sit under results.reviews in the response. The ingest writes by review_id so re-runs are idempotent.
Pagination Rules
Four rules make the loop production-safe:
- Store the input identifier, sort mode, page token, and fetch timestamp together. A token is meaningless without its sort order.
- Treat tokens as cursors, not offsets. Never assume page N contains reviews 20×N onward.
- Checkpoint the last successful token. If a later page fails, resume from there.
- Page through promptly. Tokens expire after roughly 15 minutes.
The transient 503 from the probe is the case study for rule three. The job did not lose data because the token was still valid on retry. Rate-limit responses follow the same pattern — treat them as server load and back off, never as a reason to drop a page.
Sort Modes
The endpoint accepts sort=relevant|newest|highest|lowest. Always store the sort with the page token you requested, because the token is bound to the sort order — a token from newest is not meaningful for highest. If you pull multiple sorts for one place, keep them in separate tables or key them by sort.
Owner Replies
Owner responses come back in the review record when they exist. In the probe, the first business had owner replies on 19 of 20 reviews on page one — the second business's total across both pages was 38. If you are building a review-monitoring product, that is a rich field most scrapers drop. The full field list is in the API reference.
The Signed-Out View Gotcha
This is the field-level surprise. Because the endpoint reads the public (signed-out) view of Google Maps, absolute timestamps and direct review URLs are not available. That means published_at and review_url come back null. The working field is relative_date — the human string like "2 months ago".
If your schema requires an absolute timestamp, you will have to parse relative_date yourself or accept a lower bound. Never fabricate a date where the source exposes none — that is the kind of "improvement" that quietly corrupts a review-trending chart.
How This Fits the Maps Flow
Reviews are the third leg of the local-data flow after search and place details. The place detail audit found 12 of 15 standard fields present; reviews follow the same honest-null contract. If you are pricing a project that needs reviews at volume, the Maps pricing comparison shows the per-1,000 math, and the provider comparison shows who returns owner replies at all.
Pull Real Reviews on 10 Free Calls
Serpent's Google Maps Reviews API returns 20 reviews per page with duplicate-free pagination and owner replies — and every new account gets 10 free calls, no credit card. Run this exact Python loop on your own places today.
Get Your Free API KeyExplore: Maps API · Pricing · Playground
FAQ
Does the endpoint return owner replies?
Yes. In the August 2026 sample, the first review page for one business included owner responses on 19 of 20 reviews.
How do I avoid duplicate reviews?
Use review_id as the primary key and treat page tokens as cursors, not offsets. In the two-business probe, two consecutive pages had zero overlapping review IDs.
Can I sort reviews?
The endpoint accepts sort values such as newest, relevant, highest, and lowest. Always store the sort with the page token you requested, because tokens are bound to a sort order.
Why are published_at and review_url null?
The signed-out public view does not expose absolute timestamps or direct review URLs, so those fields come back null. relative_date is the working field for when a review was left.
What should I do when a page request fails?
Checkpoint the last successful page token and resume from it. In the probe, one second-page request returned a transient 503 on a local run, and the token let the job continue.
Do page tokens expire?
Yes. Tokens are bound to the place and sort order and expire after roughly 15 minutes, so page through promptly in the same job run.



