Google Business Profile Review Scraper
Pricing
from $0.42 / 1,000 results
Google Business Profile Review Scraper
Find recent low-star Google Business Profile reviews by search term and location, with the business's contact details on every row.
Pricing
from $0.42 / 1,000 results
Rating
0.0
(0)
Developer
String
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
2 days ago
Last modified
Categories
Share
This Actor finds recent low-star reviews on Google Business Profiles across a search term and a
location, and puts the business's contact details on every one of them. Give it plumber and
Austin, TX and you get back each 1-star review left in the last six months — the review text, its
date and its author, alongside the phone number, website and postal address of the business it
was left on.
What does Google Business Profile Review Scraper do?
The rating filter and the recency window are both yours to set, so the same Actor answers "every 1-star this month", "everything at 2 stars or below this year", or simply "every review these profiles have".
It reads Google's own JSON endpoints in two stages: a search surface that finds the profiles and carries their contact details, then each profile's reviews, sorted worst-first so one request covers the whole low-star history rather than the most recent page.
- One row per review, carrying the full business record, so any single row is enough to act on.
maxReviewRatingandwithinDaysare filters and budget dials at once — a profile whose reviews all miss them contributes no rows.- Specific profiles can be monitored directly by URL instead of searched for, via
places. - Up to 50 search terms per run, each searched separately.
No Google account, API key or cookies are used — this reads what a logged-out visitor sees.
What data does it extract?
One row per review. A business with three matching reviews returns three rows, each carrying the full business record so any single row is enough to act on.
| Field | Type | Notes |
|---|---|---|
rating | number | The review's stars, 1–5 |
publishedAt | string | ISO timestamp — an exact date, not "3 months ago" |
relativeDate | string | Google's own wording, e.g. "3 days ago" |
text | string | The review body, with the reviewer's line breaks intact. null for a rating-only review |
authorName | string | The display name the reviewer published with the review |
authorProfileUrl | string | The reviewer's public Google Maps contributor page |
authorReviewCount | number | How many reviews the author has written overall — a credibility signal. 0 is a real value |
language | string | BCP-47 code for the language the review was written in, e.g. en |
imageCount | number | Photos attached to the review |
reviewUrl | string | Link straight to the review |
reviewId | string | Stable, use it to de-duplicate across runs |
businessName | string | The business as Google lists it |
businessRating | number | The profile's overall star average, e.g. 4.7 |
phone | string | Formatted phone number, e.g. (512) 309-1487 |
website | string | The business's own site, as Google links it |
address | string | Street address; null for a service-area business, which Google also shows without one |
categories | string[] | Google's own labels, e.g. ["Plumber", "Electrician"] |
latitude, longitude | number | Exact coordinates, ready to map |
matchedReviewCount | number | How many matching reviews this business had in this run — group or prioritise on it |
placeId | string | Google's ChIJ… Place ID |
dataId | string | The 0x…:0x… id Google keys the profile's reviews on |
mapsUrl | string | Link straight to the profile |
lowestRatedReviewsRead | number | How much of the profile's low-star history this run saw (see Reliability) |
searchTerm, sourceUrl, collectedAt | string | Provenance for every row |
Why scrape Google reviews?
- Reputation-management prospecting. A fresh 1-star review is a business in the market for help, and the row already carries the phone number to call.
- Agency churn signals. Watch your own clients' profiles and get the bad review the day it lands, rather than when they forward it.
- Competitor monitoring across a category and city, with the complaint text itself rather than a shifted star average.
- Complaint-theme analysis across a whole market segment, read from the review bodies.
- Franchise and multi-location oversight, one row per piece of bad feedback, grouped on
dataId.
How to use it
- Open the Actor and click Try for free, or add it to your Apify account.
- Put your terms in Searches, one per line —
plumber,hvac. Set Location to the market, e.g.Austin, TX, or leave it empty if your terms already name one. - Leave Maximum review rating at
1for 1-star only, or raise it to2to catch 1- and 2-star. - Leave Within days at
180for the last six months, or shorten it to watch a current window. - Click Start, then take the rows from the Dataset tab as JSON, CSV or Excel.
- Read the run's
SUMMARYrecord for any profile that could not be read, with its error.
To monitor a fixed set of businesses rather than a market, put their Google Maps URLs in Places
and leave Searches empty. Save the input as a task and schedule it to get each bad review as it
lands; rows carry collectedAt, and reviewId is stable, so successive runs de-duplicate cleanly.
Input
{"searches": ["plumber", "hvac"],"location": "Austin, TX","maxReviewRating": 1,"withinDays": 180,"maxItems": 1000}
| Field | Description |
|---|---|
searches | Search terms, e.g. "plumber". Up to 50, each searched separately. |
location | City, state, ZIP or country, appended to every term. Leave empty if your terms already carry a location. |
places | Optional. Google Maps place or review URLs to read directly, instead of or as well as a search. |
maxReviewRating | Returns any review at or below this rating. 1 finds 1-star only; 5 returns every review. Default 1. |
withinDays | How recent a review has to be to be returned. Default 180, roughly six months. |
maxProfilesPerSearch | How deep to page each search before filtering. Default and maximum 120. |
countryCode, language | The market the search runs in, e.g. US / en, or GB / en. |
maxItems | Cap on dataset items. Default 1000. Free plans stop at 250 requests and 250 results — see below. |
concurrency | Requests in flight at once. Default 3, maximum 5. |
Supply either searches or places — a run with neither is rejected before it starts. A business
whose reviews all miss your filter simply contributes no rows.
Input by URL
places accepts a Google Maps place or review URL — the long kind containing !1s0x…:0x… — or that
0x…:0x… id on its own. A maps.app.goo.gl short link and a bare ChIJ… Place ID do not carry
that id, and are reported in failures rather than guessed at. A profile supplied this way returns
its reviews and whatever name its URL contained; phone, website, address, categories and the
coordinates come from the search surface, so those fields are null on a URL-only run.
Output
One row per review. The shape below is illustrative — it shows the fields and their types, not a captured run.
{"reviewId": "ChZDSUhNMG9nS0VJQ0FnSUMxbHZHWVh3EAE","rating": 1,"publishedAt": "2026-07-28T14:02:11.000Z","relativeDate": "3 weeks ago","text": "Booked a same-day callout and nobody turned up.\nNo call, no refund.","language": "en","imageCount": 2,"authorName": "Dana Whitfield","authorProfileUrl": "https://www.google.com/maps/contrib/108273645190273645190","authorReviewCount": 37,"reviewUrl": "https://www.google.com/maps/reviews/data=!4m6!14m5!1m4!2m3!1sChZDSUhNMG9nS0VJQ0FnSUMxbHZHWVh3","businessName": "Lone Star Plumbing Co","businessRating": 4.1,"phone": "(512) 309-1487","website": "https://lonestarplumbing.example.com/","address": "1200 E 6th St, Austin, TX 78702","categories": ["Plumber", "Drainage service"],"latitude": 30.267153,"longitude": -97.731339,"placeId": "ChIJLwPMoJm1RIYRetVp1EtGm10","dataId": "0x8644b59a80cc032f:0x5d9b464bd469d57a","mapsUrl": "https://www.google.com/maps/place/?q=place_id:ChIJLwPMoJm1RIYRetVp1EtGm10","matchedReviewCount": 3,"lowestRatedReviewsRead": 22,"searchTerm": "plumber","sourceUrl": "https://www.google.com/search?tbm=map&q=plumber+Austin%2C+TX","collectedAt": "2026-08-21T09:14:03.512Z"}
The run also writes a SUMMARY record holding the item count, the failure count and every profile
that could not be read, with its error.
Reliability
Both halves read Google's own JSON endpoints rather than scraping rendered markup, so every value is
exact: ratings are numbers, review dates are absolute timestamps, and the reviewer's own line breaks
survive into text.
Reviews are read worst-first, which is why one request per profile is enough. Sorted that way, a
single page carries the profile's entire low-star history rather than its most recent 50 reviews — on
a busy profile that page held all 16 one-star and all 6 two-star reviews it had ever received, and the
next page was already entirely five-star, while reading newest-first would have needed eight pages to
cover the same six months. lowestRatedReviewsRead reports how many that page held; a value of 50
means it filled up, so a profile with more than 50 reviews at or below its floor may have others this
run did not read.
A search pages up to Google's own ceiling of 120 results per term, and consecutive pages overlap, so
the distinct total per term lands nearer 100. Profiles are de-duplicated on dataId before any
reviews are fetched, so a profile that appears on two search pages is never fetched twice.
A profile whose reviews cannot be read is recorded in the run's SUMMARY under failures rather than
silently contributing nothing — an intercepted response and a profile with genuinely no matching
reviews look identical otherwise, and the difference decides whether a list is complete.
There are no retries by design: the String Unblocker owns proxy rotation and anti-bot solver selection, so a retry loop here would only re-roll the same block.
Limitations
Search and review data only: no opening hours, no menus, no photos, no popular-times data and no
"about" text. Reviews are read one worst-first page deep, so this is not a full review-history export
for a profile with more than 50 low-rated reviews. Profiles supplied by URL in places carry no
address, phone, website or coordinates, because those come from the search surface rather than the
review endpoint. Google orders a search by its own relevance rather than anything stable, so
re-running can surface a business at a different rank; de-duplicate on reviewId.
How much does it cost?
A run that returns 1,000 rows costs $0.83 on a paid plan, or $1.00 with no subscription.
You are charged per result row written to the dataset, so a run that returns nothing costs nothing,
and maxItems is the budget dial.
| Plan | Per 1,000 rows |
|---|---|
| Free | $1.00 |
| Bronze | $0.83 |
| Silver | $0.58 |
| Gold | $0.42 |
| Platinum | $0.31 |
| Diamond | $0.19 |
Apify bills its own platform usage for the run on top of this.
One row is one review, so a business with three matching reviews costs three rows. A profile whose
reviews all miss your filter contributes none and costs nothing, which makes maxReviewRating and
withinDays budget dials as much as filters.
Runs started from an Apify free plan stop at 250 requests and 250 results, and the run reports
that it reached the limit. Any paid plan runs the full input and maxItems you set.
The limit exists because this Actor fetches through our own infrastructure, which Apify does not cover for free-plan runs. It binds on requests as well as results, and one ceiling covers both the search and the review stage together, so a large input cannot spend those fetches for rows the run will not return.
Using it with the Apify API
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });const run = await client.actor('usestring/google-business-reviews').call({searches: ['plumber', 'hvac'],location: 'Austin, TX',maxReviewRating: 1,withinDays: 180,maxItems: 1000});const { items } = await client.dataset(run.defaultDatasetId).listItems();const callable = items.filter((review) => review.phone !== null);console.log(`${items.length} bad reviews, ${callable.length} with a phone number attached`);
The same call works from the Python client, and from any HTTP client through the Apify REST API. The
run's SUMMARY record is in the run's default key-value store.
Is it legal to scrape Google?
This Actor reads public Google Business Profiles as a logged-out visitor. It sends no account credentials, no cookies and no API key, and it accesses nothing behind a login or a paywall. The rows it return are reviews their authors published publicly, alongside the contact details a business chose to list so that customers could find them.
It does return the display name a reviewer published with their review, which is personal data in the
GDPR sense even though it is public. If you process these rows for people in the EU or UK, that is
your lawful basis to establish, and authorName is the field to drop if you do not need it.
Scraping public data is broadly lawful in the US and the EU, but what you then do with it is governed by your own jurisdiction, your contracts and, where personal data is involved, by GDPR and similar regimes. Contacting the businesses you find is subject to your local marketing rules. This is not legal advice. If your use case is unusual, take your own.
Related actors
- Yelp Business Scraper — the same reputation surface on the other major review platform.
- Capterra Reviews Scraper — software reviews, where the complaint text is about a product rather than a visit.
- G2 Reviews Scraper — the B2B software equivalent.
- Realtor.com Listings Scraper — local business data of a different shape, for the same territory-planning work.
Pair this with a CRM import and the phone number on every row is a call list, already filtered to businesses with a fresh complaint against them.
FAQ
How do I find businesses with recent bad reviews? Run this Actor with your search terms and
location and leave maxReviewRating at 1 and withinDays at 180. Every row is a 1-star review
from the last six months, with the business it was left on attached.
Can I widen it beyond 1-star? Yes. maxReviewRating returns any review at or below the value you
set, so 2 catches 1- and 2-star, and 5 returns everything the worst-first page held.
Does it return the review text? Yes, with the reviewer's line breaks intact. A rating-only review
with no words returns null text and keeps its rating, date and author.
Why do I get several rows for the same business? Because each row is one review. Group on
dataId, or read matchedReviewCount, which tells you how many that business had in this run.
How many businesses does one search cover? Up to 120, which is Google's own ceiling per search term; overlapping pages mean the distinct count is usually nearer 100. Use more search terms, or more specific ones, to cover a market more deeply.
Do I need a Google account, API key or cookies? No. It reads public Google Business Profiles as a logged-out visitor and never signs in.
Can I monitor specific businesses instead of searching? Yes — put their Google Maps URLs in
places. See "Input by URL" for which URL forms carry the id this needs.
Can I search several cities in one run? location is one value applied to every term, so run the
Actor once per city — or put each city into the search terms themselves and leave location empty.
Feedback
Something wrong in the data, or a field you need that is not here? Open an issue on the Actor's Store page and we will read it. Bug reports that name the search term, the location and the date of the run are the fastest to act on.