# Yelp Business Scraper & Monitor (ratings, hours, change alerts) (`datahamster/yelp-business-monitor`) Actor

Yelp business scraper: search by term and location or paste business URLs and get flat rows with rating, review count, price range, categories, address, phone, hours, claimed and closed flags. Monitor mode alerts when a rating, review count or open status changes. No login, no Yelp API key.

- **URL**: https://apify.com/datahamster/yelp-business-monitor.md
- **Developed by:** [Viktor Dubnytskiy](https://apify.com/datahamster) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 result items

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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 Business Scraper & Monitor (ratings, hours, change alerts)

Scrape Yelp businesses by search term and location, or by pasting business page URLs, and get one flat row per business with its rating, review count, categories, address and hours. Run the same input in monitor mode on a schedule and you get only the businesses whose rating, review count or open status has moved since the last run.

### What you get (example output)

One row per business. Real values from the example dataset of this actor:

| Field | Example value |
|---|---|
| `name` | `Uncle Benny's Donut and Bagel` |
| `rating` / `reviewCount` | `4.5` / `588` |
| `priceRange` | `$$` |
| `categories` | `["Donuts", "Bagels", "Coffee & Tea"]` |
| `address` / `city` / `region` / `postalCode` | `2049 Irving St` / `San Francisco` / `CA` / `94122` |
| `phone` | `(415) 731-1323` |
| `isClaimed` / `isClosed` / `isAdvertiser` | `true` / `false` / `true` |
| `neighborhoods` / `timezone` | `["Outer Sunset"]` / `America/Los_Angeles` |
| `url` | `https://www.yelp.com/biz/uncle-bennys-donut-and-bagel-san-francisco` |

Also on every row: `bizId`, `alias`, `country`, `hoursByDay` (Yelp's raw week-minute ranges), `searchTerm`, `searchLocation`, `rank`, `source` (`search` or `business`), `scrapedAt`.

### Use cases

- **Reputation monitoring for multi-location brands**: track rating and review count per location and get a webhook the day one of them drops.
- **Local lead lists**: pull the businesses ranked for a term in a city, with categories, address, phone and whether the listing is claimed.
- **Competitive and market research**: compare rating, price range and advertiser status across a category in one neighbourhood.

### How it works

1. Each `searches` entry becomes a Yelp search URL; 10 businesses are returned per page, up to `searchPages`.
2. `businessUrls` are opened directly as business pages, which carry the richer fields (phone, hours, claimed flag, neighbourhoods); `fetchDetails` does the same for every search hit.
3. Requests go through Apify Web Unblocker, which passes Yelp's DataDome protection; no login and no Yelp API key is involved.
4. Rows are deduplicated on the Yelp business id, so overlapping searches are not charged twice.
5. In `monitor` mode the run compares against the previous run of the same task and returns only businesses whose tracked fields changed, with `changedFields` and `previous`, optionally posting a summary to a webhook or Telegram chat.

### Input

| Field | Meaning | Default |
|---|---|---|
| `searches` | `[{"term": "coffee", "location": "San Francisco, CA"}]` | that example |
| `businessUrls` | `yelp.com/biz/<alias>` URLs or bare aliases | empty |
| `searchPages` | Search result pages per search (10 businesses per page) | `1` |
| `fetchDetails` | Open each search hit's business page for phone, hours, claimed flag | `false` |
| `maxItems` | Stop after this many businesses | `20` |
| `mode` | `scrape` or `monitor` (only rating / review-count / status changes) | `scrape` |
| `monitorKey` | State key for monitor mode outside a saved task | empty |
| `webhookUrl` | HTTPS endpoint for the monitor-mode change summary | empty |
| `telegramBotToken` / `telegramChatId` | Send the monitor-mode summary to Telegram | empty |

### Pricing

| Event | Price |
|---|---|
| result | $0.002 per business ($2 per 1,000) |
| monitor-check | $0.006 per monitor run |
| change | $0.002 per changed business |

Charged only for businesses actually pushed. Blocked pages are not charged; three in a row end the run with reason `blocked`.

### Why this actor

- Monitor mode is the point: it returns only what changed (`changedFields`, `previous`) instead of a fresh full dump you have to diff yourself.
- Passes Yelp's DataDome protection through Apify Web Unblocker — no login, no cookies, no Yelp API key.
- Search hits and business pages come back in the same schema, so lists and detail lookups merge cleanly.
- You pay per pushed row; blocked or empty runs cost nothing beyond platform usage, and `RUN_SUMMARY` records the reason (`no_matches`, `blocked`, `rate_limited`, `invalid_input`, `upstream_error`).
- Honest about what Yelp does not expose — see Limits, which are stated rather than hidden.

### Limits

- **Review texts are not included.** Yelp renders reviews client-side through a private GraphQL endpoint that its bot protection blocks; this actor tracks `reviewCount` and `rating` instead and alerts on changes, which is what most monitoring needs.
- Yelp uses DataDome; the actor runs through Apify Web Unblocker, billed by Apify per request on top of results. A search page yields 10 businesses per request.
- `phone`, `isClaimed`, `postalCode` and `hoursByDay` come from business pages — use `businessUrls`, or `fetchDetails` for search hits; on plain search rows they are `null`.
- `hoursByDay` is Yelp's raw structure: minute offsets from the start of the week, not formatted opening times.
- No person-level data: reviewer names and profiles are not collected.

### FAQ

**Can I scrape Yelp without the Yelp Fusion API key?**
Yes. This actor reads public Yelp pages through Apify Web Unblocker; you provide no Yelp credentials and are not bound by the API's daily call quota.

**Does it return Yelp review text?**
No. Yelp serves review bodies from a protected client-side endpoint. What you get is the rating and review count per business, and, in monitor mode, an alert when either moves.

**How do I get alerted when my competitor's rating drops?**
Save the actor as a task with those businesses in `businessUrls`, set `mode: monitor`, schedule it daily and add `webhookUrl` or the Telegram fields. Each run returns only businesses whose rating, review count or open status changed.

### Changelog

- 0.1: initial release — search and business pages, monitor mode.

***

If this actor is useful, please leave a review on its Apify Store page — that is how other people find it. For bugs or a field you need, open a ticket on the **Issues** tab of this actor.

# Actor input Schema

## `searches` (type: `array`):

List of Yelp searches as JSON objects: {"term": "coffee", "location": "San Francisco, CA"}. `term` is what you would type in Yelp's "Find" box (a category or business name); `location` is the "Near" box - a city with state, a neighbourhood or a ZIP code. Example: \[{"term": "coffee", "location": "San Francisco, CA"}].

## `businessUrls` (type: `array`):

Individual Yelp business pages to read, as a full URL or just the alias from it. Business pages carry the full detail (phone, hours, claimed flag, neighbourhoods). Accepted forms: "https://www.yelp.com/biz/uncle-bennys-donut-and-bagel-san-francisco", "uncle-bennys-donut-and-bagel-san-francisco".

## `searchPages` (type: `integer`):

How many search result pages to request per search, 1 to 50. One page is 10 businesses, so searchPages 3 covers the first 30 hits. Example: 1.

## `fetchDetails` (type: `boolean`):

true opens the business page of every search hit to add phone, opening hours, claimed flag and neighbourhoods, at the cost of one extra request (and unblocker traffic) per business. false keeps only what the search page shows. Example: false.

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

Stop after this many businesses in total. Integer, example: 20. You are charged only for businesses actually pushed to the dataset.

## `mode` (type: `string`):

"scrape" returns every business found. "monitor" returns only businesses whose rating, review count or open status changed since the previous run of the same task, and adds changeType, changedFields and previous. Example: "scrape".

## `monitorKey` (type: `string`):

Optional name for the monitor-mode state when the actor is not started from a saved task; runs sharing this key compare against each other. Example: "sf-coffee".

## `webhookUrl` (type: `string`):

Optional HTTPS endpoint that receives a POST with a JSON summary of the changed businesses at the end of a monitor-mode run. Example: "https://hooks.example.com/yelp".

## `telegramBotToken` (type: `string`):

Optional Telegram bot token (from @BotFather) used to send the monitor-mode change summary. Format: "123456789:AA...".

## `telegramChatId` (type: `string`):

Optional Telegram chat id that receives the monitor-mode summary; use a negative id for a group. Example: "-1001234567890".

## Actor input object example

```json
{
  "searches": [
    {
      "term": "coffee",
      "location": "San Francisco, CA"
    }
  ],
  "searchPages": 1,
  "fetchDetails": false,
  "maxItems": 20,
  "mode": "scrape"
}
```

# Actor output Schema

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

All pushed rows (dataset, JSON)

## `resultsTable` (type: `string`):

Dataset in the Console viewer

## `runSummary` (type: `string`):

RUN\_SUMMARY record (pushed, skipped, emptyReason)

# 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 = {
    "searches": [
        {
            "term": "coffee",
            "location": "San Francisco, CA"
        }
    ],
    "searchPages": 1,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("datahamster/yelp-business-monitor").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 = {
    "searches": [{
            "term": "coffee",
            "location": "San Francisco, CA",
        }],
    "searchPages": 1,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("datahamster/yelp-business-monitor").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 '{
  "searches": [
    {
      "term": "coffee",
      "location": "San Francisco, CA"
    }
  ],
  "searchPages": 1,
  "maxItems": 20
}' |
apify call datahamster/yelp-business-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datahamster/yelp-business-monitor"
        }
    }
}
```

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/CHwFvNOGKVynRR8nw/builds/MdjnAYQvmfxLG5MEg/openapi.json
