Scraping Google reviews directly violates Google's Terms of Service and risks IP blocks, legal action, and data quality issues. A search API such as Scavio returns ratings, review counts and review snippets from Google results, and its Google Maps reviews endpoint returns the individual reviews themselves (text, star rating, reviewer, dates), all as structured JSON with predictable pricing. Whichever option you choose, you remain responsible for how you use the data (see our Terms).
Legal Risks of Direct Scraping
Google's ToS explicitly prohibit automated access to Google Maps and review data. Companies that have scraped Google reviews at scale have received cease-and-desist letters. The hiQ v. LinkedIn ruling (2022) established that scraping publicly accessible data is not automatically illegal under the CFAA, but Google's terms create a contractual barrier that scraping violates.
The practical risk: Google blocks scraper IPs aggressively, rotates DOM structures to break parsers, and has sent legal notices to scraping services that specifically target Google Maps data.
The Search API Approach
Instead of scraping Google yourself, search for the business name + "reviews" through a search API. The organic results carry review-site listings and snippets that mention the business, which is enough to see where it is being talked about. The Google Search endpoint is POST /api/v2/google and costs 1 credit per request.
import os
import requests
API = "https://api.scavio.dev"
H = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}"}
def review_mentions(business_name, location):
"""Organic Google results for '<business> <location> reviews'."""
r = requests.post(f"{API}/api/v2/google",
headers=H,
json={"query": f"{business_name} {location} reviews", "gl": "us", "hl": "en"},
timeout=90,
)
r.raise_for_status()
return [
{"title": item.get("title"), "url": item.get("link"), "snippet": item.get("snippet")}
for item in r.json().get("organic_results", [])
]
for m in review_mentions("Blue Bottle Coffee", "San Francisco"):
print(m["title"], m["url"])Individual Reviews: the Maps Reviews Endpoint
Search snippets give you mentions, not the reviews. For the reviews themselves, use two Google Maps calls, 1 credit each:
- Google Maps search (
POST /api/v2/google/maps/search) finds the place and returns itsrating, totalreviewscount and adata_id. - Google Place Reviews (
POST /api/v2/google/maps/reviews) takes thatdata_id(or aplace_id) and returns a page of up to 20 reviews, each with the starrating, the review text insnippet, the reviewer underuser, and the posting and last-edit dates. Passsort_by(relevance,newest,highest_rating,lowest_rating) and followpagination.next_page_tokenfor more pages.
def find_place(query, ll):
"""First Google Maps result for a query around a map center (@lat,lng,zoomz)."""
r = requests.post(f"{API}/api/v2/google/maps/search",
headers=H,
json={"query": query, "ll": ll, "gl": "us", "hl": "en"},
timeout=90,
)
r.raise_for_status()
places = r.json().get("local_results", [])
return places[0] if places else None
def get_reviews(data_id, pages=1, sort_by="newest"):
"""Page through a place's reviews, 20 per request."""
reviews, token = [], None
for _ in range(pages):
body = {"data_id": data_id, "num": 20, "sort_by": sort_by, "hl": "en"}
if token:
body["next_page_token"] = token
r = requests.post(f"{API}/api/v2/google/maps/reviews",
headers=H, json=body, timeout=90)
r.raise_for_status()
data = r.json()
reviews.extend(data.get("reviews", []))
token = (data.get("pagination") or {}).get("next_page_token")
if not token:
break
return reviews
place = find_place("Blue Bottle Coffee", "@37.7749,-122.4194,13z")
print(f"{place['title']}: {place['rating']} stars from {place['reviews']} reviews")
for rv in get_reviews(place["data_id"], pages=2):
text = (rv.get("snippet") or "")[:80]
edited = rv.get("iso_date_of_last_edit") or rv.get("iso_date", "")
print(rv["rating"], edited[:10], rv["user"]["name"], text)Each review carries two dates. iso_date is when the review was first posted; iso_date_of_last_edit is when it was last edited, and that is the date the newest sort orders by. Filter on iso_date_of_last_edit when you want recent activity, or a review posted years ago and edited last week will look old.
Monitoring Review Sentiment Over Time
Track how a set of businesses moves by taking a periodic snapshot: the aggregate rating and review count from Maps search, plus how many of the newest reviews are 1 or 2 stars. Store each snapshot and compare it with the previous one. This works for competitive intelligence and reputation monitoring without maintaining scrapers.
import json
from datetime import datetime, timedelta, timezone
def snapshot(businesses, ll, days=30):
"""Rating, review count and recent low-star reviews per business."""
cutoff = datetime.now(timezone.utc) - timedelta(days=days)
rows = []
for name in businesses:
place = find_place(name, ll)
if not place:
rows.append({"name": name, "found": False})
continue
recent_low = []
for rv in get_reviews(place["data_id"], pages=1, sort_by="newest"):
edited = rv.get("iso_date_of_last_edit") or rv.get("iso_date")
if not edited or rv.get("rating", 5) > 2:
continue
if datetime.fromisoformat(edited.replace("Z", "+00:00")) >= cutoff:
recent_low.append(rv)
rows.append({
"name": place["title"],
"rating": place.get("rating"),
"review_count": place.get("reviews"),
f"low_star_last_{days}d": len(recent_low),
})
return {"date": datetime.now(timezone.utc).strftime("%Y-%m-%d"), "businesses": rows}
competitors = ["Blue Bottle Coffee", "Starbucks", "Philz Coffee"]
print(json.dumps(snapshot(competitors, "@37.7749,-122.4194,13z"), indent=2))That is 2 credits per business per snapshot. One page of the newest 20 reviews covers a short window for a busy place, so raise pages if you need to look further back.
Google Places API as Middle Ground
Google's official Places API also returns review data. Asking Place Details for the reviews field bills the Place Details Enterprise + Atmosphere SKU: $25 per 1,000 requests ($0.025 each) after 1,000 free calls a month, per Google's Maps Platform pricing as of October 2026, and it is Google's own official product. Place Details returns at most five reviews per place, so it suits a "latest few reviews" widget better than monitoring or analysis. When you need the full review history of a place, page through the Maps reviews endpoint instead.