# Changelog of Houzz Professionals · Contacts by Search URL (`corent1robert/houzz-fr-professionals-scraper`) Actor

- **URL**: https://apify.com/corent1robert/houzz-fr-professionals-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/corent1robert/houzz-fr-professionals-scraper.md

### Changelog

All notable Store-facing updates for this Actor are listed here.

### 1.20 · Apify Actor 1.20

#### Changed

- **README — Also available** — cross-links to French official register Actors (accountants, notaires, companies, BODACC).

### 1.19 · Apify Actor 1.19

#### Added

- **Key-value store schema** wired in **`actor.json`** (**`storages.keyValueStore`**) pointing to **`.actor/key_value_store_schema.json`**: separates **RUN\_LOG** (**`text/plain`**) and **`INPUT`** snapshot (**`application/json`**) as Console collections/tabs—no crawler-internal keys are documented here beyond what buyers care about.

### 1.18 · Apify Actor 1.18

#### Changed

- **Pay-per-event (optional):** each saved profile charges **`houzz-professional-with-email`** or **`houzz-professional-no-email`** via `Actor.pushData(..., eventName)`, using the same **`email` merge logic** as spreadsheet output (`pickPrimaryEmailForCsv`: storefront `email` then `email_from_website`). **Store setup:** declare both priced events **and keep `apify-default-dataset-item` at $0** when writing to the default dataset with an explicit event (Apify stacks default-dataset charges otherwise).

### 1.17 · Apify Actor 1.17

#### Changed

- **Throughput:** default Cheerio parallelism **`56`**, hard ceiling **`80`** (still overridable via JSON). Higher implicit **`minConcurrency` floor** (**~55% of max**, min **12**) and **`maybeRunIntervalSecs` `0.03`**. **`systemStatusOptions.maxEventLoopOverloadedRatio` `0.68`** so Cheerio's synchronous DOM work does not over-throttle the autoscaler. **`maxRequestRetries`** **`3`**, handler timeout **`75s`**. After each listing sheet, **next catalog page and profile batch enqueue together** (`Promise.all`) so pagination overlaps profile fetches.

#### Infrastructure

- Platform default run memory **`3072` MB** — more headroom for heavier parallel HTML parses.

### 1.16 · Apify Actor 1.16

#### Changed

- **Throughput:** Crawlee’s autoscaled pool used to **start at `desiredConcurrency = minConcurrency` only**, so early profile fetches often ran almost **single-digit parallel** until the scaler caught up—now **`desiredConcurrency`** is pinned to **`maxConcurrency`** up front (still throttled if the runtime reports overload).

#### Improved

- **Uncapped runs:** when **Maximum professionals = 0**, a startup **WARNING** explains that full-catalog crawls feel slow—use **30–150** for quick samples.

### 1.15 · Apify Actor 1.15

#### Changed

- **Faster turnaround on browse URLs:** default Cheerio parallelism is now **`40`** (cap **`50`**) with a **higher `minConcurrency` floor (~45% of max)** so many profile pages start fetching sooner after the first listing sheet parses. Autoscaler **ramps faster** (**`scaleUpStepRatio`** **`0.35`**, **`autoscaleIntervalSecs`** **`2`**). Listing sheets enqueue discovered profile URLs in **one batched `addRequests`** instead of awaiting per-link. **`maxRequestRetries`** lowered to **`4`** so flaky pages spend less wall time in backoff. Platform default run memory lifted to **`2048` MB** to match heavier concurrent HTML parses.

### 1.14 · Apify Actor 1.14

#### Changed

- **Console input:** **`maxConcurrency`** and **`datasetJson`** are no longer surfaced on the Apify form — Cheerio parallelism and dataset row shape rely on **sensible code defaults**. Power users override through **programmatic JSON input** (API keys documented in README).

### 1.13 · Apify Actor 1.13

#### Changed

- **Dataset JSON (default):** Platform rows now default to the **same merges as compact CSV** — `phone` / `email` fold split + outbound sources, **`website` + network URL columns** replace the raw `social_links` array, sparse **null / empty keys are omitted**, **`scrape_error` only when**. API / Console: **`datasetJson`: `complete`** restores full extractor payloads for custom pipelines.

### 1.12 · Apify Actor 1.12

#### Changed

- **Throughput:** Cheerio concurrency default raised to **`20`** (cap **`50`**) with a derived **`minConcurrency` floor** so the autoscaled pool no longer behaves like a single-lane crawler at startup. Faster pool ramp-up (**`scaleUpStepRatio`**, **`autoscaleIntervalSecs`**, **`maybeRunIntervalSecs`**). Console input **`Parallel HTTP requests`** maps to **`maxConcurrency`**. API callers may pin **`minConcurrency`**.

### 1.11 · Apify Actor 1.11

#### Fixed

- **Dataset schema vs. `pushData`:** Store runs could loop on **Schema validation failed** because optional row fields used JSON **`null`** while `dataset_schema.json` only allowed plain strings / numbers / arrays. The schema now uses **`null` unions** (and declares **`address_country`**) so profile rows validate.

### 1.10 · Apify Actor 1.10

#### Added

- **Outbound website phone & social enrichment:** The same capped outbound crawl also fills **`phone_from_website` / `phone_website_fetch_url`** when storefront **`phone`** is absent ( **`tel:`** plus common FR textual formats; rejects the same digits as Houzz **`phone`** when provided), merges discovered social URLs from **`a[href]`** and bundle literals into **`social_links`** (deduped with Houzz `sameAs` / SPA grid merges), and can run **after Houzz `email`** is already known whenever a primary outbound URL exists (**`skipEmailHarvest`**) — so spreadsheets pick up FB/IG/etc. lines found only on the pro site without duplicating inbox work.

#### Changed

- **Buyer-facing enrichment logs:** Default runs **no longer print one INFO line per profile URL** when a website inbox is found. Harvests are **rolled into Profiles saved recap lines** (`website inbox harvested for N profile row(s)`) and remaining counts land in the **shutdown summary**. **`verboseLogs: true`** restores **Verbose —** INFO lines per harvest with mailbox + shortened profile/source URLs only for troubleshooting.

- **Local `output.csv`:** Default uses a **compact (~21 column) CRM order** (id, name, merged `phone`, merged `email`, address, geo, ratings, budget, bio short, social links, directory + profile URLs, photo, scrape\_error). **`phone`** column prefers storefront **`telephone`**, split **`contact_point_*`**, then **`phone_from_website`**. **Detailed** CSV also omits split phone cols, **`email_from_website`** / **`phone_from_website`** helper URLs (`email_website_fetch_url`, `phone_website_fetch_url`), **`email_website_status`**, filling **`email`** from Houzz first, then outbound website harvest when that field is blank. Dataset **JSON** still carries the granular fields (`contact_point_*_phone`, `email_from_website`, `phone_from_website`, etc.) for tooling that needs provenance.

- **Outbound website inbox discovery:** No more hard-coded `/contact` / `/cgu` ladders. The Actor **GETs the outbound homepage**, parses **`a[href]`** anchors in **DOM order**, and sequentially opens up to **`10`** distinct **same-origin** HTML targets (asset-like paths skipped) until a mailbox is found or **`websiteEmailMaxPages`** (default **`11`** homepage + follow-ups, **`12`** max) is exhausted.

- **Console / status UX:** Crawlee’s periodic **“Crawled X/Y pages…”** platform status and default **Statistics** INFO lines are disabled in favour of this Actor’s own English, outcome-first messages; catalog heartbeats no longer print `page i / max` or `saved / cap` fractions.

#### Fixed

- **Website email enrichment:** HTML snapshots were **truncated at 750 KB** before scanning. Heavy **Wix / builder** shells often exceed that while the real **`mailto:` / text mailboxes sit deeper in the markup** — e.g. `studiocastille.fr`. The ingest cap is now **~2 MB** so typical editor bundles still parse while bounding memory.
- **SPA / Vite / Lovable shells:** Sites that serve a **tiny HTML document** (`<div id="root">` + bundled `script type=module`) used to export **no inbox in static HTML** — the contact line often lives **only in the entry `.js`**. The Actor now **GETs up to three same-origin entry scripts** from the shell (skips `/~*` proxy shims) **before** crawling internal `<a>` links, each request still counting toward **`websiteEmailMaxPages`**.
- **Local `npm start` + `./input.json`:** Root-level `input.json` was ignored unless copied into **`storage/key_value_stores/default/INPUT.json`**; it is now **merged after** the KV-store input (`./input.json` wins on conflicting keys).

### 1.9 · Apify Actor 1.9

#### Added

- **Local spreadsheet export:** After a desktop run, writes **`output.csv`** at repo root alongside the dataset JSON snapshots—default columns target tabular outreach, optional wide layout via **`csvDetailedExport`**.

#### Changed

- **`output.csv` line breaks:** Long bios contained real newline characters; RFC‑4180 allows those inside quoted cells, but many spreadsheet apps behave better with **one physical line per professional**. Plain-text CSV cells now replace line breaks (`\n`, `\r`, Unicode separators) with spaces. **`storage/datasets/default/*.json`** is unchanged.

### 1.8 · Apify Actor 1.8

#### Added

- **Phone split hints:** Dataset fields **`contact_point_mobile_phone`**, **`contact_point_office_phone`**, and **`contact_point_other_phones`** when JSON-LD `contactPoint.contactType` text suggests mobile/portable wording vs office/service wording (`phone` remains the storefront `telephone` field — no guesses from numbering).
- **Optional website email enrichment:** Console toggle **`websiteEmailEnrichment`** (API alias supported) scans the primary non-social outbound site (home + capped contact/legal paths via Crawlee **`sendRequest`**, honors the same **`proxyConfiguration`**) filling **`email_from_website`**, **`email_website_fetch_url`**, and **`email_website_status`** when Houzz **`email`** is absent. API **`websiteEmailMaxPages`** (default **11**, max **12**) limits depth.

#### Changed

- **Docs / dataset schema:** Describe the outbound lookup and typed contact-point columns in README plus dataset metadata.

### 1.7 · Apify Actor 1.7

#### Changed

- **Run UX (Store-grade logging):** Outcome-first banner + starter-URL preview cap, three-space sub-step indent, wall-clock summary, aggregated counters (duplicates, JSON-LD misses, SPA-grid rescues, malformed profile skips), throttled catalog heartbeat lines on deep browse chains, buyer-safe failure text (full URLs + errors only with `verboseLogs`), and a clear Apify Proxy acknowledgement when enabled.

### 1.6 · Apify Actor 1.6

#### Added

- **HTML fallback:** Parses the storefront **Business details / professional grid** (`#business`, localized column titles) when JSON-LD is missing or incomplete. Fills `professional_name`, `phone`, postal + city from `<br>` address blocks, `typical_job_budget` (average project cost line), merges **decoded** outbound `Website` URLs from Houzz `/trk/` trackers into `social_links`. Enables rows like formerly empty-address profiles when markup still exposes the SPA grid.

#### Changed

- **Dataset:** New field **`typical_job_budget`** (string; sparse).

#### Fixed

- **`/trk/` redirect decode:** Reads only the **first URL segment after `/trk/`** so Houzz tracker suffixes (`/hex…`) are never concatenated into the base64 payload.

### 1.5 · Apify Actor 1.5

#### Changed

- **Dataset quality:** Dedupe pushes within one run (`pf~` id). Normalize empty postal strings to `null`, sanitize + dedupe `social_links`, drop redundant `structured_data_url` when it matches `profile_url`, skip duplicate `logo_url` when identical to `photo_url`. Richer columns from Schema.org markup when Houzz emits them: `business_schema_types`, geo coordinates, `price_range_hint`, opening hours summary, founding date hint, payments, employee-count hint, map URL, structured `contactPoint` summary, `legal_entity_name`, `alternate_business_name`, `slogan`, and union/array `areaServed` flattened with `"; "`.
- **Docs:** Dataset schema overview view includes storefront + identifiers + geo + schema types fields.

#### Fixed

- **Social links:** Filter invalid placeholders like standalone `http://`.

### 1.4 · Apify Actor 1.4

#### Changed

- **Local workflow:** Removed optional merge with repo-root `./input.json` — `apify run` input is **`storage/key_value_stores/default/INPUT.json`** (or `--input-file=…`) only, matching Apify CLI validation semantics. README documents that **`storage/`** is ignored and **no `./output.csv`** is produced.

### 1.3 · Apify Actor 1.3

#### Changed

- Console exposes **only** `searchUrls` and `maxItems`. Proxy plus advanced crawler knobs (`maxListingPages`, `maxConcurrency`, `verboseLogs`, `proxyConfiguration`) are documented for API / fork use only.

### 1.2 · Apify Actor 1.2

#### Added

- Official **multi-country** Houzz storefront support (`houzz.com`, `.fr`, `.co.uk`, `.de`, `.it`, `.es`, `.jp`, `.com.au`, `.ru`, Nordic & APAC hubs—extend `HOUZZ_REGISTERED_DOMAINS` when Houzz launches a new domain).
- Per-domain **`Accept-Language`** headers.
- Dataset field **`houzz_site`** (`houzz.fr`, `houzz.com`, …) plus schemas updated.

#### Changed

- Listing detection accepts **any** `/professionals/…` browse URL (metro filters, `/c/` city hubs, facets, pagination)—not limited to historic French layouts.
- Card harvesting keys off universal **`pf~` links** regardless of localized folder spelling.

### 1.1 · Apify Actor 1.1

#### Changed

- Console form narrowed to **`searchUrls`** + **`maxItems`** with optional Proxy; pagination depth / concurrency / verbose logging became API defaults.

### 1.0.0 · Apify Actor 1.0

#### Added

- Cheerio+Crawlee browse flow from Houzz professional directory URLs with `rel="next"` pagination.
- Profile enrichment from JSON-LD `LocalBusiness` blocks (`RUN_LOG`, dataset views).
