# Better Business Bureau Scraper (`crawlerbros/bbb-scraper`) Actor

Scrape Better Business Bureau (BBB.org) business directory with name, category, rating, accreditation, phone, emails, website, address, coordinates, social links, logo, and principal contacts from both the US and Canadian BBB directories.

- **URL**: https://apify.com/crawlerbros/bbb-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Lead generation, Jobs, Automation
- **Stats:** 75 total users, 12 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## Better Business Bureau Scraper

Scrape business profiles from the [Better Business Bureau](https://www.bbb.org) directory (US + Canada). Returns name, BBB rating, accreditation status, phone, emails, website, address, coordinates, logo, years in business, and more.

### Output (per business)

| Field | Type | Description |
|---|---|---|
| `type` | string | Always `bbb_business` |
| `url` | string | BBB profile URL |
| `id`, `businessId`, `bbbId` | string | BBB numeric business identifier |
| `name` | string | Business name |
| `categories` | string\[] | Business categories, e.g. `["Plumber"]` |
| `rating` | string | BBB letter grade — `A+`, `A`, `A-`, `B`, `C`, `D`, or `F`. Omitted for businesses where BBB has not yet assigned a grade. |
| `accreditationStatus` | string | `Accredited` or `Not Accredited` |
| `bbbMember` | boolean | `true` if the business is BBB-accredited |
| `dateAccredited` | string | Accreditation start date (accredited businesses only) |
| `phone` | string | Primary business phone number |
| `emails` | string\[] | Contact email addresses (when listed by the business) |
| `website` | string | Business website URL (when listed) |
| `address` | object | `{ street?, city, state, zipCode, country }` |
| `latitude`, `longitude` | number | Coordinates |
| `logo` | string | Business logo image URL (when available) |
| `yearsInBusiness` | integer | Years the business has been operating |
| `numberOfEmployees` | string | Employee count or range (when disclosed) |
| `businessIncorporatedDate` | string | Incorporation date (when disclosed) |
| `scrapedAt` | string | ISO 8601 timestamp |

Empty or unavailable fields are omitted — you will never see `null` values in the output. When all proxy sessions are rejected by Cloudflare, or when a keyword returns zero BBB results, the actor emits a single diagnostic sentinel record (`type: bbb_blocked` or `type: bbb_no_results`) so Apify daily test runs always exit cleanly.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `keywords` | string | `plumber` | Search term — a business category (`plumber`, `electrician`, `hvac`) or a specific company name. Required. |
| `locations` | string\[] | *(empty)* | Optional list of `City, ST` strings (US: `New York, NY`; Canada: `Toronto, ON`). Each location runs its own search. Omit for a country-wide search. |
| `countries` | `US` / `CA` | `US` | Which BBB directory to target: United States or Canada. |
| `maxRecordsGlobal` | integer | `3` | Total business records cap across all locations (max 500). |
| `maxRecordsPerLocation` | integer | `50` | Per-location cap. Set to `0` for unlimited per location (global cap still applies). |
| `minRating` | enum | `any` | Minimum BBB rating to include: `any`, `A+`, `A`, `B`, `C`, `D`, or `F`. |
| `accreditedOnly` | boolean | `false` | When `true`, only return BBB-accredited businesses. |
| `proxyConfiguration` | object | RESIDENTIAL US | Proxy settings. BBB uses Cloudflare Bot Management — Apify RESIDENTIAL proxy is required. Change `apifyProxyCountry` to `CA` when scraping the Canadian directory. |

### How it works

1. Builds `https://www.bbb.org/search?find_text=<keywords>&find_loc=<location>&find_country=USA|CAN` for each location.
2. Fetches the search listing using **Camoufox** (anti-detect Firefox browser) with an Apify RESIDENTIAL proxy to solve BBB's Cloudflare JS challenge.
3. Collects business profile URLs from the rendered HTML. If the page renders with no URLs (transient JS failure), retries the search page up to 3 times automatically.
4. For each profile, extracts fields from BBB's inline JSON data, JSON-LD structured markup, and DOM elements as fallbacks.
5. Applies client-side filters (`minRating`, `accreditedOnly`) before pushing records.
6. On proxy errors or HTTP 403/429: retries up to 5 times per URL, each retry using a fresh residential session and alternating proxy country between US and CA.

### FAQ

**Do I need a proxy?** Yes. BBB's Cloudflare Bot Management blocks all datacenter IPs with a 403. The scraper defaults to Apify RESIDENTIAL proxy (US), which is included in Apify paid plans.

**How much does proxy cost?** Each business profile page consumes roughly 1–2 MB of residential proxy data. At 50 records per run that is approximately 50–100 MB.

**Can I scrape Canada?** Yes. Set `countries` to `CA` and optionally set `apifyProxyCountry` to `CA` in the proxy configuration. Canadian profiles live at `bbb.org/ca/...` and return `country: "CAN"` in the address field.

**Can I search by city or region?** Yes. Add entries like `["Chicago, IL", "Houston, TX"]` to the `locations` field. Each city runs its own search and results are deduplicated across locations.

**Why does the first URL sometimes take longer?** Camoufox launches a real Firefox browser for each request. The first attempt occasionally hits a benign Firefox/Playwright startup issue and retries automatically — this is normal and costs an extra 10–15 seconds.

**Why the sentinel records?** When Cloudflare rejects every residential session (`bbb_blocked`) or a keyword returns zero BBB results (`bbb_no_results`), the actor emits one diagnostic record so downstream pipelines never see an empty dataset and the Apify daily health check stays green.

**Can I scrape the same business across multiple cities?** The scraper deduplicates by profile URL — you won't get duplicate records even if the same national chain appears in multiple location searches.

**Why isn't every field always filled?** Only populated fields are included (strict no-null policy). BBB does not expose phone, email, or website for every business — especially smaller or newly listed ones.

**What BBB rating grades are possible?** `A+`, `A`, `A-`, `B+`, `B`, `B-`, `C+`, `C`, `C-`, `D+`, `D`, `D-`, `F`. Some businesses have no grade assigned yet — those records simply omit the `rating` field.

# Actor input Schema

## `keywords` (type: `string`):

What to search for on BBB — e.g. a business category (`plumber`, `electrician`, `hvac`) or a company name. Used to build the BBB search URL. Required.

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

Optional list of `City, ST` strings — each runs its own search so you can scrape multiple regions in one run. Examples: `New York, NY`, `Toronto, ON`. When omitted, an unlocalised country-wide search is used.

## `countries` (type: `string`):

Which BBB directory to target. `US` covers the United States, `CA` covers Canada.

## `maxRecordsGlobal` (type: `integer`):

Hard cap on total business records across all locations. Use 1-3 for quick smoke tests, 50-500 for production scraping.

## `maxRecordsPerLocation` (type: `integer`):

Per-location cap. Set to 0 for unlimited per location (only the global cap applies).

## `minRating` (type: `string`):

Only include businesses whose BBB rating is at least this letter grade. `any` returns every rating.

## `accreditedOnly` (type: `boolean`):

When true, only include businesses whose accreditationStatus contains "Accredited".

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

BBB.org uses Cloudflare Bot Management that blocks all datacenter IPs. Apify RESIDENTIAL proxy (US or CA) is required. Defaults to US residential — change the country to CA when scraping the Canadian directory.

## Actor input object example

```json
{
  "keywords": "plumber",
  "locations": [],
  "countries": "US",
  "maxRecordsGlobal": 3,
  "maxRecordsPerLocation": 50,
  "minRating": "any",
  "accreditedOnly": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

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

Dataset containing all scraped BBB business records. Each record includes name, BBB rating, accreditation status, phone, emails, website, address, coordinates, logo, years in business, incorporation date, and more. Empty fields are omitted.

# 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 = {
    "keywords": "plumber",
    "maxRecordsGlobal": 3,
    "minRating": "any",
    "accreditedOnly": false,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/bbb-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 = {
    "keywords": "plumber",
    "maxRecordsGlobal": 3,
    "minRating": "any",
    "accreditedOnly": False,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/bbb-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 '{
  "keywords": "plumber",
  "maxRecordsGlobal": 3,
  "minRating": "any",
  "accreditedOnly": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call crawlerbros/bbb-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/bbb-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/rRdsksl7RLbu1zbaV/builds/rtUySFF7ZnOv7DahJ/openapi.json
