# Healthgrades Scraper — Doctor & Dentist Leads, NPI + Phone (`scrapersdelight/healthgrades-scraper`) Actor

Scrape the Healthgrades provider directory by specialty and city — doctor/dentist name, credentials, specialty, NPI number, star rating, review count, every office address with phone and GPS, awards and profile URL. 184 specialties, all 50 states. Public data, no login. Export JSON/CSV/Excel.

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

## Pricing

$1.50 / 1,000 per provider returneds

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

## Healthgrades Scraper — doctor & dentist leads with NPI + phone

Turn the Healthgrades provider directory into a clean lead list. Pick a **specialty** and a
**city or state**, and get one row per provider: name, credentials, specialty, **NPI number**,
star rating, review count, **every office address with its own phone number and GPS coordinates**,
awards, "accepting new patients", bio and profile URL.

184 specialties (from Family Medicine and Dentistry to Vascular Surgery), all 50 states + DC + PR.
Already have the doctors you want? Paste their **profile URLs** instead of searching. Turn on
**patient reviews** to get each provider's newest written reviews, with a date filter.
Public data, no login, no API key.

### What you get (one row per provider)

| Field | Example |
|---|---|
| `name` / `credentials` | `Dr. Maribel Carbia` / `DMD` |
| `npi` | `1174799001` — the 10-digit National Provider Identifier |
| `specialty` / `specialtyDetail` | `Dentistry` / `Dentistry` |
| `rating` / `reviewCount` | `4.8` / `26` |
| `phone` | `(305) 666-0006` |
| `practiceName` | `The Dental Studio` |
| `addressStreet` / `City` / `State` / `Zip` | `4100 Salzedo St Ste 20` / `Coral Gables` / `FL` / `33146` |
| `latitude` / `longitude` | `25.73398` / `-80.26041` |
| `offices[]` | every office: name, street, city, state, zip, **its own phone**, GPS, office profile URL |
| `officeCount` | `2` |
| `acceptingNewPatients` | `true` |
| `awards[]` | `["Healthgrades Honor Roll"]` |
| `highlights[]` / `attributes[]` | `["Listened/answered questions"]` / `["Virtual Visits"]` |
| `description` | the provider's published bio |
| `lastVerified` | `May 29, 2026` — when Healthgrades last verified the profile |
| `sponsored` | `true` for the paid cards Healthgrades injects at the top of each page |
| `profileUrl`, `imageUrl`, `providerId`, `providerType` | source + stable IDs |
| `searchSpecialty`, `searchLocation`, `resultPage`, `sourceUrl`, `scrapedAt` | provenance |

Rows from a pasted profile URL carry the same fields; the search-only ones (`distanceMiles`,
`attributes`, `highlights`, `sponsored`, `searchSpecialty`, `searchLocation`, `resultPage`) are `null`.

#### Patient reviews (`includeReviews: true`)

Three more fields on each provider row:

| Field | Example |
|---|---|
| `writtenReviewCount` | `26` — written reviews on the profile (`reviewCount` counts all star ratings, `44`) |
| `reviewsReturned` | `10` — reviews in `reviews` after your date window and per-provider cap |
| `reviews[]` | newest first, one object per review (below) |

```json
{
  "reviewId": "f4c3a323-92c7-404a-aff6-c09ed48e3cd5",
  "datePublished": "2026-09-14",
  "rating": 5,
  "title": "Great Doc",
  "text": "Dr. Lavi is an extraordinary physician, and I cannot recommend him highly enough. …",
  "author": null,
  "authorDetails": null,
  "isTelehealth": false,
  "helpfulCount": 0,
  "language": null,
  "translatedText": null,
  "scores": {
    "feltRespected": 5, "officeEnvironment": null, "appointmentScheduling": 5,
    "listenedAnsweredQuestions": 5, "explainedConditions": 5, "staffFriendliness": 5,
    "appointmentNotRushed": 5, "trustedDecisions": 5, "waitTime": 1
  },
  "providerResponse": null
}
```

- `author` is the reviewer's display name and `authorDetails` the line Healthgrades shows under the
  review (e.g. `"Danesh in Los Angeles, CA"`); both are `null` for anonymous reviews.
- `scores` are the reviewer's answers to Healthgrades' survey questions, 1–5 stars (5 = best);
  `null` = question skipped. `waitTime` runs the other way: 1 = shortest wait, 5 = longest.
- `providerResponse` is `{ "text", "date" }` when the provider replied publicly, else `null`.

### Input

```json
{
  "specialties": ["Family Medicine", "Cardiology"],
  "locations": ["Los Angeles, CA", "Miami, FL"],
  "maxItems": 500,
  "maxPagesPerSearch": 10,
  "includeProviderDetails": true,
  "excludeSponsored": true,
  "minRating": 4,
  "requirePhone": true
}
```

- **specialties** — everyday name (`"Dentistry"`, `"Primary Care"`, `"OBGYN"`) or the exact
  Healthgrades slug (`"dermatology-directory"`). Full list:
  <https://www.healthgrades.com/specialty-directory>
- **locations** — `"City, ST"` for a city, or a bare state code (`"CA"`) to sweep a whole state.
  Healthgrades spells cities out, so `"St. Louis, MO"` is converted to `saint-louis` for you.
- Every specialty is crossed with every location. 2 specialties × 3 cities = 6 searches.
- **startUrls** — paste directory URLs (pagination is followed for you) and/or provider profile
  URLs: `https://www.healthgrades.com/physician/…` (MDs, DOs), `/dentist/…`, and `/providers/…`
  (therapists, chiropractors, nurse practitioners, optometrists, physical therapists…). Each
  profile URL returns one provider row. Lists can mix both kinds and can be combined with
  specialties × locations.
- **includeProviderDetails** — ON opens each provider profile to add NPI, phone, all offices,
  GPS, awards and bio. OFF is ~5× faster but returns listing-level fields only (no phone, no NPI).
- **includeReviews** — adds the patient reviews (see above). It reads the provider's profile page,
  so the profile fields are added too even when `includeProviderDetails` is off.
- **maxReviewsPerProvider** — newest first; `0` = all the profile shows (up to 20).
- **reviewsNewerThan / reviewsOlderThan** — keep only reviews inside a date window. Absolute
  (`2026-01-01`) or relative to the run (`30 days`, `6 months`, `1 year`); both ends inclusive.

Profile URLs with reviews from the last 12 months:

```json
{
  "startUrls": [
    { "url": "https://www.healthgrades.com/physician/dr-alfred-lavi-xvy45" },
    { "url": "https://www.healthgrades.com/dentist/dr-lynn-mutch-xtq7p" }
  ],
  "includeReviews": true,
  "reviewsNewerThan": "12 months",
  "maxReviewsPerProvider": 20
}
```

### Who buys this

Medical-practice marketing agencies, healthcare staffing and locum recruiters, medtech and
practice-management SaaS sales teams, insurance credentialing and provider-network builders,
and researchers who need an NPI-keyed provider list they can join to CMS/NPPES data.

### How it works (and its honest limits)

- Directory pages are server-rendered HTML; provider cards are read from stable
  `data-qa-target` attributes, and each provider profile is parsed from its schema.org JSON-LD
  block **plus** the DOM (phone lives in the DOM, GPS lives in the JSON-LD). Nothing is scraped
  with a whole-page regex.
- **Deduplicated** by the Healthgrades provider id and again by NPI, so the same doctor is never
  returned — or billed — twice in a run.
- **`detailFetched` tells you which rows carry the profile fields.** NPI, phone, GPS, `offices`
  and `acceptingNewPatients` are read from each provider's profile page, not from the result card.
  Healthgrades rate-limits by request volume, so on a busy run a share of those profile fetches is
  refused; the actor paces itself and retries the refused ones on a fresh connection before the row
  ships. Measured over back-to-back runs on 2026-08-23 — a deliberately hostile test, the same city
  pulled ~30 times in one afternoon — **80–98% of rows** carried NPI, phone and GPS; a first run
  against a rested source sits at the top of that range. Any row that still misses them is flagged
  `detailFetched: false` — filter on it if you only want complete records.
- **Radius, not city limits.** Healthgrades returns "providers within ~10 miles of the city",
  so a `Los Angeles, CA` search legitimately includes Culver City and Beverly Hills addresses.
  Filter on `addressCity` if you need strict city limits.
- **Sponsored cards.** Each result page carries up to 3 paid listings. They are flagged
  (`sponsored: true`) and can be excluded with `excludeSponsored`.
- **practiceName is not always published.** Where Healthgrades shows the placeholder "Practice",
  the actor falls back to the group's directory-URL name, and returns `null` when there is none
  (~35% of rows in a Miami dentistry sample).
- **Education, residency and board-certification detail are not in scope** — Healthgrades loads
  those client-side after the page renders, so they are not in the HTML this actor reads. Awards
  and the `highlights` list are returned.
- Rating/review counts are Healthgrades' own patient ratings; providers with no ratings have
  `rating: null`.
- **Reviews: up to the 20 most recent written reviews per provider** — the ones the profile page
  shows. A provider with 351 written reviews returns its newest 20 (`writtenReviewCount` still
  says 351). Star-only ratings without text are not reviews and are not returned.
- Unknown profile URLs (Healthgrades sends them to its home page) and `/usearch` search-result
  URLs are skipped with a warning in the log.

### Legal / responsible use

This actor collects information Healthgrades publishes openly for patients to browse: provider
names, professional credentials, NPIs (a public federal identifier) and office contact details.
That is professional/business information, not consumer personal data — but **you** are
responsible for using it lawfully: B2B outreach, credentialing, network building and market
research, in line with Healthgrades' terms of use and any privacy, anti-spam or marketing law
that applies to you (GDPR/CCPA, TCPA, CAN-SPAM). Do not use it to contact patients, and do not
represent scraped ratings as an endorsement.

### Pricing

Pay per event: **$0.0015 per provider returned**. You are charged only for rows actually
delivered to your dataset — duplicates and filtered-out providers are free.

Patient reviews are included in the per-provider price: a provider row with its reviews costs
the same $0.0015 as one without.

# Actor input Schema

## `specialties` (type: `array`):

One or more Healthgrades specialties. Use the everyday name ("Family Medicine", "Dentistry", "Cardiology", "Psychiatry") or the exact directory slug ("dermatology-directory"). 184 specialties are supported — the full list is at https://www.healthgrades.com/specialty-directory. Every specialty is crossed with every location below.

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

"City, ST" for a city (e.g. "Los Angeles, CA") or just a state code ("CA") to sweep the whole state. Tip: Healthgrades spells cities out — use "Saint Louis, MO", not "St. Louis" (the actor converts St./Ft./Mt. for you).

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

Directory URLs (…/family-practice-directory/ca-california/los-angeles, pagination followed) and/or provider profiles (…/physician/…, …/dentist/…, …/providers/…), one row each.

## `excludeSponsored` (type: `boolean`):

Healthgrades injects up to 3 paid "sponsored" cards at the top of each page. Turn this on to keep only organic results.

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

Drop providers rated below this. Unrated providers are dropped when a floor is set. "Any rating" keeps everyone.

## `minReviewCount` (type: `integer`):

Drop providers with fewer patient ratings than this. 0 = keep everyone.

## `requirePhone` (type: `boolean`):

Drops rows with no office phone. Needs profile enrichment ON (phone is not on the listing card).

## `includeProviderDetails` (type: `boolean`):

ON (recommended): opens every provider profile to add the NPI number, office phone numbers, all office addresses with GPS coordinates, awards, bio and "accepting new patients". OFF: listing-level fields only (name, specialty, rating, one address) — about 5× faster, but no phone and no NPI.

## `includeReviews` (type: `boolean`):

Adds a nested reviews list to each provider row: text, rating, date, title, per-question scores. Up to the 20 newest written reviews the profile shows. Included in the per-provider price.

## `maxReviewsPerProvider` (type: `integer`):

Newest first. Healthgrades shows up to 20 written reviews per provider; 0 = all of them.

## `reviewsNewerThan` (type: `string`):

Absolute date (2026-01-01) or relative to now (30 days, 6 months, 1 year). Inclusive. Leave empty for no lower bound.

## `reviewsOlderThan` (type: `string`):

Absolute date or relative (e.g. 7 days). Inclusive. Combine with the field above for a window, e.g. 2026-01-01 to 2026-06-30.

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

Stop after this many provider rows in total (across all searches). Set 0 for no limit.

## `maxPagesPerSearch` (type: `integer`):

Healthgrades returns 20 organic providers per page (a big city can run to 400+ pages). This caps how deep each specialty × location goes.

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

Off by default — Healthgrades answers direct requests from Apify, which is faster and cheaper on its large pages. If Healthgrades refuses a direct request, the run switches itself to the Apify datacenter proxy. Turn a proxy on here only if you want every request to go through it.

## Actor input object example

```json
{
  "specialties": [
    "Family Medicine"
  ],
  "locations": [
    "Los Angeles, CA"
  ],
  "startUrls": [],
  "excludeSponsored": false,
  "minRating": "0",
  "minReviewCount": 0,
  "requirePhone": false,
  "includeProviderDetails": true,
  "includeReviews": false,
  "maxReviewsPerProvider": 20,
  "maxItems": 100,
  "maxPagesPerSearch": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

The dataset of scraped Healthgrades providers, one item per doctor / dentist / clinician.

## `reviews` (type: `string`):

The same provider rows with their nested patient reviews (filled when includeReviews is on).

# 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 = {
    "specialties": [
        "Family Medicine"
    ],
    "locations": [
        "Los Angeles, CA"
    ],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/healthgrades-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 = {
    "specialties": ["Family Medicine"],
    "locations": ["Los Angeles, CA"],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/healthgrades-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 '{
  "specialties": [
    "Family Medicine"
  ],
  "locations": [
    "Los Angeles, CA"
  ],
  "maxItems": 100
}' |
apify call scrapersdelight/healthgrades-scraper --silent --output-dataset

```

## MCP server setup

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