# Changelog of Yelp Scraper \[Just 💰$1] — Business Reviews, Phone & Menus (`blackfalcondata/yelp-business-scraper`) Actor

- **URL**: https://apify.com/blackfalcondata/yelp-business-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/blackfalcondata/yelp-business-scraper.md

## Changelog

### 0.1.2 — 2026-09-04

#### Added

- Structured address on every business: `street`, `city`, `state`, `postalCode` and `country`
  as their own fields, alongside the existing single-line `location`.
- `hours` — the full week of opening hours, labelled by Yelp's own day names. `hoursToday`
  is unchanged.
- `isCurrentlyOpen`, `photoCount`, `alias` (the canonical `/biz/<alias>` slug) and `isYelpAdvertiser`
  (true when the business advertises on Yelp).
- Telegram, Discord, Slack, WhatsApp and webhook notifications now actually fire. The input
  fields existed but nothing was sent.
- Start URLs now accept a business page (`yelp.com/biz/...`), not only a search page. Pasting
  one used to fail the run.
- `includePhotos` / `maxPhotos` — the business's photo URLs, off by default so results stay
  small for everyone who only wants `photoCount`.
- `healthScore` — the latest health-inspection result Yelp shows (US), and `closedUntil`,
  which carries a date only while a business is temporarily closed.

#### Memory

The default run memory is now 2 GB, up from 1 GB. Measured on the same 100-business search:
1 GB never finished the profile phase at all (killed after 29 minutes with nothing to show),
2 GB finished in 108 seconds, 4 GB in 95. 1 GB is still selectable for small runs. Note that
the Actor-start event is charged per GB, so a 2 GB run's start fee is $0.008 instead of
$0.004; on a run of any real size that is a rounding error next to what it saves.

#### Two runs on one Saved State Key can no longer both deliver

Starting the same monitor twice — a schedule that fires twice, or a manual run alongside a
scheduled one — used to let both runs scrape and charge for the same businesses. The Actor now
takes a real exclusive claim on the state key before doing any work; the second run stops
immediately. Measured twice on runs overlapping by 27 and 18 seconds: the winner delivered 60
and 50 businesses, the loser delivered zero both times and cost $0.00005.

#### Results now arrive during the run

Businesses are written to the dataset as their profiles finish, in batches of ten, instead of
being held in memory and written in one go at the very end. A run that is interrupted — a
timeout, a block, an aborted run — now leaves you everything it had finished, rather than
nothing at all. Measured: 30 results were readable 67 seconds into a run that ended at 70.

#### Incremental runs no longer re-buy what has not changed

A monitor used to fetch the full profile of every business on every run, just to discover that
nothing had changed. It now uses the search result itself as the gate: a business already in
your Saved State Key whose name, address, main photo and claim status are unchanged is not
fetched again. Measured on a 120-business search: a quiet run went from ~105 seconds to ~25,
and the run log tells you how many were skipped.

The trade-off, stated plainly: a change only the full profile would show — a new phone number,
a new website, different opening hours — is no longer picked up on a business you have already
received. A rename, a move, a new main photo and a claim all still trigger a fresh fetch and a
full updated record. Turn on Emit Unchanged to get every business back in full on every run.

#### Changing what a result contains now returns your businesses again

Incremental history is kept per output shape. Switching Include Full Details, photos, menus,
e-mail enrichment, Compact, Exclude Empty Fields, the description format or its maximum length
gives you every matching business back once, in the new shape, instead of an empty run saying
nothing has changed. Runs that keep the same settings are unaffected.

#### Note for incremental users

The first run after this release returns your businesses again. Their records changed: review
highlights now have content, the address is structured, and the fields that decide "has this
business changed" now include phone, website, categories, price band, hours, the review
quotes and the health result. After that first run, only real changes come back.

#### Fixed

- `attributes` listed things the business does NOT offer. Yelp ships the negatives alongside
  the positives (its own page strikes them through) and all of them were being delivered — on
  one live restaurant, 18 of 47 entries, including "Offers delivery" for a business that does
  not deliver. Only what the business actually offers is returned now.
- `reviewHighlights` was empty on every result ever returned. Yelp splits each quote across
  several text nodes and the parser read a field that does not exist; the quotes were on the
  page the whole time. Businesses now come back with 5-8 of them.
- A run whose searches were all refused now fails instead of finishing green with an empty
  dataset. A refused page mid-run no longer passes for the end of the results either — the run
  says so in the log instead of quietly returning a short list. A block served as an ordinary
  200 response is treated as a refusal, not as "no businesses here".
- Results are no longer written past the run's maximum cost limit and then remembered as
  delivered. A capped run now delivers what it can pay for and leaves the rest for the next run.
- Changes to a business's phone, website, categories, price range, opening hours or closed
  status now come back as changed results in incremental mode. Only a rename or a move did
  before. Rating and review count are deliberately not tracked — they move constantly and
  would return your whole list as "changed" on every run.
- Incremental state no longer grows without limit. A business not seen for 90 days is dropped
  from the run's memory.
- Reordering a multi-value Search Term or Location no longer starts a fresh incremental
  history and re-delivers everything.
- One failed browser session no longer silently downgrades every remaining business in the run
  to search-result-only data.
- Search results without an id are dropped instead of collapsing into one bogus record with a
  broken URL.
- With several search terms, each result now carries the term that actually found it.
- A search that reports no further pages no longer triggers dozens of pointless requests.
- Latitude/Longitude/Radius set only partly is now reported in the log instead of being
  silently ignored. A radius run with Include Full Details off is now rejected with an
  explanation instead of returning unfiltered businesses, and a business whose coordinates
  are unknown is no longer counted as inside the radius.
- The same business reached both by its page URL and through a search is now delivered — and
  charged for — once.
- A business whose full profile could not be fetched is no longer delivered as an
  almost-empty row. It used to be charged for twice: once as a stripped result, once again
  when the next run recovered it.
- Turning photos, menus or e-mail enrichment on now re-delivers your businesses with the new
  data instead of reporting them all unchanged.
- A run that finds nothing no longer leaves its Saved State Key locked for 30 minutes, and a
  run that lost its turn no longer clears the lock belonging to the run that took over.
- Incremental history now survives all of the above: a run whose identity moved adopts its
  earlier history instead of re-delivering everything as new.
- A refused menu lookup is no longer recorded as "this business has no dishes", and neither is
  a menu response that arrives in an unexpected shape.
- A results page where every entry is unusable — no id, or no business name — now fails
  instead of counting as an empty page, and a run that ends up delivering none of the
  businesses it found fails too. Businesses that are genuinely gone still end the run normally.
- An empty first page is retried once. The same search was measured returning 0 businesses on
  one call and 20 on the next, so a single blink used to end the run with a clean, wrong zero.
- A results page with no pagination information is treated as a broken response instead of as
  "there are no more pages", which used to cut every run down to its first page.
- Changes to a business's amenities, year established or popular dishes now come back as
  changed results in incremental mode.
- A profile that fails on a refusal, a timeout or a dropped connection is retried before the
  business is given up on; only rate limits and server errors were retried before. A blocked
  run now gives up in half the time it did while that retry was duplicated.
- A response that stalls after its headers is now cancelled instead of holding a connection
  open for the rest of the run.
- Start URLs on look-alike hosts (`yelp.company`) or look-alike paths (`/searches`) are no
  longer accepted as Yelp targets.
- A menu entry with no name is dropped instead of being delivered as a nameless dish, and a
  menu lookup that fails no longer costs you the whole business — the profile is delivered
  either way.
- A business the Search Radius excluded is no longer remembered as delivered, so it is
  returned if its position later falls inside the radius.
- Businesses Yelp reports as unclaimed now come back as `isClaimed: false` instead of empty.
- `contactEmail` no longer returns the dummy address a website theme ships with. Two different
  Brooklyn restaurants were both being delivered as `user@domain.com`; an address on the
  business's own domain is now preferred, and template placeholders are dropped. An empty
  field is better than a fake lead.
- A single Start URL passed as plain text instead of a list is now used, rather than the run
  finishing successfully with no results.
- A menu or contact-email lookup that could not be READ is no longer recorded as a change. It
  used to bill an updated result for the missing value and another when it came back.
- A radius run now scans a sensible number of businesses even when the result limit is small,
  and says in the log when the radius is what left the run empty.
- Start URLs are authoritative: a URL that cannot be used stops the run instead of quietly
  scraping the remaining ones or falling back to the Search Term. A Yelp search URL carrying
  Yelp's own filters or sort is refused, because this scraper cannot apply them and would
  otherwise return the unfiltered result set.
- The same search term or URL listed twice no longer starts a fresh incremental history.
- Suite and unit numbers (`addressLine2`) are now part of the street address.
- A business-page Start URL with Include Full Details off is rejected instead of finishing
  with zero results.
- A business skipped by Skip Reposts is no longer remembered as delivered, so turning the
  option off returns it.
- A key-value store hiccup while loading incremental state no longer leaves the Saved State
  Key locked for 30 minutes.
- A run where every business profile failed to load now fails instead of finishing green with
  an empty dataset. Businesses that no longer exist on Yelp still end the run normally.
- A profile that fails to load is retried once before the business is given up on.
- Requests that stall are now cancelled properly. The previous timeout could leave the
  connection open, which on rare occasions hung a run to its deadline with no output.
- Incremental runs no longer remember businesses the result limit cut off. They were stored
  as already-seen and never delivered on later runs; now they come back on the next run.
- A centre + radius run now fetches enough businesses to fill the result limit with ones
  inside the radius, instead of filtering a full page down to a handful.
- A centre + radius run keeps its own incremental history, so widening the radius delivers
  the newly included businesses instead of treating them as already seen.

#### Removed

- Four fields that were exact copies of a field that stays: `entityName` (= `title`),
  `sellerWebsite` (= `website`), `priceText` (= `priceRange`) and `descriptionText`
  (= `description`). Byte-identical on every record; together 298 bytes of a 3,636-byte row.
  Nothing is lost — the twin is still there under its canonical name. (`portalUrl` also
  duplicates `url` but stays: it is the standard link-back field across all our Actors.)
- 14 fields that were always empty for Yelp (`subtitle`, `subcategory`, `descriptionHtml`,
  `descriptionMarkdown`, `priceValue`, `priceCurrency`, `listingType`, `condition`,
  `publishedAt`, `publishedAge`, `validThrough`, `sellerName`, `sellerType`, `sellerUrl`).
  They carried no data on any run; dropping them makes each result smaller. If your pipeline
  reads one of them, it was reading `null`.

### 0.1.1 — 2026-08-17

#### Fixed

- Runs with email enrichment enabled no longer log `Attempting to charge for an unknown event 'email-enriched'`. The Actor tried to bill an event that is not part of its pricing, so the charge was always rejected and the warning was written into your run log instead. Nobody was ever billed for it, no run cost changes, and email enrichment stays covered by the standard per-result fee.

### 0.1.0 — 2026-07-17

- Initial release.
- Search Yelp businesses by term + location.
- Full business profile: rating, review count, phone, website, hours, categories, address,
  neighborhoods, coordinates, attributes, price range, year established, specialties.
- Popular dishes / menu items (opt-in, restaurants).
- Contact-email enrichment from the business website (opt-in, billed per email found).
- Incremental mode with cross-run dedup.
