# Angi.com \[Only $0.9💰] Scraper (Resilient Leads & Profiles) (`memo23/angi-scraper`) Actor

\[Only $0.8💰]  Extract high-quality contractor leads from Angi.com (Angie's List). Scrape business names, phone numbers, full addresses, ratings, review counts, and service categories. Just input a ZIP code or category link to instantly get structured local business data in JSON or CSV.

- **URL**: https://apify.com/memo23/angi-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Lead generation, Automation, Agents
- **Features:** Limited permissions
- **Stats:** 56 total users, 5 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.90 / 1,000 results

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Angi.com Scraper (Leads & Profiles)

#### How it works

![How Angi Scraper works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-angi.png)

**Unlock the Full Power of Angi Home Services Data** - The most resilient scraper for extracting high-quality leads, professional profiles, and business details from Angi.com (formerly Angie's List). Whether you're conducting market research, performing competitor analysis, or building a local service database, our scraper delivers precise, structured data at scale.

*"From garage builders and plumbers to roofers and electricians, turn Angi's massive directory into your strategic advantage."*

### Why Use This Scraper?

- **Multiple Starting Points**: Start from the entire US directory, a specific state, a city, a service category, or directly input professional profile URLs.
- **Enterprise-Grade Resilience**: Implements parallel TLS fingerprint racing (`impit`) and intelligent proxy rotation to bypass aggressive Cloudflare and anti-bot blocks.
- **Deep Directory Discovery**: Automatically discovers nested states → cities → categories → professional profiles without manual pagination.
- **Structured Output**: Extract clean, verified business names, addresses, phone numbers, and service categories.
- **Export Ready**: Supports deeply nested JSON for developers or flattened CSV formats for CRM imports.

### Overview

The Angi.com Scraper is designed to seamlessly navigate Angi's Next.js infrastructure. By relying on a custom internal handler rather than standard browser automation, it maintains high throughput and bypasses limitations that block standard crawlers. The output focuses primarily on **business profile rows**, regardless of where the scrape starts.

### Supported Inputs

You can mix and match different types of Angi URLs in the `startUrls` input:

- **US Directory Root**: `https://www.angi.com/companylist/us/` (Scrapes the entire US)
- **Near Me Root**: `https://www.angi.com/nearme/`
- **State Hubs**: `https://www.angi.com/companylist/us/tx/` (Scrapes all cities in Texas)
- **City Hubs**: `https://www.angi.com/companylist/us/al/adamsville/`
- **Category Lists**: `https://www.angi.com/nearme/garage-builders/`
- **Direct Profiles**: `https://www.angi.com/companylist/us/al/cullman/access-garage-doors-of-birmingham-reviews-1.htm`
- **ZIP Codes**: Just input a 5-digit ZIP code like `90210` and the scraper will automatically resolve it to the corresponding city hub!

### Use Cases

| Audience | Example Use Case |
|---|---|
| **Sales & Lead Gen** | Build targeted contact lists of local contractors (e.g., all plumbers in Texas) with verified phone numbers. |
| **Market Researchers** | Analyze service density and categorize businesses by specific niches across different ZIP codes. |
| **Marketing Agencies** | Offer specialized marketing or SEO services to home service professionals found on Angi. |
| **Competitor Analysis** | Track local competitors, their listed services, operating hours, and service areas. |

### How It Works

```mermaid
flowchart LR
    A[Start URLs] --> B{URL Type?}
    
    B -->|Directory/Hub| C[Discover States & Cities]
    B -->|Category| D[Discover Businesses]
    B -->|Profile| E[Direct Extraction]
    
    C --> D
    D --> E
    
    E --> F[Next.js JSON Payload]
    F --> G[(Apify Dataset)]
    
    style A fill:#2e86de,stroke:#000,color:#fff
    style B fill:#feca57,stroke:#000,color:#000
    style E fill:#10ac84,stroke:#000,color:#fff
    style G fill:#ff6b6b,stroke:#000,color:#fff
```

1. **You provide URLs**: Input one or more Angi directory, category, or profile URLs.
2. **Actor categorizes inputs**: The scraper detects the URL type (hub, state, city, category, or profile).
3. **Deep fan-out**: For directory URLs, the scraper automatically navigates the hierarchy to discover individual business profiles.
4. **Data extraction**: The actor fetches the Next.js build data for each profile, extracting robust business information.
5. **Dataset export**: The extracted profiles are saved to your Apify dataset, ready for JSON or CSV download.

### Input Configuration

Here is a typical configuration. The `useInternalHandler` flag is enabled by default to ensure maximum anti-bot resilience.

```json
{
  "startUrls": [
    { "url": "https://www.angi.com/nearme/garage-builders/" }
  ],
  "maxItems": 500,
  "flattenDatasetOutput": true,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"]
  }
}
```

### Output Overview

The scraper's primary dataset items are **business profile rows**.

- **Nested JSON**: Keeps objects intact (e.g., `contactInfo.address.city`).
- **Flattened (CSV-friendly)**: If you enable `flattenDatasetOutput`, nested objects are flattened into dot-separated keys (e.g., `contactInfo_address_city`), ensuring no columns are dropped in Excel.

#### Sample JSON Output

```json
{
  "url": "https://www.angi.com/companylist/us/al/cullman/access-garage-doors-of-birmingham-reviews-1.htm",
  "category": {
    "text": "Garage Builders",
    "title": "Garage Builders",
    "link": "/companylist/us/al/garage-builders.htm"
  },
  "state": {
    "text": "AL",
    "title": "AL Home Services",
    "href": "https://www.angi.com/companylist/us/al/"
  },
  "city": {
    "text": "Adamsville",
    "title": "Adamsville",
    "link": "/companylist/us/al/adamsville/"
  },
  "companyProfile": {
    "businessInfo": {
      "businessName": "Access Garage Doors of Birmingham",
      "amenities": {},
      "hoursInfo": {
        "hoursInfoDetail": [],
        "acceptsAppointments": false
      }
    },
    "contactInfo": {
      "emails": [],
      "phoneNumbers": ["205-555-0123"],
      "address": {
        "street1": "123 Business Way",
        "city": "Cullman",
        "state": "AL",
        "country": "United States",
        "postalCode": "35055"
      }
    },
    "logoUrl": "https://example.com/logo.png"
  }
}
```

#### Key Output Fields

##### Top-Level Group

- **`url`** (String): The direct URL to the professional's profile on Angi.
- **`category`** (Object): Details about the specific service niche.
- **`state`** (Object): Geographic state container.
- **`city`** (Object): Local city container.

##### Company Data

- **`companyProfile`** (Object): The core data container extracted from the profile page.
  - **`businessInfo`** (Object): Contains the official `businessName`, `amenities`, and `hoursInfo`.
  - **`contactInfo`** (Object): Contains arrays for `emails`, `phoneNumbers`, and the physical `address` object.
  - **`slug`** (Object): Canonical URL and aliases.
  - **`logoUrl`** (String): Full URL to the business logo.
  - **`images`** (Array): Gallery of business-provided images or project photos.

*Note: For ease of use in flat exports like CSV, key profile fields are also mirrored at the top level.*

### FAQ

**Can I scrape a specific category nationwide?**
Yes! Input a nationwide category URL like `https://www.angi.com/nearme/garage-builders/`, and the scraper will automatically navigate through all states and cities for that category.

**Does it extract emails?**
The scraper extracts emails only when they are publicly available on the business profile. Many businesses do not expose their email publicly.

**What is the "flattenDatasetOutput" option?**
Angi's data is deeply nested. If you want to download a CSV for Excel or Google Sheets, enable this option. It unpacks nested objects so that every data point gets its own column.

### Support

- For issues or feature requests, please use the **Issues** section of this actor.
- If you need customization or have questions, feel free to contact the author:
  - Author's website: <https://muhamed-didovic.github.io/>
  - Email: <muhamed.didovic@gmail.com>

### Additional Services

- **Request Customization**: Need specific filters or custom fields? <muhamed.didovic@gmail.com>
- **Dataset Purchases**: Looking for a pre-scraped dataset of all Angi pros? Reach out for pricing.
- **API Access**: High-volume API services available for enterprise integrations.

### 🤖 For AI Agents & LLM Apps

Compact reference for AI agents calling this actor via the [Apify MCP server](https://mcp.apify.com) or the Apify API (actor: `memo23/angi-scraper`).

**Purpose:** Scrape Angi.com business profiles from directory, state, city, category or profile URLs (or ZIP codes) — one row per home-service professional, fanning out the directory hierarchy automatically.

**Minimal input:**

```json
{
  "startUrls": [
    { "url": "https://www.angi.com/nearme/garage-builders/" }
  ],
  "flattenDatasetOutput": true,
  "maxItems": 50,
  "maxRequestRetries": 30,
  "attemptsPerRound": 3,
  "enrichEmails": false
}
```

ZIP mode instead of URLs: `{ "zipCodes": ["90210"], "zipCodeCategory": "plumbing" }` — a 5-digit ZIP resolves to its city directory; `zipCodeCategory` is optional (empty = whole city).

**Output:** one dataset row per business profile — `url`, `category {text, title, link}`, `state {text, title, href}`, `city {text, title, link}`, `companyProfile { businessInfo {businessName, amenities, hoursInfo}, contactInfo {emails, phoneNumbers, address {street1, city, state, country, postalCode}}, slug, logoUrl, images }`. With `flattenDatasetOutput` these become dot-separated columns (e.g. `companyProfile_contactInfo_address_city`).

**Behaviors an agent should know:**

- `startUrls` accepts the US directory root, state/city hubs, category lists and direct profile URLs; directory and category URLs fan out to individual profiles automatically.
- Alternatively pass `zipCodes` (resolved to city directories); `zipCodeCategory` narrows to one service category.
- Set `maxItems` to cap the run — omit or `0` means no limit, and directory/category fan-outs can return very large volumes.
- `flattenDatasetOutput: true` for CSV/Excel (dot-separated keys); `false` (default) keeps nested JSON.
- Angi blocks aggressively — the actor uses a built-in residential proxy and parallel TLS-fingerprint racing; `maxRequestRetries` defaults to 30 and `attemptsPerRound` to 3.
- `enrichEmails: true` (experimental, best-effort) adds `contactEmail`, `contactWebsite` and an `emailEnrichment` object; emails are otherwise only present when public on the profile.
- Pay-per-event billing — see the Pricing tab on the actor page.

### ⚠️ Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Angi Inc. (formerly Angie's List) or any of its subsidiaries or affiliates. All trademarks mentioned are the property of their respective owners.

The scraper accesses only publicly available Angi business-directory and professional-profile pages — no authenticated endpoints, no login-only or account data, and nothing behind the Angi.com login wall. Users are responsible for ensuring their use complies with Angi's Terms of Service, applicable data-protection law (including GDPR and CCPA where relevant), and any contractual obligations of their own organisation.

### SEO Keywords

angi scraper, scrape angi, Angi.com scraper, angi leads scraper, angi api, Angie's List scraper, contractor scraper, home services scraper, local business directory scraper, contractor lead generation, home service leads, plumber scraper, electrician directory data, roofing contractor leads, service provider data, local contractor database, home improvement leads, business profile scraper, us contractor directory, angi profiles export

# Actor input Schema

## `startUrls` (type: `array`):

Provide Angi URLs (e.g. /companylist/us/, /nearme/, state hubs, city hubs, category listings, or direct profiles).

## `zipCodes` (type: `array`):

Enter a list of 5-digit ZIP codes (e.g. 90210). The scraper will automatically resolve them to the correct Angi city directories.

## `zipCodeCategory` (type: `string`):

If providing ZIP codes, optionally select a service category to strictly scrape that category within the ZIP code. Leave default to scrape the whole city.

## `flattenDatasetOutput` (type: `boolean`):

If **true**, deep-flatten rows: nested objects → keys like businessInfo_businessName; arrays of objects → name_0_field; primitive arrays comma-joined; empty objects as '{}'. Includes the duplicate companyProfile\_\* tree. If **false** (default), keep nested JSON.

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

Stop the run after pushing this many rows to the dataset (each profile save or missing_profile_payload row). Omit or **0** for no limit.

## `maxRequestRetries` (type: `integer`):

How many retry rounds each URL gets before giving up. Angi blocks aggressively, so a high value (30+) is recommended. Higher = more resilient but slower and more proxy usage.

## `attemptsPerRound` (type: `integer`):

Number of fetches fired in parallel each retry round, each on a fresh proxy IP. The first HTTP 200 wins. Higher raises the per-round success rate and reduces latency, at the cost of more concurrent proxy requests. Default 3.

## `proxy` (type: `object`):

Leave empty — the actor already routes all traffic through its own built-in residential proxy at no extra cost to you. Only set this if you want to use your own proxies.<br><br>Heads-up: selecting Apify's RESIDENTIAL group here will make the run fail before it starts unless your account has that add-on enabled.

## `enrichEmails` (type: `boolean`):

If enabled, finds a contact email for each result from its own website (or by discovering it from the name). Adds contactEmail + contactWebsite columns plus a detailed emailEnrichment object. Best-effort.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.angi.com/companylist/us/al/garage-builders.htm"
    },
    {
      "url": "https://www.angi.com/companylist/us/al/forestdale/garage-builders.htm"
    }
  ],
  "zipCodes": [],
  "zipCodeCategory": "",
  "flattenDatasetOutput": false,
  "maxRequestRetries": 30,
  "attemptsPerRound": 3,
  "enrichEmails": false
}
```

# 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 = {
    "startUrls": [
        {
            "url": "https://www.angi.com/companylist/us/al/garage-builders.htm"
        },
        {
            "url": "https://www.angi.com/companylist/us/al/forestdale/garage-builders.htm"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/angi-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 = { "startUrls": [
        { "url": "https://www.angi.com/companylist/us/al/garage-builders.htm" },
        { "url": "https://www.angi.com/companylist/us/al/forestdale/garage-builders.htm" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("memo23/angi-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 '{
  "startUrls": [
    {
      "url": "https://www.angi.com/companylist/us/al/garage-builders.htm"
    },
    {
      "url": "https://www.angi.com/companylist/us/al/forestdale/garage-builders.htm"
    }
  ]
}' |
apify call memo23/angi-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/angi-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/8Ys6V1I0Qw7ubPCbJ/builds/fdJSruvcYGM9dXCDf/openapi.json
