# Changelog of Healthcare Google Maps Scraper (`mukeshrana90/healthcare-google-maps-scraper`) Actor

- **URL**: https://apify.com/mukeshrana90/healthcare-google-maps-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/mukeshrana90/healthcare-google-maps-scraper.md

## Changelog

All notable changes to the Healthcare & Medical Clinic Email Scraper are documented here.

### 1.2.0 — 2026-09-17

#### Changed

- **Enrichments are on by default.** `enrichEmails`, `enrichSocials`, `enrichHealthcare` and
  `enrichMicroNiche` previously defaulted to `false`, so a typical run returned a plain Maps
  scrape — emails, NPI matches, insurance and clinic type all silently skipped — while still
  paying for a full browser and proxy crawl. All four now default to on and are pre-checked
  in the console.
- **Default run memory is 2 GB**, down from 4 GB, which is what `maxConcurrency: 4` actually
  needs now that images, fonts and map tiles are all blocked. Compute is billed as
  memory × time, so idle gigabytes were pure cost.
- **`maxResults` prefills at 50** instead of 20; a 20-result run cannot amortise the fixed
  cost of a browser launch plus a feed scroll.

#### Improved

- **Half the wasted page loads.** The run used to enqueue 2× `maxResults` places up front to
  absorb attrition. Because Crawlee navigates before it calls the request handler, every
  leftover place paid for a full Google Maps app load and then returned nothing. Collection
  depth and visit budget are now separate — 2× is collected into the reserve, 1.15× is
  visited, and `topUp()` spends the difference only when a place actually drops out.
- **The crawler now stops at `maxResults`** (and at the pay-per-event charge limit) instead
  of draining the rest of the queue.
- **Map tiles are blocked.** Google Maps streams its canvas as vector tiles over XHR
  (`/maps/vt?pb=…`), which the existing `resourceType` filter never saw — on a place page
  they are routinely the heaviest thing on the wire. Those and Google's `*_204` telemetry
  beacons are now aborted by URL.
- **Failed places cost less.** `maxRequestRetries` 3 → 2 and `navigationTimeoutSecs` 90 → 60,
  capping a dead place at ~2 minutes of billed memory instead of ~6.

#### Fixed

- **Large single-specialty practices are no longer `unknown`.** The group-practice branch of
  the clinic classifier was bounded at `doctorCount < 5`, so a practice with 5+ doctors and
  fewer than 3 specialties — a large dental or dermatology group, say — matched no branch at
  all and fell through to `unknown`. Those records returned no clinic type and billed no
  niche-classification event.
- The feed-scroll summary logged `Scrolled to 0 place links` whenever the scroll ended on an
  early-exit path, because the counter was only written after the break checks.
- **`niche-classification` no longer bills for unclassified records.** `classifyClinicType()`
  always returns a string and `'unknown'` is truthy, so the charge condition fired on every
  record — including ones the classifier could not place — against the event's own stated
  trigger of "charged when a clinic type is classified".
- **Multi-term searches no longer return results for only one term.** The per-search fair
  share was claimed on handler entry, but searches run concurrently — every search had
  "started" before the first one finished scrolling, so the pending-share count read 0 and
  the first search to finish took the entire budget. Searching `["dental clinic",
  "dermatologist"]` returned 12 dermatologists and 0 dentists; it now splits 7/5.
- **The run-level charge limit is now honoured by every event, not just the dataset item.**
  `Actor.charge()` results were discarded, so only `pushData()` could trip `budgetReached`;
  and because the crawler kept draining its queue after that flag was set, a run that hit its
  limit went on loading pages it could never bill for.

### 1.1.0 — 2026-07-13

#### Added

- **Store discoverability** — keyword-optimized, unified actor title and description; added
  Store `categories` (Lead generation, Business, Marketing) so the actor appears in faceted browse.
- **README** — output-table screenshot above the fold and a "What can you do with this scraper?"
  section covering common healthcare lead-generation searches.

#### Improved

- **Higher email hit-rate.** Reworked email extraction:
  - Generic clinic mailboxes (`info@`, `support@`, `office@`, `reception@`) are now kept and
    ranked instead of being discarded — they are the primary contact address for most practices.
  - De-obfuscates spam-protected addresses (`&#64;`, `%40`, `[at]`/`[dot]`) before matching.
  - Prioritizes `mailto:` links, the most reliable signal on a page.
  - Free web-mail addresses (Gmail, etc.) are used as a last resort rather than dropped, so
    small practices still yield a lead — while a same-domain address always wins when present.
- **Empty-feed resilience.** When a cold residential IP returns an empty Google Maps feed, the
  search now reloads and then automatically retries on a fresh IP instead of returning 0 results.

#### Fixed

- Blacklist no longer over-rejects legitimate addresses (e.g. `president@…` was previously
  dropped by a substring match on "press").

### 1.0.0

- Initial public release: Google Maps healthcare scraping with single-fetch website enrichment
  (emails, socials, insurance, doctor count, review sentiment), micro-niche classification
  (online booking, metro area, clinic type), and pay-per-event pricing.
