# Yellow Pages US Scraper - Business Listings & Phones (`dami_studio/yellow-pages-us-scraper`) Actor

Search US Yellow Pages by trade and city or ZIP and get one row per business: name, phone, street address, city, state, ZIP, website, categories, years in business and the listing id. Ratings where Yellow Pages has them, roughly a third of listings. $0.60 per 1,000.

- **URL**: https://apify.com/dami\_studio/yellow-pages-us-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 business scrapeds

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

## Yellow Pages US Scraper

Tell it what you want and where in the United States. "plumber" in "Austin, TX", "dentist" in "90210". You get one row per listing: business name, phone, street address, city, state, ZIP, website, the categories yellowpages.com files the business under, its star rating and review count, how many years it has been trading, and the link to the listing. No account to create, no browser involved.

Every search term is run against every location, so three trades across five cities is one run rather than fifteen.

### Billing

**$0.60 per 1,000 businesses**, plus a **$0.001 start fee per run** (billed per gigabyte of run memory, so exactly that on the default 1 GB).

Same rate on every plan, free or paid. No volume tiers, no minimum spend, no subscription.

| Businesses | Total cost |
|---|---|
| 100 | $0.061 |
| 1,000 | $0.601 |
| 10,000 | $6.001 |
| 100,000 | $60.001 |

One `business-scraped` event per business row written to the dataset. Nothing else is metered per row. A business that turns up twice in one run, once as a sponsored placement and again as an ordinary result, or under two of your search terms, is returned once and charged once.

Free: the sample row an empty run returns, and every diagnostic row. A blocked target, a dead URL, a search that matched nothing. Those all carry `"charged": false`. A run that finds nothing costs the start fee and no more.

No price is attached to `apify-default-dataset-item`, so a diagnostic or a notice can't quietly bill you. Filter the dataset on `"charged": true` and the number of rows you get back is the number of events you were billed for.

### Input

```json
{
  "searchTerms": [
    "plumber",
    "hvac contractor"
  ],
  "locations": [
    "Austin, TX",
    "78704"
  ],
  "maxItems": 120,
  "includeAds": false
}
```

| Field | What it does |
|---|---|
| `searchTerms` | What to look for, the way you would type it into the Yellow Pages search box: a trade, a category or a business name. Up to 20 per run. |
| `locations` | Where to look: a US "City, ST" such as `Austin, TX`, or a 5-digit ZIP such as `90210`. Up to 20 per run. Every term is run against every location, and the run covers up to 30 searches in total. |
| `maxItems` | Total rows across all searches. The budget is shared evenly, so six searches and 300 rows gives you 50 of each. Results arrive 30 to a page. Default 30, hard ceiling 3,000. |
| `includeAds` | Off by default. Turn it on to also get the sponsored placements at the top of each page. They are real local businesses and arrive with `"isAd": true`. |
| `searchUrls` | Optional. Paste yellowpages.com search or category URLs directly instead of terms and locations. Anything that is not a yellowpages.com URL is skipped with a free diagnostic row. |
| `proxyUrls` | Leave empty. Fill it in only if you want the traffic to leave through proxy servers you already pay for, as `http://user:pass@host:port`. |

Run it with **empty input** and you get one clearly labelled sample row, free, so you can see the output shape before you spend anything.

### Output

One row per business. A real row from a real run:

```json
{
  "ok": true,
  "charged": true,
  "recordType": "business",
  "name": "Clarke Kent Plumbing",
  "phone": "(512) 766-0970",
  "street": "1408 W Ben White Blvd",
  "city": "Austin",
  "state": "TX",
  "zip": "78704",
  "serviceArea": null,
  "website": "http://www.clarkekentplumbing.com/",
  "categories": [
    "Plumbers",
    "Plumbing-Drain & Sewer Cleaning"
  ],
  "rating": 2.87,
  "ratingIsApprox": false,
  "reviewCount": 15,
  "yearsInBusiness": 40,
  "openStatus": "closed now",
  "listingUrl": "https://www.yellowpages.com/austin-tx/mip/clarke-kent-plumbing-10674347",
  "ypid": "10674347",
  "isAd": false,
  "rank": 2,
  "searchTerm": "plumber",
  "location": "Austin, TX",
  "page": 1,
  "totalResults": 489,
  "searchUrl": "https://www.yellowpages.com/search?search_terms=plumber&geo_location_terms=Austin%2C+TX",
  "scrapedAt": "2026-08-16T19:50:12.450Z"
}
```

#### Field notes

- `name` — the business name exactly as Yellow Pages prints it, including the "DDS" and "Inc" suffixes people actually search for.
- `phone` — the primary number on the listing, in US display format. Present on effectively every listing.
- `street` — street line only. A listing filed against a city rather than an address has no street line, and neither does a mobile or service-area business. Both come back `null` rather than a guess.
- `serviceArea` — set instead of `street` when the listing says something like "Serving the Austin Area", which is how Yellow Pages files tradespeople with no storefront.
- `city`, `state`, `zip` — the address split into its parts. Where the card shows only a city line, these are still filled in and `street` is left null.
- `website` — the external site the listing links to, or `null`. Links that point back into yellowpages.com rather than at the business are dropped rather than passed through as a website.
- `categories` — every category the listing is filed under, most specific first. This is how Yellow Pages itself files the business, so it beats guessing from the name.
- `rating` — the average star rating. Most listings have never been reviewed, so this is `null` more often than not. Measured across 255 rows from a real HVAC run, `rating` and `reviewCount` were filled on 37%. That's the source's coverage, not a gap in the scrape.
- `ratingIsApprox` — `false` when the exact average was available, `true` on the handful of cards where only the rounded half-star display exists. Look at this before you sort by rating.
- `reviewCount` — how many reviews that average is built on. A 5.0 from two reviews is not a 5.0 from two hundred.
- `yearsInBusiness` — years trading, where the listing claims it. A rough proxy for how established a business is.
- `openStatus` — what the card said at the moment it was read ("open now", "closed now", "open 24 hours"), or `null` when no hours are on file.
- `listingUrl` — the listing page, with click-tracking parameters stripped, so the same business gives the same URL on every run.
- `ypid` — the yellowpages.com listing id. Stable, and the right key to deduplicate on when you re-run.
- `rank` — position in the ordinary results for that search, starting at 1. Null on sponsored rows.
- `totalResults` — how many listings Yellow Pages says the whole search has, not how many you asked for.
- `searchTerm`, `location`, `searchUrl` — which of your searches produced this row, so a multi-term run is easy to split apart afterwards.

Diagnostic rows carry `"_diagnostic": true`, `"charged": false` and an `errorCode` you can switch on: `NO_RESULTS` when a search matched nothing, `INVALID_TARGET` when a location was not recognised or a supplied URL was not a yellowpages.com URL, `BLOCKED` when every address the run tried was refused, `NETWORK` when the site could not be reached at all, and `TIME_BUDGET` when the run ran out of time before reaching a search. Each carries a plain-English `error` and the search it belongs to. The sample row carries `"_sample": true` and turns up only when the input had no search terms and no locations.

### How it works

- It reads the ordinary logged-out search page, the same one a person sees, and takes every field off it. There is no second request per business, which is why a row costs a fraction of a page view rather than a page view of its own.
- Requests leave through a pool of rotating addresses that aren't billed by the gigabyte, so the traffic cost is already inside the price you see.
- Yellow Pages refuses a share of requests when it's busy, and retrying harder makes that worse. A refused page is asked for again a little later, a handful of times, with the gap growing each time. That returns the page more often than hammering it does.
- Pages are fetched several at a time rather than one after another, each from its own address.
- The exact star average and review count come from the structured data the page publishes for search engines, so the rating is the real average rather than the rounded half-star the page draws.
- Each search is paged 30 listings at a time until your row budget is reached, and listing ids already seen in the run are skipped before anything is charged.

### Limitations

- United States only. A non-US location comes back as an uncharged `INVALID_TARGET` row rather than a guess.
- Yellow Pages caps a search at 100 pages and reports at most 3,000 results for a broad term. For more than that, split the search by ZIP or by narrower categories rather than asking for a bigger number.
- Ratings and review counts come from Yellow Pages' own reviews, and most listings have never been reviewed. On a measured 255-row run only 37% carried a rating, so plan for `rating` and `reviewCount` to be null on most rows. That's the source, not the scrape.
- Not every listing has a website, and unclaimed listings often have no street line either. Those fields come back null rather than filled with a guess.
- Email addresses are not on the search results page and are not returned. Neither are opening-hours tables, photos or review text.
- Sponsored placements are excluded unless you ask for them. When you do, they are marked `"isAd": true` and can repeat businesses that also appear organically. Those are deduplicated and charged once.
- `openStatus` is a snapshot at read time and says nothing about the hours around it.
- A busy hour can still cost you the odd page. The run retries it, spaced out, from several addresses; if it still cannot get through you get an uncharged `BLOCKED` row for that search rather than a silent gap. Across two batteries of 50 varied searches each, every search returned listings.
- Ceilings per run: 20 search terms, 20 locations, 30 searches in total and 3,000 rows. Split larger jobs across runs.

### Questions

**What counts as a location?**

A US city with its state (`Austin, TX`, `Beverly Hills, CA`) or a 5-digit ZIP such as `90210`. A state on its own works but returns Yellow Pages' statewide ordering, which is rarely what you want. If the site does not recognise what you typed you get one uncharged `INVALID_TARGET` row saying so, and the run carries on to your other searches.

**How many rows will one search actually give me?**

Thirty per page, and the run keeps paging until it reaches your `maxItems` or Yellow Pages runs out of listings. A common trade in a large metro has hundreds; a narrow term in a small town may have five. `totalResults` on every row tells you how deep that particular search could have gone.

**Do I get charged for ads?**

Only if you ask for them. `includeAds` is off by default, so an ordinary run charges you for ordinary directory results and nothing else. With it on, sponsored rows are charged like any other row and are marked `"isAd": true` so you can drop them later if you change your mind.

**What happens if a search matches nothing?**

You get one uncharged diagnostic row for that search with `errorCode: "NO_RESULTS"`, and the run carries on to your other searches. You are never billed for a search that returned nothing.

**Will the run fail if Yellow Pages blocks it?**

No. It retries across rounds and across addresses, and if it still cannot get through you get an uncharged `BLOCKED` row explaining that, with the run still finishing as succeeded. A failed run would still bill you the start fee, which would mean paying to be told something went wrong.

**Do I need a proxy?**

No. The run brings its own egress and the cost of it is already inside the price you see. The `proxyUrls` field exists only for callers who specifically want traffic to leave through servers they already own.

**Can I run this on a schedule?**

Yes. Nothing is held between runs, so the same input is safe to repeat. Use `ypid` to work out which listings are new since last time. `listingUrl` is stable across runs for the same reason.

**Can I find businesses with no website?**

Filter the rows on `website == null`. The field is only filled when the listing actually links out to one, so a null there means the directory has no site on file for that business.

# Actor input Schema

## `searchTerms` (type: `array`):

What to look for, the way you would type it into the directory's search box: a trade, a category or a business name. Examples: "plumber", "dentist", "coffee shop", "personal injury lawyer". Up to 20 per run.

## `locations` (type: `array`):

Where to look, as a US "City, ST" such as "Austin, TX" or a 5-digit ZIP code such as "90210". Every search term is run against every location, so 3 terms and 2 locations is 6 searches. Up to 20 per run, 30 searches per run in total.

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

Total rows to return across every search. The budget is shared evenly between the searches, so 6 searches and 300 rows gives you 50 of each. Results are paged 30 at a time. Keep it low while you are testing - you pay per business.

## `includeAds` (type: `boolean`):

Off by default, so you are only charged for the ordinary directory results. Turn it on to also get the sponsored placements at the top of the page; they are real local businesses and are marked with "isAd": true. A business that appears both as an ad and as an ordinary result is still only returned, and charged, once.

## `searchUrls` (type: `array`):

Instead of terms and locations, paste yellowpages.com search or category URLs directly - for example https://www.yellowpages.com/search?search\_terms=plumber\&geo\_location\_terms=Austin%2C+TX or https://www.yellowpages.com/austin-tx/plumber. Anything that is not a yellowpages.com URL is skipped with a free diagnostic row.

## `proxyUrls` (type: `array`):

Leave this empty. By default the run rotates a large pool of addresses that cost you nothing per gigabyte. Fill it in only if you specifically want the traffic to leave through proxy servers you already pay for, in the form http://user:pass@host:port.

## Actor input object example

```json
{
  "searchTerms": [
    "plumber"
  ],
  "locations": [
    "Austin, TX"
  ],
  "maxItems": 30
}
```

# Actor output Schema

## `results` (type: `string`):

Every row in the default dataset: name, phone, street, city, state, zip, website, categories, rating, reviewCount, yearsInBusiness, listingUrl, searchTerm, location, serviceArea, openStatus, isAd, rank, ypid, ratingIsApprox, page, searchUrl, totalResults. An empty, blocked or unmatched run returns a single uncharged row explaining what happened instead.

# 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 = {
    "searchTerms": [
        "plumber"
    ],
    "locations": [
        "Austin, TX"
    ],
    "maxItems": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/yellow-pages-us-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 = {
    "searchTerms": ["plumber"],
    "locations": ["Austin, TX"],
    "maxItems": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/yellow-pages-us-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 '{
  "searchTerms": [
    "plumber"
  ],
  "locations": [
    "Austin, TX"
  ],
  "maxItems": 30
}' |
apify call dami_studio/yellow-pages-us-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/yellow-pages-us-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/hSJIHIrZfc6bAI4UR/builds/Bg1QAsOW11ZRJ35eP/openapi.json
