# Changelog of Google Maps Scraper Pro – Reviews, Photos & Place Details (`badr_xba/google-maps-scraper-pro`) Actor

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

## Changelog

### 2026-11-09 (review invitation)

- Successful runs that saved at least one result end their status message with one short, optional invitation to leave a Store review or open an issue (never on failed or empty runs); README section "Feedback and reviews". Output and billing unchanged.

### 2026-11-08 (visual kit 1.1.0)

#### Added

- `FAILED_INPUTS` entries get a stable `errorCode` (`INVALID_INPUT`, `INVALID_URL`, `SHORT_LINK_EXPIRED`,
  `NO_SEARCH_TERM`, `INVALID_PLACE_ID`, `INVALID_AREA`, `NO_AREA`, `AREA_NOT_FOUND`, `GEOCODING_FAILED`,
  `PLACE_NOT_FOUND`, `BLOCKED`, `PARTIAL_BLOCKED`, `UNEXPECTED_ERROR`); the visual report groups failed inputs by code.
- Visual report on kit 1.1.0: live progress page while the run is going (searches and direct places done / total,
  places so far; at most one write a minute), data-quality panel, at most 3 photos per place in the page (main photo
  first; the dataset keeps every image), photo column in the table view.
- The final report is also written when the run stops on an unexpected error (status FAILED), instead of staying
  "Running".

### 2026-11-08

#### Added

- **Place photo** `imageUrl`: Google's main photo of each place (lh3.googleusercontent.com, Google's own 408 px card
  size; a smaller thumbnail is asked at `=w408-h306-k-no` by editing only its size suffix). The search request now asks
  for the photo block the Google Maps web page asks for (`!20m…` of the `pb` parameter; before, `!20m0` = no photo).
  Null when Google has no photo for the place; Street View thumbnails, owner avatars and partner logos are never used.
  Local run 2026-11-08: 10 of 10 restaurants in Chicago (search only) with a photo, 3 URLs checked (HTTP 200, image/jpeg); with details + 3 images: 5 of 5.
- Dataset schema: `imageUrl` declared, shown first (`"format": "image"`) in every view (Overview, Contacts, Reviews).
- Visual report: cards show the place photo (lazy, `referrerpolicy="no-referrer"`, category tile when null or broken).
- **Visual report** (`REPORT` record, Output tab → *Visual report*): business cards (place photo + photo gallery when the row has them, else a category tile; name, rating, review distribution, reviews, price level, category, address, phone, website, e-mail, socials, opening hours, Google Maps link), key figures, rating
  distribution / top categories / top cities charts, search, filters (category, city, minimum rating, has website /
  phone / e-mail, open at scrape time, price level), sorting, table view and CSV/JSON export of the filtered rows.
  Built by the shared `src/visual_kit.py` (v1.0.1) with this Actor's `src/report.py`. At most 2,000 rows are kept in
  memory for it; it is never billed and a report error is only logged as a warning.
- Output schema: *Visual report* link; key-value store schema: `REPORT` (text/html).
- `imageUrl` and `images[].imageUrl` / `imageUrls` go through the kit's `normalize_image_url`: `//host/…` becomes
  `https://host/…`, anything that is not an http(s) link becomes null. Valid links are unchanged.

#### Tests

- `tests/test_photos.py` (9 tests): real Google answers saved 2026-11-08 with the photo block
  (`search_photos_nyc_2026-11-08.json.gz`: 20/20 photos; `search_photos_paris_2026-11-08.json.gz`: 18/20, one Street
  View-only place and one without anything stay null), old answers without the block give null, size handling, schema
  views, report cards.
- `tests/test_report.py`: 14 tests on real rows (`tests/fixtures/report_run_items.json`: 10 rows of production run F0NksmCsBvXh59jd5 + 1 row parsed from the saved Google place-detail answer details_nyc.json.gz, with 4 photos):
  image ownership / placeholder tile, invalid image links dropped, escaping, 5,000 synthetic rows (time, size, cap
  banner), JSON export = embedded data, report errors never raise, bounded buffer, and a whole offline run whose
  `REPORT` holds exactly the rows pushed.

#### Fixed

- `imageUrl` was empty on every search-only row (last demo run: 0 of 20): the search request asked Google for no photo
  (`!20m0`), so the photo block the code read (72) was never in the search answer, only in place detail answers.
  Search rows now have it; detail answers also read their gallery blocks (51 / 37), skipping unusable addresses
  instead of returning null.

### 2026-11-07

#### Fixed

- `city` kept the postal code in countries that write it first (`75020 Paris`) or after the city (`London SW1A 1AA`):
  the city now comes alone, read from Google's structured address when it matches the line.
- `countryCode` was only the "Country" input (empty by default): it is now the place's own country code from Google,
  the input being the fallback.
- `postalCode` and `state` fall back to Google's structured address when the address line gives none.
- Images: only one block of the place detail answer was read, so most places returned 1 photo even with a higher
  "Maximum images" (local test: 6 images for 5 places × 3 asked). The gallery blocks are now read too, deduplicated,
  Street View thumbnails skipped (same test: 13 of 15). `imageUrl` also falls back to the gallery.
- Dataset schema: 16 keys the Actor always writes were not declared (`name`, `latitude`, `longitude`, `rating`,
  `openState`, `timezone`, `phoneInternational`, `domain`, `reviewsUrl`, `googleMapsUrl`, `cityLine`, `category`,
  `searchQuery`, `searchLocation`, `whatsappUrl`, `imagesCount`). Now declared with their real types.

#### Improved

- Websites (contacts add-on): page bodies streamed and cut at 400 KB, at most 30 s per site and route, contact pages
  read two at a time.
- Direct Google requests (no proxy) reuse one connection pool; cookies still cleared after every request.
- Place detail requests: exponential backoff with jitter (longer after 403/429/5xx), no pause after the last attempt.
- Every row is checked against the dataset schema types before it is saved (`src/validate.py`).
- Descriptions of the dataset fields say when a field is often empty and why (`price`, detail-only fields…).
- `.actor/actor.json`: default memory 512 MB (local peak 89 MB with details, reviews, images and contacts; the
  platform run sampled used 56 MB of 4096 MB).
- Tests: `python -m pytest -q` (offline, real saved Google Maps answers in `tests/fixtures`); `tests/` excluded from
  the Docker image.
