# YellowPages Canada Leads Scraper — Phone, Address, Website (`scrapersdelight/yellowpages-canada-scraper`) Actor

Scrape Canadian business leads from YellowPages.ca by what + where: name, phone, full postal address with postcode, website, category, rating and geo. Every result page walked contiguously — 100% of a 1,749-result search returned. No login. Export JSON/CSV/Excel.

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

## Pricing

$3.00 / 1,000 lead 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/platform/actors/running/actors-in-store#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

## 🇨🇦 YellowPages Canada Leads Scraper — Phone, Address, Website

**Turn [yellowpages.ca](https://www.yellowpages.ca) — Yellow Pages Canada / YP.ca — into a clean
Canadian B2B lead list.** Search any business type in any Canadian city or postal code and get one
row per business: **name, phone (E.164 + as displayed), full postal address with postcode, website,
YP category, star rating, review count, geo coordinates** and, optionally, **opening hours, services,
payment methods, languages and brands carried**.

At **$3 per 1,000 leads with no per-run start fee**, this is the cheapest way to pull YellowPages.ca
leads on Apify. No login, no API key. Paste a *what* and a *where* and run.

```json
{
  "what": "plumber",
  "where": "Toronto ON",
  "maxItems": 20,
  "maxPages": 1,
  "includeCompanyDetails": false,
  "requestConcurrency": 5
}
```

That block is literally the input this Actor ships with. Click **Try for free** → **Start**. A run on
those shipped defaults returned **20 rows in 5.6 seconds** — phone 100%, address 85%, postal code 80%,
category 100%, website 75% — and cost **$0.06** (20 leads × $0.003, billed exactly 20 events).

***

### 🧩 The wedge: this one actually paginates

YellowPages.ca is easy to fetch and hard to *enumerate*, and it is the enumeration that scrapers on
this site keep getting wrong. Here is the trap, stated plainly:

> Every YP.ca result page embeds a JSON blob ending in `"nextPage" : "N+1"`.
> **That field never terminates.** A search with 50 pages of results still reports a `nextPage` on
> page 52, 53, 54 … forever. Anything that paginates by following `nextPage` runs until it is killed,
> burning your compute on empty pages.

The real stop signal is a page that renders **zero result cards while still returning HTTP 200**.
This Actor paginates on the path segment (`/search/si/<page>/<what>/<where>`), dedupes on YP's own
merchant id, and stops on the first empty page.

**Measured, on the live site, through the Apify proxy, on 2026-08-15:**

| | |
|---|---|
| Contiguous walk of `plumber` / `Toronto ON` | **pages 1 → 51, 51/51 HTTP 200 (100%)** |
| Cards returned across those pages | **1,749** |
| Results YellowPages.ca itself reports for that search | **1,749** — an exact match, nothing skipped |
| Unique merchant ids | 1,747 → **0.11% duplicate rate** |
| What those 2 duplicates were | sponsored cards on page 1 repeating an organic card on page 1 — both removed by id dedupe *before* anything is billed |
| Where it stopped | page 51 rendered 0 cards; the result counter flipped to `(0 Result)` |
| Merchant detail pages | **33/33 HTTP 200 (100%)** |

The run also asserts uniqueness in-process: if the number of rows pushed ever exceeds the number of
distinct merchant ids pushed, the run **throws** instead of handing you a dirty dataset you paid for.

#### ⚠️ How deep YellowPages.ca will actually let you go

**Measured 2026-08-18:** YP.ca serves at most **~60 result pages (~2,100 businesses) per search**,
no matter how large a total it prints at the top of the page. A contiguous walk of
`restaurants / Ontario` — a search the site labels **25,633 results** — returned **2,071 unique
businesses across 61 pages**, and page 61 rendered zero cards. `plumber / Toronto ON` (1,751 results)
fits under that ceiling and came back complete: **1,748 unique businesses, page 51 empty**.

So: any search whose total is **under ~2,100 comes back complete**. To pull a market bigger than
that, **split the query** — by city instead of province, or by a narrower category (`italian
restaurant`, `sushi`, `pizza` rather than `restaurants`) — and merge the datasets. The Actor logs the
site's own reported total (`Site reported N total results`) on every run, so a 1-page probe tells you
in advance whether one query will cover your market.

### 📤 What you get — one row per business

| Field | Description |
|---|---|
| `merchant_id` | YP.ca's own listing id — the stable key this Actor dedupes on |
| `name` | Business name |
| `phone` | Primary phone in E.164, e.g. `+14166998623` |
| `phone_display` | Phone as YP.ca shows it, e.g. `416-699-8623` |
| `phones` | Every number on the listing (main line, toll-free, second location) |
| `address` | Full postal address as displayed |
| `street` / `city` / `province` / `postal_code` | The address, split |
| `country` | Always `CA` |
| `category` | Primary YP category, e.g. *Plumbers & Plumbing Contractors* |
| `categories` | Every category the business is listed under |
| `services` | Services the business advertises |
| `description` | The business's own blurb |
| `website` | Business website, decoded out of YP's `/gourl/` redirect (no extra request) |
| `rating` / `review_count` | YP star rating (whole stars, as the site displays) and review count |
| `open_status` | *Open now* / *Closed now* / *Open 24 hours* at scrape time |
| `latitude` / `longitude` | Geo coordinates |
| `is_ad` | `true` when YP.ca served the card as a paid placement |
| `detail_url` | The YellowPages.ca listing URL |
| `scraped_at` | ISO timestamp |
| **With `includeCompanyDetails` on** | `opening_hours` (per weekday), `payment_methods`, `languages`, `brands`, exact geo, the full services and category lists |

Rows without a name and without at least a phone **or** an address are dropped, so you never pay for
an empty lead.

### 📊 Measured field fill — read this before you buy

These are counted on real rows, not estimated. **Sorted descending; sparse fields in bold.**

**Shipped default run (`plumber` / `Toronto ON`, page 1, 20 rows):**

| Field | Fill |
|---|---|
| `phone` | 100% |
| `category` | 100% |
| `address` | 85% |
| `postal_code` | 80% |
| `website` | 75% |
| `services` | 60% |
| **`latitude` / `longitude`** | **35%** (→ 97% with `includeCompanyDetails` on) |
| **`rating` / `review_count`** | **35%** |

**Full audit, 2026-08-18 — every field counted on two whole datasets, `includeCompanyDetails` ON.**
Left column: a storefront vertical (`restaurants` / Ontario, **500 rows**). Right column: a
service-area trade (`plumber` / Toronto ON, **100 rows**). Same parser, same code path, both runs.

| Field | `restaurants` / Ontario (n=500) | `plumber` / Toronto ON (n=100) |
|---|---|---|
| `merchant_id` · `name` · `country` · `category` · `is_ad` · `detail_url` | 100% | 99–100% |
| `phone` / `phone_display` / `phones` | 99.4% | 100% |
| `address` | 99.4% | **59%** |
| `street` / `city` | 99.4% | **52% / 57%** |
| `province` | 100% | 100% |
| `postal_code` | 99.2% | **55%** |
| `open_status` | 95.6% | **43%** |
| `latitude` / `longitude` | 95.6% | **52%** |
| `website` | 87.4% | **51%** |
| `description` | 85.8% | **28%** |
| `opening_hours` | 84.2% | **43%** |
| `payment_methods` | 83.0% | **36%** |
| `languages` | 81.6% | **35%** |
| `rating` / `review_count` | 70.6% | **18%** |
| `services` | 66.8% | **49%** |
| `brands` | **3.6%** | **8%** |

Without `includeCompanyDetails`, the detail-page fields (`opening_hours`, `payment_methods`,
`languages`, `brands`) are `null`/`[]` by design, and `latitude`/`longitude` drop to ~34% (the result
page's map data covers only about a quarter of its own cards). Everything else is unchanged.

**This split is real and you should plan around it.** YellowPages.ca is a *phone-first* directory.
Storefront categories (restaurants, salons, dentists, retail) publish a full street address on
essentially every listing, at any depth. **Service-area trades (plumbers, electricians, movers,
junk removal) very often publish only a phone number and a province** — no street address anywhere,
not on the result card and not on the detail page either. That was verified directly: a free
"Cheap Junk Removal" listing on page 4 carries `+12262340782` and a detail-page JSON-LD address of
exactly `{"addressRegion":"ON","addressCountry":"CA"}`. Nothing is being missed by the parser; the
merchant simply did not publish it.

Re-verified three ways on 2026-08-18, because "the source doesn't have it" is the kind of claim that
deserves proof:

1. **Same 100 businesses, run twice** — once list-only, once with `includeCompanyDetails` on.
   `address` fill was **59.0% both times**: opening every detail page added exactly **zero** addresses.
2. **Raw bytes of an address-less listing** (`A Smith Plumbing, Drain And Heating`, 320 KB of HTML):
   the strings `streetAddress`, `addressLocality` and `coordinates` occur **0 times** on the page.
   Its JSON-LD carries a phone, hours, 20 services, payment methods and a language — and no address.
3. **The same parser on a storefront vertical** returns `address` **99.4%** and geo **95.6%** (table
   above). A parser bug cannot be vertical-selective.

Address fill also thins with depth inside one search, because paid advertiser listings are ranked
first and free listings fill the tail.

#### ⚠️ There are no email addresses on YellowPages.ca

**Measured: 0 published email addresses across 40 merchant detail pages.** YP.ca routes contact
through an on-site message form, not a mailbox. This Actor therefore ships **no `email` column at
all**, rather than a column that is silently always empty. If you need emails, run the `website`
column returned here through an email-finder / website-contact scraper as a second step — that is a
different job on a different source, and pretending otherwise would just be selling you nulls.

### ⚙️ Input

| Input | What it does |
|---|---|
| `what` | Business type, category or business name — the site's *what* box. `plumber`, `restaurants`, `dentists`, `auto repair`… |
| `where` | The site's *where* box. All three forms verified working: a city (`Toronto`), a city + province (`Toronto ON`, `Vancouver BC`), or a postal code (`M5V 2T6`). |
| `maxItems` | Stop after this many leads. `0` = everything, up to the safety cap. |
| `maxPages` | How many result pages to walk (**35 businesses per page**). The Actor stops by itself on the first empty page, so you can set this high without wasting requests. |
| `includeCompanyDetails` | Off by default. Opens each business's YP page for hours, exact geo, full services, payment methods, languages and brands. Costs one extra request per lead. Does **not** add an email — there isn't one. |
| `proxyConfiguration` | Apify proxy (datacenter) by default — measured clean, see below. |
| `requestConcurrency` | Parallel detail-page requests. Only matters when details are on. |

#### Sizing a run

35 businesses per page. `plumber / Toronto ON` is 1,748 reachable businesses = 51 pages = **$5.24**
for the complete list. `restaurants / Vancouver BC` is 3,365 reported — of which the site will serve
you the first ~2,100. The Actor logs the site's own reported total (`Site reported N total results`)
on every run, so a cheap 1-page run tells you both what the full pull will cost *and* whether your
query is over the ~2,100 ceiling and needs splitting.

**Run time.** The Actor reads the run's own timeout and stops starting new requests before it, pushes
everything it has collected, and finishes **Succeeded** with a status message saying how far it got —
it never gets killed mid-run. Verified: three unbounded `plumber / Toronto ON` runs with detail
fetching on, given a 120-second limit, stopped themselves at ~61 s and delivered 768 / 1,153 / 1,468
leads. The default run timeout is 1 hour, which is far more than any single search needs.

### 🌐 Proxies and anti-bot

YellowPages.ca has no JavaScript wall in front of its result pages. **Apify datacenter proxies are
enough**: the 51-page contiguous walk above was 51/51 HTTP 200 and the 30 detail pages were 33/33,
all through `groups-auto`. Residential is not required and is not the default — you should not pay
residential rates for this site. If you ever do see repeated challenges, switch the Proxy input to
RESIDENTIAL, country CA.

Two site-side quirks the Actor handles for you:

- **Dead listings that redirect to themselves.** A small number of YP.ca merchant pages serve an
  infinite `301 → same URL` loop. Those are skipped, logged, and **never billed**.
- **Unrecognised locations return 0 results, not an error.** A typo'd city produces an empty result
  page rather than an HTTP failure. The Actor tells you so in the **run status message** — quoting
  the site's own reported result count and the exact transport counters — instead of finishing
  silently with an empty dataset. It does **not** fail the run, and **0 rows means $0 charged**.

### 💵 Pricing

**$0.003 per lead — $3 per 1,000 — charged once per row actually delivered.** One event, no
actor-start fee, no per-run minimum. Rows are charged as they are pushed, after deduplication, so a
`maxItems` cap is a real spend cap. The competing YellowPages.ca actors on the Store sit at the same
$0.003 per business *plus* an actor-start fee on top, or at $0.005.

**Your "Maximum cost per run" is a hard stop, and you only pay for leads you actually receive.**
Before each page is delivered the Actor asks the platform how many leads your remaining budget can
still pay for, ships exactly that many complete leads, and then stops — with a status message saying
how many it delivered. It never bills a row it did not deliver, never delivers a row it did not
bill, and never hands you a half-filled lead. Measured on run `RQcewFVt5Odpb4cL0` with a $0.05 cap:
**16 leads delivered, 16 leads charged ($0.048), dataset item count 16, run SUCCEEDED in 4.4 s.**
If the cap is smaller than the price of a single lead, the run scrapes nothing and charges $0.00.

### 🤖 Sibling Actors — same input shape, same price

This is one of a family of national Yellow-Pages-style directory scrapers that all take the same
`what` / `where` / `maxItems` / `maxPages` inputs and all bill $3 per 1,000 leads:

- **PagesJaunes France Leads Scraper** — pagesjaunes.fr, with SIRET / SIREN / NAF
- **PagineGialle Italy Leads Scraper** — paginegialle.it, with P.IVA / codice fiscale

### ⚖️ Legal and compliance

This Actor reads **public directory pages** — the same HTML YellowPages.ca serves to Google.

**robots.txt** (fetched from `https://www.yellowpages.ca/robots.txt` on 2026-08-15): the two paths
this Actor uses — `/search/si/<page>/<what>/<where>` and `/bus/<Province>/<City>/<Name>/<id>.html`
— are not disallowed for `User-agent: *`. The file *does* disallow the site's machine-readable
routes, in its own words: **`Disallow: /search?fmt=JSON*`** and **`Disallow: /ajax/*`**. This Actor
does not touch either of them; it reads the crawlable HTML pages only. There is no `Crawl-delay` for
`User-agent: *` (the only one in the file is `User-agent: Slurp` / `Crawl-delay: 8`).

You are responsible for complying with YellowPages.ca's Terms of Service and, because some listings
are sole traders, with **PIPEDA** and **CASL** when you contact anyone in the output. Scraped
business phone numbers are not consent to send commercial electronic messages.

# Actor input Schema

## `what` (type: `string`):

What to search on YellowPages.ca — a business type, category or business name (the site's 'what' box). Examples: "plumber", "restaurants", "dentists", "auto repair", "wedding planners".

## `where` (type: `string`):

Where to search (the site's 'where' box) — a Canadian city ("Toronto"), a city with its province ("Toronto ON", "Vancouver BC"), or a postal code ("M5V 2T6"). All three forms are verified working. An unrecognised location returns 0 results: the run still finishes successfully, charges nothing, and says so in its status message — it is not reported as a failed run.

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

Stop after this many leads (cost/speed guard). Set 0 for every result across all pages up to the safety cap.

## `maxPages` (type: `integer`):

How many search-result pages to walk (35 businesses per page). Raise it to go deeper — a busy city/category runs to 50+ pages, and the Actor stops by itself on the first page that renders no results.

## `includeCompanyDetails` (type: `boolean`):

Open each business's YellowPages.ca page to add opening hours, exact geo coordinates, the full services list, payment methods, languages spoken and every category. Costs one extra request per lead. Leave it off for a fast name + phone + address + website run — those fields already come from the result page. NOTE: YellowPages.ca publishes no email addresses anywhere, so this does not add an email.

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

Default: Apify proxy (datacenter). Measured clean on YellowPages.ca — a contiguous 51-page walk returned 51/51 HTTP 200 through Apify datacenter IPs, and 33/33 on merchant detail pages. Switch to RESIDENTIAL, country CA only if you see repeated challenges.

## `requestConcurrency` (type: `integer`):

Max parallel requests while fetching business detail pages. Only matters when 'Fetch business details' is on.

## Actor input object example

```json
{
  "what": "plumber",
  "where": "Toronto ON",
  "maxItems": 20,
  "maxPages": 1,
  "includeCompanyDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "requestConcurrency": 5
}
```

# Actor output Schema

## `records` (type: `string`):

The dataset of scraped YellowPages.ca business leads (one item per business).

# 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 = {
    "what": "plumber",
    "where": "Toronto ON",
    "maxItems": 20,
    "maxPages": 1,
    "includeCompanyDetails": false,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/yellowpages-canada-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 = {
    "what": "plumber",
    "where": "Toronto ON",
    "maxItems": 20,
    "maxPages": 1,
    "includeCompanyDetails": False,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/yellowpages-canada-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 '{
  "what": "plumber",
  "where": "Toronto ON",
  "maxItems": 20,
  "maxPages": 1,
  "includeCompanyDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call scrapersdelight/yellowpages-canada-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/yellowpages-canada-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/Kyy3JLHQdMzUQEhuM/builds/wt0uaSs5geMP8Q3oe/openapi.json
