- Apartment building and
/b/ pages worked again. Both URL shapes had started
failing outright: five residential retries each, then a give-up, with
'str' object has no attribute 'get'. Cause was a name collision inside the shared
parser, where a helper added for the search Actor's rental-building rows shadowed
the same-named helper the detail parser depends on, so normalise_building was
handed a summary dict where it expected a list of units. The search Actor was never
affected. Verified after the fix: an apartment building returns 24 units, rents
$810 to $1,200, 1 to 2 beds, the leasing phone and 24 photos.
- Five fields removed after measuring 745 properties.
brokerPhone,
brokerOfficeName, agentLicenseNumber, stories and tourViewCount were empty
on every single row across Austin, Chicago, Denver, Greenville, Miami and Phoenix.
Zillow carries the keys but does not populate them on listing pages. Three of these were
added in 0.1.15 on the strength of where they sit in Zillow's payload rather than
on a measurement, which was the mistake. A permanently-empty column is worse than
no column, so they are gone and the honest field count is 60, not 65.
- Published coverage for every remaining field, measured on those same 745
properties. Some of it is unflattering and is printed anyway: Zestimate arrives on
19% of properties, rent Zestimate on 21%, and county on 4%. On the first 300
properties Zestimate looked like 35%; the wider sample halved it, which is exactly
why the number is published rather than estimated.
- Builder communities re-verified. Three of four live community URLs returned
builder name, builder phone, price range, spec-home and plan counts, and 11 to 21
photos. The community URL that another Zillow Actor ships as its prefilled example
returns HTTP 404.
- Default memory raised to 1 GB, with 4 GB available. A 445-property run peaked
at 289 MB, and memory grows with the deduplication state a chained run builds up.
Compute is 1% of what a property costs against 99% for residential proxy, so the
headroom is nearly free.
- Output schema added, which the Actor needs before it can be published.
- Honest blocking count. The end-of-run line used to read "0 blocked" even on runs
that absorbed 100 HTTP 403 responses, because the counter only ever meant "requests we
gave up on". It now reads "0 lost to blocking after retries", which is what the number
has always measured. The input schema's block-rate figure was also wrong: it claimed
3.2% of first attempts, and a 445-property run measured about 22%. All 445 were still
delivered, because a retry is never a charged row.
- Store copy rewritten. README, Actor description and input help text.
- Three new agent fields:
agentLicenseNumber, brokerPhone and
brokerOfficeName. The licence number is published by most MLS feeds but sits in a
different part of Zillow's payload from the other agent details, so it was being
missed. It is the field that lets you match an agent to a state registry.
- Apartment buildings and builder communities now actually work. Both URL shapes
were advertised but neither could be parsed: Zillow serves three different payloads
and only single-home pages carry the one the parser knew about. A building URL used
to burn five residential retries and return nothing. Buildings now return unit
count, rent/bed/size ranges and leasing contact; communities return spec homes,
buildable plans, and the builder's name and phone. Every row carries a
pageType
column so a mixed export still reads as one table.
- New: expand buildings into individual units. One row per rentable unit, or per
home in a community, each with its own Zillow ID, beds, baths, size and price. The
units are already on the page being fetched, so this costs no extra request, but
each one is a charged row, so it is off by default.
/b/…/lat,long_ll/ URLs are now skipped. Zillow's robots.txt disallows them
explicitly (Disallow: /b/*/*_ll/). The /b/name-city-st-id/ form is permitted and
works. Worth knowing if you are migrating: another Zillow Actor prefills the
disallowed shape as an example.
- Generated map images are no longer counted as photos. Off-market homes carry a
Google satellite tile or Street View frame where listing photos would be; those URLs
are signed and expire. They are now excluded, so
photos only ever contains real
listing photos and photoCount always matches the list beside it. Affected rows go
from photoCount: 1 to photoCount: 0.
- The run now stops at your maximum charge. If you cap what a run may cost, the
crawl stops the moment that cap is reached instead of continuing to fetch pages
that can no longer be billed. Previously the charge result was discarded, so a
capped run kept crawling to the end.
- Fixed monitoring for buildings and communities. Their change fingerprint was
computed from property-only fields, all of which are absent, so every building
hashed to the same constant and would have been reported unchanged forever. Price
range, unit count and inventory count are now part of the fingerprint. One-off
effect: the first run after this update reports everything in an existing memory
store as changed.
- Roughly twice as fast, and slightly cheaper. The old 120 requests/minute cap,
not concurrency, was what decided how long a run took. The crawler spent most of
its time idle waiting for the limiter. New defaults are 10 parallel requests at 300
per minute: measured on 1,000 properties, 4m09s instead of 8m36s and $0.613 instead
of $0.638, with all 1,000 delivered.
- Default memory 2048 MB → 512 MB. Peak usage measured at 211 MB. This cuts the
compute portion of a run about fourfold and leaves far more room to run several
scrapes in parallel on your plan.
Some runs print an aclose(): asynchronous generator is already running traceback
after finishing. It is harmless, the run exits 0 and every record is delivered; the
line above it always reads SystemExit: 0. It comes from a connection being torn down
inside the Apify SDK during interpreter shutdown, not from this Actor's own code, and
we could not eliminate it from Actor code. If you are scripting against this Actor,
trust the exit code and the item count, not the presence of a traceback.
- Fixed chaining to a search dataset. The Actor runs under limited permissions, so
its run token had no access to a dataset it did not create, so passing a
searchResultsDatasetId failed with an insufficient-permissions error. The field is
now a dataset picker that declares READ, which makes the platform grant access to
the dataset you choose. Also nicer to use: pick from a list instead of pasting an ID.
- "Maximum properties" now actually caps spend. It previously stopped writing
results but let the crawl keep fetching every remaining URL, so a 100-property cap
against a large chained dataset still paid to download the whole thing.
- Duplicate input URLs are dropped before fetching, not after. The same property
supplied twice used to cost two full page fetches.
- Removed
agentEmail. Zillow suppresses agent email on listing pages, so the
column was empty on every property. Agent email comes from agent profile pages and
will be covered by a dedicated agent Actor. Output is now 61 fields.
- Rewrote the README: scannable tables for all 61 output fields, with measured
coverage on the agent fields, and a real output example instead of an invented one.
- Documented that Zestimates are US-only, and that
zillow.com carries Canadian
listings where they are unavailable.
- Corrected the proxy guidance: residential US is required, not a fallback.
- Confirmed at scale: 1,000 properties in 8m36s with no blocking.
- Accepts street addresses and bare Zillow IDs alongside URLs, resolved through the
Zillow autocomplete endpoint. Unmatched addresses are reported and skipped instead
of failing the run.
- Chaining:
searchResultsDatasetId scrapes every property found by a search run.
- Photo width selector, 192px to 1536px. URLs only, never image bytes, so photo
count does not affect run cost.
- Re-listed properties (same home, new Zillow ID) are matched on coordinates, price
and size so you are never charged twice for the same house.
- Incremental mode: return only properties that are new or whose price, Zestimate,
rent estimate, status or description changed. Unchanged properties are never
charged. View and favourite counts are excluded from change detection because they
move on their own and would mark every property changed on every run.
- An empty run that fetched nothing is reported as a fetch failure rather than as
"no results". Those look identical from outside and only one is your problem.
- First release. Full property record from
/homedetails/, /apartments/ and
/community/ URLs, with robots.txt honoured via real longest-match rule
resolution.