# Google Local Services Ads Scraper — Leads (`scrapersdelight/google-local-services-scraper`) Actor

Scrape Google Local Services Ads advertisers - the vetted pros above the map pack - for any trade in any US metro: business name, type, city, phone, star rating, review count and breakdown, opening hours, highlights and Google's stable business IDs. No login.

- **URL**: https://apify.com/scrapersdelight/google-local-services-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Lead generation, Business, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 per business returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## 🛠️ Google Local Services Ads Scraper — vetted local pros, with phone numbers

Scrape the **Local Services Ads** that sit above the map pack — the vetted home-service and
professional pros Google puts at the very top for "plumbers near me". One row per advertiser:
**business name, trade, city, phone, star rating, review count and full star breakdown, opening
hours, highlights** and Google's stable business IDs.

These are businesses **already paying Google per lead** — the highest-intent local-business list
there is.

### What does this actor do?

- 🏢 **`name`, `businessType`, `city`** — e.g. *Radiant Plumbing, Air Conditioning, & Electrical* · Plumbers · Austin
- 📞 **`phone`** — in international format, ready to dial
- ⭐ **`rating`, `reviewCount`** plus **`starBreakdown`** (how many 1★ through 5★)
- 🕐 **`hours`** — the full weekly table, e.g. `{"Tuesday": "Open 24 hours", …}`
- ✨ **`highlights`** — e.g. *24/7 emergency services · Local business*
- 🆔 **`cid`, `bid`** — Google's stable identifiers (verified identical across repeat runs), so you can
  re-run and diff over time. **`pid`** is also returned but Google fills it with the placeholder
  `9999999999` for about 70% of advertisers — key your diffs on `cid`.
- 🔗 **`profileUrl`**, **`bookingUrl`**, **`messagingUrl`**

### Example input

```json
{
  "services": ["plumber", "electrician", "roofer", "hvac"],
  "cities": ["Austin TX", "Denver CO", "Phoenix AZ"],
  "requirePhone": true,
  "minRating": 4,
  "maxItems": 500
}
```

Every service is crossed with every city — 4 × 3 = 12 queries, up to 20 advertisers each.

### Read this: the proxy setting is the whole mechanism

**Google decides Local Services inventory from the requesting IP's location.** Measured on the same
URL in the same second:

| Lane | Response | Advertisers returned |
|---|---|---|
| Developer's home IP (non-US) | 200, 1,111 KB | **0** |
| Datacenter proxy | 200, 1,111 KB | **0** |
| **Residential US** | 200, **1,747 KB** | **20** |

A non-US or datacenter IP does not error and does not get blocked — it gets a perfectly valid page
containing an **empty result list**. That is why the proxy default here is residential US, and why
you should not change it. If a run returns nothing, the log says so explicitly rather than
pretending the city has no plumbers.

### Field coverage — measured

Measured across eight repeat runs of the same query (plumber, Austin TX):

| Field | Fill |
|---|---|
| `name`, `businessType`, `city`, `phone`, `rating`, `reviewCount`, `starBreakdown`, `highlights`, `hours`, `cid`, `bid`, `profileUrl`, `searchQuery`, `scrapedAt` | **100%** |
| `messagingUrl` | 85-100% across repeat runs |
| `bookingUrl` | 65-80% across repeat runs — only advertisers with online booking wired up |

Nothing below 65%. Measured across eight repeats of the same query — the two link fields move run to
run because Google rotates which advertisers show a booking/messaging button.

### Who is this for?

Agencies and SaaS selling to home-service and legal SMBs (an LSA advertiser has a proven ad budget),
lead-gen businesses, local-SEO teams tracking who holds the top slots, and market researchers
comparing provider density and ratings across metros.

### Honest limits

- **20 advertisers per query.** That is what Google returns per service+city; there is no deeper
  pagination. Volume comes from more service × city combinations, and the actor de-duplicates by
  business ID across the whole run.
- **US only.** This actor pins every request to `hl=en-US&gl=us` behind a US residential IP, so it
  returns US metros only.
- **Google rate-limits.** A burst of queries can draw a 429. The actor backs off hard and retries on
  a fresh IP, and reports how many it absorbed. Very large runs pace themselves and take longer.
- **No "Google Guaranteed" / "Google Screened" badge field.** The badge text is not present in the
  data the page ships, so it is not returned rather than guessed. Every advertiser in this surface
  is by definition a vetted Local Services advertiser.
- **Ratings are Google's**, aggregated across the business's reviews.

### Pricing

| What | Price |
|---|---|
| Per business returned | **$0.003** |

A 1,000-business multi-metro list costs **$3**. Filtered rows cost nothing.

### FAQ

**How many results per city?**
Up to 20 per service+city query. Use several trades and several cities for a bigger list.

**Why did I get zero results?**
Almost always the proxy. Local Services results depend on a US IP — keep the residential US default.

**Can I use my own proxy?**
Yes, but it must be residential US or Google returns an empty list.

**What trades work?**
Anything Google runs Local Services for: plumber, electrician, HVAC, roofer, locksmith, garage door,
house cleaning, pest control, lawn care, movers, and professional services like lawyers and realtors.

**Are the phone numbers direct?**
They are the numbers Google publishes on the ad, in international format.

**Can I track a competitor over time?**
Yes — `cid` and `bid` are stable, so re-run on a schedule and diff rating and review count. There is
no position/rank field; rows arrive in Google's order but that order is not stamped on the row, and
the advertiser set itself rotates (13 of 20 businesses recurred between two runs a day apart).

**Is there a badge field?**
No. The badge text is not in the page data, so nothing is invented. Presence in this list already
means the business is a vetted Local Services advertiser.

**Does it need a browser?**
No. The advertiser list is server-rendered into the page, so this is a plain HTTP fetch — fast and
cheap.

### Data source and fair use

Data comes from Google's public Local Services results pages. No login, no paywall and no private
API is involved. The actor paces itself — 1.8 s between queries, and a hard back-off on a fresh IP if
Google returns a 429. You are responsible for lawful outreach to the businesses returned.

# Actor input Schema

## `queries` (type: `array`):

Service + city, e.g. "plumber Austin TX". Each query returns up to 20 Local Services advertisers.

## `services` (type: `array`):

Combine with Cities to build queries automatically, e.g. plumber, electrician, roofer, HVAC, locksmith, garage door.

## `cities` (type: `array`):

US cities, e.g. "Austin TX", "Denver CO". Every service is crossed with every city.

## `requirePhone` (type: `boolean`):

Drop advertisers with no phone number. Dropped rows are not charged.

## `minRating` (type: `integer`):

Skip advertisers rated below this (1-5). 0 = no minimum.

## `minReviews` (type: `integer`):

Skip advertisers with fewer reviews than this. 0 = no minimum.

## `maxItems` (type: `integer`):

Stop after this many. 0 = unlimited. Each query yields up to 20, so use more queries for more rows.

## `proxyConfiguration` (type: `object`):

MUST be residential US. Google serves Local Services results based on the requesting IP's location — a datacenter or non-US IP returns a valid page with ZERO advertisers. The default is correct; change it only if you know what you are doing.

## Actor input object example

```json
{
  "queries": [
    "plumber Austin TX"
  ],
  "services": [],
  "cities": [],
  "requirePhone": false,
  "minRating": 0,
  "minReviews": 0,
  "maxItems": 20,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `businesses` (type: `string`):

The dataset of scraped Local Services advertisers.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "queries": [
        "plumber Austin TX"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/google-local-services-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "queries": ["plumber Austin TX"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/google-local-services-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "queries": [
    "plumber Austin TX"
  ],
  "maxItems": 20
}' |
apify call scrapersdelight/google-local-services-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/google-local-services-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/wbm0oZ8zm5g50PRMT/builds/IUjPsLOeUDazWazcX/openapi.json
