- Removed a dead output column. The fleet-wide dead-field sweep found
seller_name and seller_id null on 60/60 rows of a live run, while
every other field populated correctly. Live re-capture confirmed the
root cause: Facebook's anonymous Marketplace search response never
carries seller identity — marketplace_listing_seller is null on
every sampled listing (24/24 in a fresh capture, matching the sampled
production run), and no sibling field on the node carries it either.
- This Actor's own README puts listing detail pages and seller profiles
explicitly out of scope, and advertises no login / no browser
automation. Recovering seller identity would require a per-listing
detail-page fetch against a target that already needs residential
proxy just for search — that contradicts the product as sold and
multiplies request volume for no guaranteed field. Removed
seller_name / seller_id from ResultRow, dataset_schema.json,
and every README/Store-copy mention instead of shipping a
permanently-null, misleadingly-advertised field.
- README's "sometimes null" hedge on seller info was false — measured
reality is always null — so the headline copy ("title, price,
seller, city/state...") and the field table are both corrected to
drop seller entirely.
Initial implementation. Exports public Facebook Marketplace search
results (title, price, seller, city/state, delivery types) for one or
more search queries and a buy-location, via curl-cffi + a Relay
persisted-query GraphQL call — no login, no browser automation.
curl_cffi.requests.AsyncSession (impersonate="chrome131") bootstrap
GET against facebook.com/marketplace/category/search/, extracting
the CometMarketplaceSearchContentContainerQuery preloader's own
queryID/variables blob verbatim from the page's data-sjs JSON.
doc_id self-heal: on a hard errors[] response, re-derives the
current persisted-query id from the search page's own rsrc.php JS
bundles, caches it in a named key-value store, and retries once.
- NDJSON response parsing (one
json.loads per line, Content-Type
header ignored) with a binary ok/error classification and a genuinely
empty page (edges: [], no errors key) treated as a real,
successful zero-result answer — never an error, never a signal to
stop early.
- Run-wide
listing_id dedupe across every query in one run — a
listing that turns up under two different search terms is pushed and
charged once.
- Pay-per-event billing:
actor-start ($0.20, once per run, gated
behind the first real delivery) + listing-scraped ($0.005, once per
unique row pushed) — a run that delivers nothing is billed nothing.
- Per-query fault isolation — one query's bootstrap/search failure never
aborts the rest of the batch.
- Default
proxy_configuration is no Apify Proxy at all — live recon
(2026-09-19) cleared the public search endpoint with no proxy from
this machine; customers may opt in to Residential.