# Yelp Search Scraper — Find Businesses by Location (`thenetaji/yelp-search-scraper`) Actor

Search Yelp businesses by term and location and export Yelp's own ranking, with paid placements labelled separately from organic results. Optionally add each unhydrated result's full business profile.

- **URL**: https://apify.com/thenetaji/yelp-search-scraper.md
- **Developed by:** [The Netaji](https://apify.com/thenetaji) (community)
- **Categories:** Business, Lead generation, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.28 / 1,000 search results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Yelp Search Scraper

The Actor runs a Yelp business search by search term and location and exports one row per result, in Yelp's own ranking order, with paid placements labelled separately from organic listings.

### Accepted input

| Field | Type | Default | Description |
|---|---|---|---|
| `find_desc` | string | — (required) | What to search for — a category such as `"coffee"` or `"plumbers"`, or a business name. |
| `find_loc` | string | — (required) | Where to search, written any way Yelp accepts it: `"San Francisco, CA"`, a zip code such as `"94110"`, or a neighborhood such as `"Mission District, San Francisco"`. |
| `start` | integer | 0 | The result offset to start at. Yelp pages in tens, so this must be a multiple of 10; it exists to resume a search past where an earlier run stopped, not to skip forward one result at a time. |
| `maxItems` | integer | 100 | Maximum rows to save. `0` removes the limit. Rows come roughly 10 to 12 per request, since a section of paid placements can ride alongside the ten organic slots. |
| `enrichBusinessDetails` | boolean | `false` | For every result Yelp did not hydrate, fetches that business's own page and attaches it under `details`. One extra request per unhydrated result, billed only when that request succeeds. |

```json
{
  "find_desc": "coffee",
  "find_loc": "San Francisco, CA",
  "maxItems": 50
}
```

### Response fields

| Field | Contents |
|---|---|
| `business_id` | Yelp's internal id for the business. Present on every row, hydrated or not. |
| `alias` | The business's Yelp slug — the input `/v1/yelp/business` (the Yelp Business Scraper) takes. Taken from the row itself when Yelp hydrated it, otherwise parsed out of the row's own link. |
| `url` | The row's link on Yelp. |
| `is_ad` | Whether the row is a paid placement. |
| `rank` | The row's position within the result set, as Yelp reports it. |
| `snippet` | The review excerpt Yelp shows under the row, when it ships one. |
| `is_hydrated` | Whether Yelp attached a business record to this row. |
| `business` | Business data — name, phone, rating, exact review count, categories, formatted address, price range, and more — present when `is_hydrated` is true, null otherwise. |
| `details` | This result's full business page, the same shape the Yelp Business Scraper returns. Present only when `enrichBusinessDetails` is enabled and the fetch for that row succeeded. |

```json
{
  "business_id": "kHFPKRnQPfBnBMr4XvQKcQ",
  "alias": "kissaten-hifi-san-francisco",
  "url": "https://www.yelp.com/biz/kissaten-hifi-san-francisco?osq=coffee",
  "is_ad": false,
  "rank": 3,
  "is_hydrated": false,
  "business": null
}
```

### Behaviour of `is_hydrated` and `business`

Yelp fills in a full business record for a search row only for paid placements. Organic results — the large majority of any search — carry `business_id` and `alias` and nothing else; `searchResultBusiness` is null in Yelp's own response, to both a browser and a plain request equally. This is not a gap in a particular run; it is how Yelp serves search results to a logged-out client. `is_hydrated` states which case a given row is before `business` is read, and `enrichBusinessDetails` exists specifically to close that gap by fetching each unhydrated row's own page.

### Behaviour of pagination

`start` moves in steps of 10, matching Yelp's own page size, and a run continues automatically: it stops when Yelp's reported `total_results` has been reached, when a request returns no rows, or when `maxItems` is reached, whichever comes first. `start` set on the input is only the starting point for a fresh run; an interrupted run resumes at the exact row it stopped on rather than restarting from `start`.

### Frequently asked questions

**Why is `business` null on almost every row?**
Because Yelp withholds it from organic results at the server level, as explained above. Set `enrichBusinessDetails` to fetch each such row's full page instead, or pass the row's `alias` to the Yelp Business Scraper directly.

**Why did a page return more than 10 rows?**
`start` advances by Yelp's own page size of 10, but a response can carry more rows than that when a paid placement is inserted alongside the ten organic ones rather than replacing one of them. The offset still advances by 10 regardless of how many rows a given page held.

**Does `enrichBusinessDetails` re-fetch rows that already have a business record?**
No. It is skipped for any row where `is_hydrated` is already true, since those rows already carry a business record for free; only unhydrated rows are fetched and billed.

**What happens to a row whose enrichment request fails?**
The row is kept exactly as it was, without a `details` field, and nothing is charged for it; a failure on one row does not interrupt the rest of the run.

### Related

[Yelp Business Scraper](https://apify.com/thenetaji/yelp-business-scraper) fetches a business's full profile directly by its alias, for when the alias is already known and a search is not needed.

# Actor input Schema

## `find_desc` (type: `string`):

What to search for — a category ("coffee", "plumbers") or a business name.

## `find_loc` (type: `string`):

Where to search, written any way Yelp accepts it: "San Francisco, CA", a zip code like "94110", or a neighborhood like "Mission District, San Francisco".

## `start` (type: `integer`):

Row to start the search at. Yelp pages in tens, so this must be a multiple of 10 — use it to resume a search past where an earlier run stopped rather than to skip forward one result at a time.

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

Maximum rows to save. Set 0 for no limit. Rows come roughly 10-12 to a page.

## `enrichBusinessDetails` (type: `boolean`):

For every result Yelp did not hydrate — normal for organic results, see the Business field below — fetch that business's own page and attach it. One extra request per unhydrated result, billed only when it succeeds.

## Actor input object example

```json
{
  "find_desc": "coffee",
  "find_loc": "San Francisco, CA",
  "start": 0,
  "maxItems": 20,
  "enrichBusinessDetails": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

All records scraped by this run

# 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 = {
    "find_desc": "coffee",
    "find_loc": "San Francisco, CA",
    "start": 0,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/yelp-search-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 = {
    "find_desc": "coffee",
    "find_loc": "San Francisco, CA",
    "start": 0,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/yelp-search-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 '{
  "find_desc": "coffee",
  "find_loc": "San Francisco, CA",
  "start": 0,
  "maxItems": 20
}' |
apify call thenetaji/yelp-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thenetaji/yelp-search-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/LA9gpycLA41dvwb6C/builds/a9BkgJLEQFNjsLxYE/openapi.json
