Facebook Marketplace Scraper - $0.40 per 1,000 Listings
Pricing
from $0.40 / 1,000 listings
Facebook Marketplace Scraper - $0.40 per 1,000 Listings
Every Marketplace listing says whether it sold, and that flag is on the row with title, price, currency, city, link, photo and posted date. Filter by price, condition, category and recency. No login needed from you. $0.40 per 1,000 listings.
Pricing
from $0.40 / 1,000 listings
Rating
0.0
(0)
Developer
Dami's Studio
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Facebook Marketplace Scraper
Marketplace searches are local, so this wants a search term and a place. Every match comes back as one row: title, price, currency, city, listing link, photo, and when it was posted. Filter by price range, category, item condition and how recently something was listed, and sort the way the site does.
You don't sign in and you don't paste any cookies. See About logging in for what the run actually does.
- Several search terms in one run. Each listing is one row, deduplicated across terms, so the same item is never charged twice.
- Works anywhere Marketplace does. A city name, a Marketplace city slug or raw coordinates all centre the search.
- Price range, condition, listed-in-the-last-N-days, category and sort order are real filters Facebook applies, not a filter pass afterwards.
- Paste a Marketplace search URL straight from your browser and it gets taken apart into the same filters.
- Empty input returns one labelled sample row, free, so you can see the output shape before spending anything.
Price
$0.40 per 1,000 listings. Plus $0.0025 per run as the platform start fee, billed per gigabyte of run memory, so exactly that on the default 1 GB.
No volume tiers, no minimum spend, no subscription.
| Listings | Total |
|---|---|
| 100 | $0.0425 |
| 1,000 | $0.4025 |
| 10,000 | $4.0025 |
| 100,000 | $40.0025 |
What gets charged
- One
listing-scrapedevent per listing row written to the dataset. Nothing else is metered per row. - Free: the sample row an empty run returns, and every diagnostic row (a refused search, a dead URL, a term that matched nothing). They all carry
"charged": false. - Listings that already appeared earlier in the same run are dropped before they're charged.
- A search term that matches nothing produces an uncharged diagnostic row, not a bill.
- A run that finds nothing costs the start fee and nothing else.
- Rows never leave the dataset without a charge and are never charged without a row. The billed event is a named one, so nothing is quietly attached to
apify-default-dataset-item.
Input
{"searchTerms": ["road bike","mountain bike"],"location": "Austin, TX","minPrice": 100,"maxPrice": 800,"daysSinceListed": 7,"sortBy": "newest","maxItems": 60}
| Field | What it does |
|---|---|
searchTerms | One or more things to search for. Up to 10 per run. The row budget is split evenly between them. |
location | Where to search. A plain city name works ("Austin, TX", "Manchester", "Toronto"), so does a Marketplace city slug ("nyc", "la", "sanfrancisco"), and so does a raw latitude,longitude pair. Marketplace search is local, so you need this unless you set the coordinates yourself or paste a URL that carries them. |
latitude / longitude | Exact coordinates to centre the search on, if you'd rather not rely on a place name. Set both or neither. |
startUrls | Marketplace search URLs copied from your browser. Term, place, price range, condition, sort order and category are all read out of the URL. Up to 10 per run. |
minPrice / maxPrice | Price range, in the currency of the place you're searching. Both optional. |
condition | Any of new, used_like_new, used_good, used_fair. One value is the most reliable. Several are passed through exactly as the site sends them. |
daysSinceListed | Only listings posted in the last N days, 1 to 30. |
category | A Marketplace category id, to narrow to one department. |
sortBy | best_match, newest, price_low_to_high, price_high_to_low or distance. Defaults to Facebook's own relevance order. |
exactMatch | Makes Facebook match your term as a phrase rather than loosely. Good for a model number. |
availability | Set to sold to look at sold listings instead of available ones. |
radiusKm | Passed through to Facebook exactly as its own filter sends it. Read the limits section before relying on it. |
maxItems | Total rows across all terms. Default 20, hard ceiling 1,000. Keep it low while testing, since you pay per row. |
proxyUrls | Leave empty. Only for callers who want traffic to leave through proxy servers they already pay for, as http://user:pass@host:port. |
sessionCookies | Leave empty unless you're running heavily. See About logging in. |
Run it with empty input and you get one labelled sample row, free.
Output
One row per listing. This is a real row from a real run:
{"ok": true,"charged": true,"recordType": "listing","searchTerm": "road bike","listingId": "1510649247532466","listingUrl": "https://www.facebook.com/marketplace/item/1510649247532466/","title": "RoyalBaby Freestyle 20 Inch Kids Bicycle with Kickstand and Water Bottle, White","price": 68,"priceFormatted": "$68","currency": "USD","originalPrice": null,"priceMin": null,"priceMax": null,"city": "New York","state": "NY","locationName": "New York, New York","imageUrl": "https://scontent.xx.fbcdn.net/v/t39.84726-6/767388547_2298847434264202_example_n.jpg","videoUrl": null,"postedAt": "2026-08-07T14:44:36.000Z","postedAtTimestamp": 1786301076,"daysListed": 9,"isSold": false,"isPending": false,"isLive": true,"categoryId": "1658310421102081","deliveryTypes": ["IN_PERSON", "PUBLIC_MEETUP"],"subtitle": null,"fromGroup": null,"scrapedAt": "2026-08-16T09:14:02.771Z"}
Field notes
listingUrlis the permanent link to the item. Stable, so it works as a key across runs.priceis a plain number in the listing currency. A "Free" listing is 0. A listing with no price at all is null.priceFormattedis exactly the string Facebook shows, symbol included.currencyis the ISO code, worked out from the symbol Facebook formats the price with. See the limits section.originalPriceis the crossed-out price when the seller has cut it, null otherwise. Subtractpricefrom it to get the discount.priceMinandpriceMaxonly fill in for listings sold as a price range rather than a single figure.city,stateandlocationNameare where the seller says the item is.stateis empty in countries that don't use one.imageUrlis the primary photo as a Facebook CDN link. Those expire, so download what you want to keep.postedAtandpostedAtTimestampare when the listing was created, as ISO 8601 UTC and as epoch seconds.daysListedis how long the item has been sitting there. Useful for spotting stale listings.isSold,isPendingandisLiveare the item status at read time.categoryIdis Facebook's internal department id. Stable enough to group by, but Facebook publishes no public name for it, so none is invented here.deliveryTypesis how the seller will hand it over:IN_PERSON,PUBLIC_MEETUP,SHIPPING.subtitleis the extra line under vehicle listings, mileage and similar. Null for most categories.fromGroupis set when the item was cross-posted from a buy-and-sell Group.
Real rows carry "charged": true. Sample rows carry "_sample": true, diagnostic rows carry "_diagnostic": true and an errorCode. Neither is billed.
How it works
It calls the same public search endpoint the Marketplace page itself calls and reads listings straight out of the JSON. Nothing is rendered and no headless browser starts.
Only the search results are fetched, roughly 2.6 KB of traffic per listing. Getting the same rows out of a rendered page means pulling down the images and scripts wrapped around them as well, and that bandwidth has to be paid for somewhere.
Each search is paged until your maxItems is reached, and listing ids already seen in the run are skipped. If Facebook rotates the internal id of its own search query, the Actor re-reads the current one from the page and carries on rather than failing.
About logging in
You never supply a Facebook account and you never paste a cookie. Searches go out logged out, and that's what answers a normal run.
When Facebook refuses every exit address for a term, the run retries that term once using a Facebook session that ships inside the Actor. That session is shared by everyone running this Actor, and Facebook rate-limits per account rather than per caller, so under heavy concurrent load it's a small shared budget rather than an unlimited one.
If you run large jobs and want that fallback to be reliably yours, paste your own cookie into sessionCookies as c_user=<value>; xs=<value>, one line per account. Then the run uses your account and your rate limit and touches the shared one not at all. Treat those values like a password. Anyone holding them can act as that account, and Facebook may sign the session out or restrict an account it decides is automated.
The closing log line carries sessionState, so you can see which path a run took.
What people use it for
- Reselling and arbitrage. Run the same terms every morning, sort by
postedAt, look only at what appeared since yesterday. - Price research before buying or listing. Pull 200 comparable items and take the median of
priceinstead of guessing. - Local market monitoring for a category you trade in. Set
daysSinceListedto 1 so each run is only new stock. - Spotting price drops.
originalPriceminuspricegives you every listing whose seller has already cut the asking price. - Feeding a dashboard or a spreadsheet a typed table instead of scrolling an infinite feed by hand.
Reading the output
- Real rows carry
"charged": trueand"recordType": "listing". One billed event each. - The sample row carries
"_sample": trueand"charged": false. There's exactly one, and only when the input had no terms and no URLs. - Diagnostic rows carry
"_diagnostic": true,"charged": falseand anerrorCode:NO_LOCATIONwhen the run had no usable place to search,NO_RESULTSwhen a term matched nothing,BLOCKEDwhen Facebook refused every address for that term,NETWORKwhen it couldn't be reached,BAD_INPUTfor a URL that isn't a Marketplace URL,TIME_BUDGETwhen the run ran out of time.
Filter on charged == true and you have exactly the rows you paid for.
Searching by URL instead of by term
If you already built the search you want in a browser, paste the URL into startUrls. All of these work:
https://www.facebook.com/marketplace/nyc/search?query=road%20bikehttps://www.facebook.com/marketplace/la/search?query=sofa&minPrice=50&maxPrice=400https://www.facebook.com/marketplace/austin/search?query=guitar&daysSinceListed=7&sortBy=creation_time_descend
The place in the path becomes the search location, and query, minPrice, maxPrice, itemCondition, daysSinceListed, sortBy, exact and category_id become the matching filters. Anything you also set in the input fields wins over the URL, so you can paste one URL and vary one knob.
Limits
- The seller's name isn't returned. Facebook leaves it out of public search results entirely, and the only page that carries it costs far more traffic than a listing is worth. A column that's always blank is worse than no column.
- Item condition is a filter you can search by, not a field that comes back. Same reason: it lives on the individual listing page.
radiusKmis sent exactly as Facebook's own radius filter sends it, but the public search applies its own catchment around the coordinates. The same search at 2 km and at 500 km returned the same listings in the same order. Treat the coordinates as what decides the search area.- Facebook returns 24 listings per page and caps it there, so a large
maxItemsmeans several pages and a longer run. Very deep paging eventually stops producing new items. Marketplace itself does the same. currencyis read from the symbol Facebook formats the price with. Facebook disambiguates the ones that matter (CA$,MX$,A$,R$), so this is reliable, but a locale using a symbol shared by two currencies can be ambiguous.priceFormattedalways holds the literal string if you want to check.- Photo links point at Facebook's CDN and expire after a while.
- Marketplace throttles hard at peak hours. A refused term produces an uncharged
BLOCKEDrow and the run carries on to your other terms. Re-running usually succeeds. - Only listings visible without an account of yours are returned. Anything Facebook restricts to members of a private Group is out of reach.
- Hard ceilings: 1,000 rows and 10 search terms or URLs per run. Split bigger jobs.
Questions
Do I need a Facebook account or cookies?
No. You supply nothing and nothing of yours is ever logged in. The Actor carries its own fallback session for the case where Facebook refuses every address. sessionCookies exists only if you'd rather that fallback used your account instead of a shared one.
Why do I have to give a location?
Marketplace is a local marketplace, and the search endpoint returns nothing for a query with no coordinates. A plain city name is enough. It gets turned into coordinates for you.
Why is there no seller name?
Facebook doesn't include it in public search results. It's only on the individual listing page, and fetching that page per item would cost many times what a row sells for.
What happens if a term matches nothing?
One uncharged diagnostic row with errorCode: "NO_RESULTS", and the run carries on to your other terms.
Will the run fail if Facebook blocks it?
No. A refused search becomes an uncharged BLOCKED row and the run still finishes as succeeded. A failed run would still bill the start fee, which would mean paying to be told something went wrong.
Can I run it on a schedule?
Yes. Nothing is held between runs. Use listingId to work out which rows are new since last time and postedAt to sort them.
Does it work outside the United States?
Yes. City names in the UK, Canada, Australia, Europe, Latin America and Asia all resolve, and prices come back in the local currency.