# Changelog of Airbnb Pro Host · Business Emails by City (`corent1robert/airbnb-professional-host-scraper`) Actor

- **URL**: https://apify.com/corent1robert/airbnb-professional-host-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/corent1robert/airbnb-professional-host-scraper.md

## Changelog

All notable changes to the Airbnb Professional Host Scraper are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### \[1.21] - 2026-09-28

#### Changed

- Console: **Property type** is hidden. Search still uses all types unless `propertyTypes` is set via API. Hotel-only and other filters stay available in JSON.
- **Free plan:** after ~24 pro cards, skip remaining date/guest variants and go to the contact sample (then stop if listing pages timeout). Console status after a paid export points to Host Portfolio.
- StaysSearch HTML uses **Apify datacenter proxy** (`BUYPROXIES94952` / `StaticUS3`). Native Node `fetch` ignores `agent`, so the search request goes through `https.request` + `HttpsProxyAgent` to keep HOSTINFO (`Professionnel` / `Business host`). Contacts stay on disclosure `.fr`. Chrome modal only for search cards already marked pro.
- Coverage search: **6 parallel StaysSearch requests** (unique datacenter sessions) instead of a 1-at-a-time mutex. Multi-pass default concurrency 6. Incomplete HTML: retry same TLD (new session), then **that request only** on `.com` — never flip the whole run. Empty pages already retry `.com` per request.
- Maximum coverage keeps **23 date/guest variants** and **5 pages** per variant. Speed is **parallelism only**: 10 StaysSearch HTTP, all input cities at once (cap 4), 8 variants at a time, contact HTTP **16–24** even when Chrome fallback is on (Chrome stays capped separately). Listing contact batches no longer shrink to 4 when Chrome is enabled.
- Proxy: **datacenter group only** (`BUYPROXIES94952` / `StaticUS3`). No residential. `{ useApifyProxy: true }` without a group can still bill residential (~$8/GB). Search misses fall back to `.com` on datacenter, not residential.

#### Fixed

- Store Try detection no longer treats paying users, API runs, a positive charge budget, or a 6-minute timeout (Datareacher preview) as a 5-minute QA run. Those runs paginate search instead of stopping after one page (~18 listings / a handful of unique pro hosts).
- City search default `maxPages` is **5** (`0` = unlimited). If Airbnb only returns the current `pageCursors` entry, the next `items_offset` is synthesized. Listing quota is at least 60 so `onlyProHosts` + `deduplicateHosts` can still fill ~20 unique contacts.

### \[1.20] - 2026-09-06

#### Fixed

- Empty / wiped Try input, missing city, invalid listing URLs, and city names without a country no longer fail the run. We fall back to the Console example (Lyon or one listing) and finish SUCCEEDED.
- Store Try / 5-minute check caps search to a few listings and HTTP contacts so the run does not time out.
- `Actor.exit` now times out in 5 seconds so RUN\_LOG flush cannot hang the run.

### \[1.19] - 2026-09-01

#### Changed

- Contact page timeout now starts after a fetch slot is free (queued pages were aborting at 8s without a request). Fast / no-modal runs fetch 8 listing pages at a time. Browser modal path is unchanged.
- README reordered for Store visitors: modes, personas, Quick Start (city + URLs), fields, and output JSON before examples, legal, and maintainer notes. Same content; suite + host cluster merged at the bottom.
- Console: listing URLs are a paste list (not the crawler Start URLs table). City vs URL fields stay visible (platform cannot hide by Mode); pasting URLs while Mode is City search now fails instead of silently running the prefilled city. Property type is empty by default (all types).
- End-of-run log (cloud): when `hostProfileUrl` is present, point to [Airbnb Host Portfolio](https://apify.com/corent1robert/airbnb-host-portfolio-scraper/examples/bulk-host-portfolios-from-pro-host-export) (bulk example). Store description names Host Portfolio as the next step.
- Console: next-step line on the input form. Default dataset views (**Pro Hosts — Contacts**, **Outreach**, **Market Research**) now include `hostProfileUrl` so a CSV export can be pasted into Host Portfolio.

#### Fixed

- City without country (`Paris`, `Marbella`, `Lyon`, …) infers the country instead of failing the run. Unknown names still require `City, Country`.
- Removed mode still used by old API calls (`hostProfileUrl`) and empty city/URL input now `Actor.fail` with a Console status instead of a Node stacktrace.
- Search pagination: Airbnb now returns `pageCursors[]` instead of `nextPageCursor`. Single-pass city search was stopping after the first 18 listings. Deployed as a rebuild of Store version **1.19** (10-version cap).

### \[1.18] - 2026-08-24

#### Changed

- Free plan Try: one prefill city (Marbella), HTTP contacts (no Chrome), search stops once enough listings exist for the 20-contact cap. Default memory 2048 MB.
- Store suite: README and listing copy name companion Actors (Property Details, Calendar). Dropped “Store does not cross-sell”.
- `deduplicateHosts` defaults to **true** (one row per host). Dataset adds `disclosurePresent` so empty US/UK contact cells are not read as a scrape failure.

### \[1.17] - 2026-08-19

#### Changed

- Charge-then-export: `$0` budget or remaining PPE `0` does not write contact rows.
- README **Also available** (Airbnb suite) at the top.

### \[1.16] - 2026-08-18

#### Changed

- Store SEO: `Airbnb Host Email Scraper · Pro Hosts by City | Apify`; README differentiator vs description-hunt email scrapers.

### \[1.15] - 2026-08-17

#### Changed

- **Store title** — `Airbnb Pro Host · Business Emails by City` (middle-dot listing pattern).
- **Run log** — ASCII banners, emoji stripped from console / `RUN_LOG` (premium Store signature).

### \[1.14] - 2026-08-17

#### Changed

- **README cluster** — Property Details and Calendar are the next steps after Pro Host + Portfolio, not separate Store campaigns.

### \[1.13] - 2026-08-17

#### Changed

- **Store copy** — city search is worldwide; emails/phones are richest in EU/EEA. Removed the misleading “Europe countries only” listing line. Miami Example stays a discovery task, not a promise of US inboxes.
- **Anonymous Try off** — same as Instagram / HubSpot. Free logged-in users still get 20 contacts per run.
- **README + Console** — Free plan cap stated before Start. Quick start links Host Portfolio for the next step after a Pro Host export.

### \[1.12] - 2026-08-17

#### Changed

- **Free plan cap (20 contacts per run)** — same pattern as HubSpot / Social Handle Checker. Anonymous Try still works. Paid runs are unchanged (PPE budget only). Remaining listing pages are skipped once the cap is reached so free runs stop burning proxy/compute.

### \[1.11] - 2026-06-19

#### Added

- **Published tasks kit (SEO)** — 16 task specs + input JSON for Apify Console task publishing ([docs](https://docs.apify.com/platform/actors/publishing/publish-task)). English-only maintainer docs in `published-tasks/`.
- **Dataset views** — `outreachCrm`, `marketResearch`, `allFields`; wired in `output_schema.json`. Plain-English view titles (no emoji).
- **`.apifyignore`** — excludes `storage/`, spikes, `published-tasks/`, local CSV.
- **README (Store-grade)** — *Who is this for?* persona table, objection line, pricing and legality sections, **Also available** cross-links, published Examples table.

#### Changed

- **`actor.json` v1.11** — Store title uses `·` pattern; description includes *No login or Airbnb account needed*; `meta.generatedBy`; removed invalid `keyValueStore` from `storages`; `seoDescription` tuned for Apify Actor snippet.
- **README structure** — Quick start and Examples in first 25%; *What it extracts* moved below quick path per Apify heatmap guidance.

### \[1.10] - 2026-05-04

#### Fixed

- **Input validation: graceful exit instead of crash** — `throw new Error()` on invalid input (missing country, no valid URLs) replaced with `await Actor.fail(statusMessage)`. The Actor now exits cleanly with a human-readable status message visible in the Apify console, instead of crashing with a Node.js stacktrace.
- **Smart country suggestion on validation error** — when a city is entered without a country, the error message now suggests the correct country for 50+ common cities (US, UK, Germany, Italy, Portugal, Greece, Australia, etc.). Example: `"London"` → *Did you mean "London, United Kingdom"?* instead of the misleading *"London, Spain" or "London, France"*.
- **No charge for empty results (`onlyProHosts: true`)** — the final dataset push now filters out pro host listings that have no contact data at all (no `companyEmail`, `companyPhone`, or `companyName`). Users are only charged for results that actually contain extracted contact information.

#### Changed

- **Input prefill** — updated to `["Marbella, Spain", "Miami, United States"]` to show users that non-EU cities are supported and to reinforce the `City, Country` format.
- **Actor title** — updated to *Airbnb Pro Host Business Email Scraper* for better Store discoverability.
- **Actor description & SEO metadata** — rewritten to highlight emails, phone numbers, company names, and global city coverage (Europe, US, UK, 50+ countries).

### \[1.9.0] - 2026-03-26

#### Removed

- **Post-search geographic filter on cards** (`isListingInSearchLocation`): listings returned by Airbnb search are no longer excluded based on the city line on the card (avoided false negatives e.g. Thessalonique vs Thessaloniki). The search area remains whatever **Airbnb’s search URL** covers. `isListingInSearchLocation` remains in `searchUtils.js` for unit tests.

#### Added

- **CSV column `Photo count`** (immediately after **Photo URLs**); photo URL list is unchanged.
- `**hostContactCacheByHostId`**: when `**deduplicateHosts: true`**, after the first successful contact fetch per Airbnb `**hostId**` (from listing HTML), company/contact fields are **reused** for the same host’s other listings so redundant **Puppeteer modal** runs are skipped when possible.
- **Structured English runtime logs**: separator line `LOG_SEP`, clearer section headers, progress lines unchanged (bars, %, ETA).

#### Changed

- **Apify dataset · `onlyProHosts` (défaut `true`)** : plus de `**Actor.pushData` pendant l’extraction\*\* — envoi **une fois à la fin**, après filtre pro, dédup listing et dédup hôte. `**onlyProHosts: false`\*\* réactive le push **progressif** (comme avant pour les runs « tous les types d’hôtes »).
- **Co-host columns (CSV + Google Sheets)**: single **Co-host names** replaced by `**Co-host name 1`**, `**Co-host name 2`**, `**Co-host name 3**` (max three names; comma/newline-split for strings). Helper `**coHostNamesToThreeColumns**` in `searchUtils.js`. Dataset JSON still exposes `**coHostNames**` as an array.
- **Local CSV export (`output.csv`)**: **UTF-8 BOM** (`\uFEFF`) so Excel detects encoding; **prices** formatted to **two decimals**; **rating** normalized (e.g. New / numeric); **discount %** formatting aligned; `**exportToCSV`\*\* receives `**input`\*\* for CSV-related flags.
- **Google Sheets**: `**EXPORT_HEADERS` / `buildListingRow`\*\* aligned with CSV (including **Photo count** and three co-host columns).
- **Apify Store input form**: removed `**disclosureDomain`\*\* from `input_schema.json` (advanced options remain available via **raw JSON / API** where applicable).

#### Fixed

- **Discount %** in CSV: French locale uses a consistent numeric format with decimal separator.

### \[1.8.1] - 2026-03-25

#### Fixed

- **Apify : 0 annonce sur `airbnb.fr`** pour certaines villes UE hors France (ex. Thessaloniki) : la page JSON peut renvoyer `searchResults` vide depuis IP datacenter. **Repli automatique** vers le domaine logique (`**airbnb.com`**) une fois par run (log d’avertissement) ; la divulgation reste sur `**airbnb.fr`**. Le flag `_useLogicalSearchDomain` sur l’objet `cityInput` évite de retenter `.fr` à chaque page de pagination.

### \[1.8] - 2026-03-25

#### Changed

- **Recherche UE sur `airbnb.com` (ex. Grèce)** : autocomplete, URL `/s/…` et fetch HTML **StaysSearch** passent par `**airbnb.fr`\*\* quand le pays est **UE/EEE** et que le domaine « logique » serait `.com`, afin d’obtenir les cartes **« Hôte professionnel »** et un pré-filtre `onlyProHosts` fiable depuis **Apify / proxy**. Le suffixe d’URL reste le **pays réel** (ex. `--Greece`). `getLocaleConfig` inchangé pour le reste ; divulgation toujours via `getDisclosureFetchDomain`.

### \[1.7] - 2026-03-25

#### Fixed

- **Apify vs local (emails pros vides sur le cloud)** : pour les pays **UE/EEE**, le chargement du **bloc légal** (email, tél, raison sociale) se fait désormais via **airbnb.fr** + `Accept-Language` aligné, même quand la **recherche** reste sur **airbnb.com** (ex. Grèce). Sur `airbnb.com` depuis des IP datacenter, Airbnb omet souvent la même divulgation qu’en Europe — d’où des résultats en local et quasi rien sur Apify. Option JSON `**disclosureDomain`\*\* pour forcer un autre site (ex. `airbnb.es`).

### \[1.6] - 2026-03-25

#### Fixed

- `**deduplicateHosts: true` + Apify\*\* : les lignes n’étaient envoyées au dataset **qu’à la fin** du run ; en cas d’**arrêt manuel**, le dataset restait **vide** alors que l’extraction avait trouvé des pros. Désormais, chaque **hôte dédoublonné** avec email / téléphone / raison sociale est **poussé au fur et à mesure** (re-push si les champs contact s’enrichissent après fusion).

### \[1.5] - 2026-03-25

#### Added

- `**coHostNames`\*\*: extraction des **co-hôtes** depuis le JSON PDP `"cohosts":[{"name":"…"},…]` (fiable sur airbnb.fr / .com même sans libellés « Your co-host » en français), avec repli sur le texte « Your co-host » / « Votre co-hôte » si présent. **CSV / Sheets (v1.9)** : trois colonnes **Co-host name 1–3** au lieu d’une seule colonne « Co-host names ».

### \[1.4] - 2026-03-25

> Store / `apify push`: version must be `**MAJOR.MINOR`\*\* (e.g. `1.5`). `package.json` uses `1.5.0` for npm.

#### Fixed

- **English search cards (`airbnb.com`)**: Pro hosts are labeled **“Business host”** on listing cards, not “Professional host” — search-step detection now matches both, so `onlyProHosts` pre-filters contact extraction again instead of opening every listing.

#### Added

- **Automatic Airbnb site**: Domain (airbnb.com, airbnb.fr, airbnb.es, …) is inferred from the **country** in `City, Country` — no UI field; optional `locale` in JSON API still overrides.

#### Fixed

- **Wrong country in results (e.g. Greece)**: Domain follows the parsed country instead of defaulting to airbnb.fr when cities share one country.
- **Autocomplete**: Uses the same country-aware domain as search (per city).
- **English card titles** (`Apartment in Thessaloniki`, …): City parsed from the title so geographic filtering works on airbnb.com.
- **Local `npm start`**: Merging root `input.json` clears stale `searchLocale` / `locale` from Apify local storage when those keys are absent from the file.

#### Changed

- **Location filter**: Removed from the Actor form (historical; post-search city-line filter dropped in **\[1.9]**).
- **Google Sheets & Drive**: Removed from the Actor input form (Step 3). Same keys still work when passed in **raw JSON** input or API (`googleSheetsEnabled`, `googleDriveUploadPhotos`, …).

***

### \[1.3.0] - 2026-03

#### Added

- **onlyProHosts** in UI: Toggle to include all host types (pro + individual) — unchecked = more listings, takes longer
- **Custom needs contact**: <corentin@outreacher.fr> in Actor description

#### Changed

- **Modes reduced to 2**: Host profile mode removed — only City search and Listing URLs
- **Output in English**: Amenities and rating normalized (French → English: "Nouveau" → "New", "4,96 (24)" → 4.96)
- **Interface**: Emojis removed from step titles and mode labels

***

### \[1.2.0] - 2025-03-11

#### Added

- **City name autocomplete**: Resolves city names via Airbnb autocomplete API — variants (Iraklio, Irakleio, Chaniá…) are converted to the canonical form for the locale (e.g. Heraklion, Chania on airbnb.fr). Fixes zero-result searches for cities with translated spellings.
- **useCityAutocomplete**: Input option (enabled by default). Set to `false` to disable.
- **COUNTRY\_TO\_ISO**: Complete country mapping (445 entries) in multiple languages (EN, FR, ES, DE, IT, PT, EL…) to filter autocomplete results correctly.
- **input.locale** : Override the domain (en, fr, es, de, it, pt). Leave empty for auto-detection from city country.

#### Changed

- **Global fallback** : No longer defaults to airbnb.fr. Greece, USA, UK, Canada, Australia → airbnb.com. France, Spain, Germany… → their local domain.
- **Contact extraction** : Uses the locale domain (airbnb.com for Greece) instead of always fetching from airbnb.fr — output labels, URLs and content now match the city’s country.

***

### \[1.1.1] - 2025-02-25

#### Changed

- **Streaming memory**: Push + remove from memory after each batch — no longer holds 10k listings in RAM
- **Default memory**: Back to 4 GB (streaming keeps usage low)
- **Large runs**: Concurrency reduced when >2000 listings
- **Run log**: Buffer capped at 2000 lines

#### Fixed

- **OOM**: Streaming architecture avoids OOM on 10k+ listings without paying for 8–16 GB RAM

***

### \[1.1.0] - 2025-02-24

#### Added

- **RUN\_LOG**: Live progress log in Key-Value Store — users can open the Run Log link during execution to monitor scraping progress (bars, %, ETA, pro hosts found)
- **Progressive push**: Results stream to the dataset as each contact is extracted — no need to wait until the end to see data
- **CHANGELOG**: Version history visible on the Apify Store page

#### Changed

- **Output schema**: Added "Run Log (live)" link pointing to Key-Value Store RUN\_LOG record
- **onlyProHosts**: Default `true` (pro hosts only). Set `false` in input to include all host types (individual + pro)

***

### \[1.0.0] - 2025-02

#### Added

- Initial release
- City search, Listing URLs modes
- Contact extraction: email, phone, company name, address
- Professional host detection via Airbnb labels + page scraping
- Output: ~50 columns, CSV/JSON export, deduplicate hosts option
