Facebook Marketplace Scraper
Pricing
from $0.40 / 1,000 listings
Facebook Marketplace Scraper
Search Facebook Marketplace by keyword and place and get one row per listing: title, price, currency, city, listing link, photo and posting date. Filter by price, condition, category and recency. No login, no cookies, no browser - and the cheapest Marketplace scraper on the market.
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
17 hours ago
Last modified
Categories
Share
Give it a search term and a place, and get back the Facebook Marketplace listings that match: title, price, currency, city, listing link, photo, and when it was posted. Filter by price range, category, item condition, how recently it was listed, and sort the way the site does. No Facebook account, no cookies, no login, no browser.
- Search many terms in one run — each listing is one row, deduplicated across terms so you are never charged twice for the same item.
- Works anywhere Marketplace does: give a city name, a Marketplace city slug, or raw coordinates, and the search is centred there.
- Price range, item condition, listed-in-the-last-N-days, category and sort order are all real filters applied by Facebook, not filtered afterwards.
- Paste a Marketplace search URL straight from your browser and it is parsed into the same filters.
- Runs with empty input return one labelled sample row, free, so you can see the exact output shape before spending anything.
Price
$0.40 per 1,000 listings, plus a $0.0025 start fee per run (billed per gigabyte of run memory, so exactly that on the default 1 GB).
This is the cheapest Facebook Marketplace scraper on the market, and it is the same rate on every plan, free or paid. There are no volume tiers, no minimum spend, no subscription and no add-on fees. What you read here is what you pay on day one and on day four hundred.
| Listings | Total cost |
|---|---|
| 100 | $0.0425 |
| 1,000 | $0.4025 |
| 10,000 | $4.0025 |
| 100,000 | $40.0025 |
What is actually 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 blocked target, a dead URL, a search that matched nothing. Those rows all carry
"charged": false. - Listings that already appeared earlier in the same run are dropped before they are 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 more.
- Rows never leave the dataset without a charge, and are never charged without a row. The billed event is a named one, so there is no price quietly attached to
apify-default-dataset-item— the trick that makes some scrapers bill you for their own error messages.
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 shared 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 location-based, so this is required unless you set latitude and longitude yourself or paste a URL that carries them. |
latitude / longitude | Exact coordinates to centre the search on, if you would rather not rely on a place name. |
startUrls | Marketplace search URLs copied from your browser. The search term, place, price range, condition, sort order and category in the URL are all read out of it and applied. |
minPrice / maxPrice | Price range, in the currency of the place you are 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 within the last N days, 1 to 30. |
category | A Marketplace category id, if you want to narrow to one department. |
sortBy | One of best_match, newest, price_low_to_high, price_high_to_low, distance. Defaults to Facebook's own relevance order. |
exactMatch | Set true to make Facebook match the phrase more strictly instead of loosely. |
availability | Set to sold to look at sold listings instead of available ones. |
radiusKm / radiusMiles | Passed through to Facebook exactly as its own filter sends it. Read the limitations section before relying on it. |
maxItems | Total rows across all terms. Default 20, hard ceiling 1,000. Keep it low while testing — you pay per row. |
proxyUrls | Leave empty. Fill it in only if you want the traffic to leave through proxy servers you already pay for, as http://user:pass@host:port. |
Run it with empty input and you get one clearly labelled sample row, free, so you can see the output shape before you spend anything.
Output
One row per listing. 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
listingUrl— the permanent link to the item. Stable, and safe to use as a primary key across runs.price— a plain number in the listing currency. A "Free" listing is 0. A listing with no price at all is null.priceFormatted— exactly the string Facebook shows, symbol included.currency— the ISO code, read from the symbol Facebook formats the price with. See the limitations section.originalPrice— the crossed-out price when the seller has cut it; null otherwise.originalPriceminuspriceis the discount.priceMin / priceMax— only populated for listings sold as a price range rather than a single figure.city / state / locationName— where the seller says the item is.stateis empty in countries that do not use one.imageUrl— the primary photo, as a Facebook CDN link. Those links expire after a while, so download what you want to keep.postedAt / postedAtTimestamp— when the listing was created, as ISO 8601 in UTC and as epoch seconds.daysListed— how long the item has been sitting there, in days. Useful for spotting stale listings.isSold / isPending / isLive— the item status at read time.categoryId— Facebook's internal department id for the item. Stable, so you can group by it, but Facebook does not publish a public name for it.deliveryTypes— how the seller will hand it over, e.g.IN_PERSON,PUBLIC_MEETUP,SHIPPING.subtitle— the extra line Facebook shows under vehicle listings — mileage and similar. Null for most categories.fromGroup— set when the item was cross-posted from a buy-and-sell Group; null otherwise.
Every real row carries "charged": true. Sample rows carry "_sample": true and diagnostic rows carry "_diagnostic": true with an errorCode you can filter on, and neither is ever billed.
How it works
- It calls the same public search endpoint the Marketplace page itself calls, and reads the listings straight out of the JSON it returns — no page rendering, no headless browser, no Facebook account, no cookies and no login.
- Only the search results are fetched, which is about 2.6 KB of traffic per listing. That is deliberate: the equivalent rendered page is seventeen times bigger for the same rows, and paying for those bytes is what makes most scrapers of this site expensive.
- Requests go out through the kind of addresses Facebook serves Marketplace to, and the cost of that egress is already inside the price you see — there is nothing for you to configure and no proxy of your own to buy.
- Each search is paged until your
maxItemsis reached, and listing ids already seen in the run are skipped so you are never charged for the same item twice. - If Facebook rotates the internal id of its own search query, the Actor re-reads the current one from the page and carries on instead of failing.
What people use it for
- Reselling and arbitrage — run the same terms every morning, sort by
postedAt, and see only what appeared since yesterday. - Price research before buying or listing something: pull 200 comparable items, then take the median of
pricerather than guessing. - Local market monitoring for a category you trade in — furniture, bikes, tools, phones — with
daysSinceListedset to 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 spreadsheet with a clean typed table instead of scrolling an infinite feed by hand.
- Lead lists for dealers and buyers in one metro area — search a term, centre it on the city, keep everything under a price ceiling.
Reading the output
Every run writes three kinds of row, and they are easy to tell apart:
- Real rows carry
"charged": trueand"recordType": "listing". These are the rows you paid for, one billed event each. - The sample row carries
"_sample": trueand"charged": false. There is exactly one, it only appears when the input had no search terms and no URLs, and it exists so you can look at the shape of the output before you spend anything. - Diagnostic rows carry
"_diagnostic": true,"charged": falseand anerrorCodeyou can switch on:NO_LOCATIONwhen the run had no usable place to search,NO_RESULTSwhen a term matched nothing,BLOCKEDwhen Facebook refused every address tried for that term,NETWORKwhen it could not be reached at all,BAD_INPUTwhen a supplied URL was not a Marketplace URL, andTIME_BUDGETwhen the run ran out of time before reaching a term.
If you only want the data, filter on charged == true. The count of those rows always equals the number of events you were billed for, so the dataset is its own invoice.
Searching by URL instead of by term
If you have already built the search you want in a browser, paste the URL into startUrls and it is taken apart for you. 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 all become the matching filters. Anything you also set in the input fields wins over what the URL said, so you can paste one URL and vary one knob.
Limitations
- The seller's name is not returned. Facebook does not put it on the public search results at all — the field comes back empty for every logged-out caller, and the only page that carries it costs far more in traffic than a listing is worth. Rather than sell you a column that is always blank, it is left out.
- Item condition is a filter you can search by, but not a field that comes back, for the same reason: it lives on the individual listing page rather than in the search results.
radiusKmis sent to Facebook exactly as its 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, not the radius, 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 returning 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 period. Download anything you intend to keep.
categoryIdis Facebook's internal department id. There is no public name for it, so no name is invented.- Marketplace throttles hard at peak hours. A term that is refused produces an uncharged
BLOCKEDdiagnostic row and the run carries on to your other terms; re-running usually succeeds. - Only listings visible to a logged-out visitor are returned. Anything Facebook restricts to signed-in users, or to members of a private Group, is out of reach and always will be — this Actor never signs in.
- The hard ceilings are 1,000 rows and 10 search terms (or Marketplace URLs) per run. Split bigger jobs across runs.
Questions
Do I need a Facebook account or cookies?
No, and you should not supply any. The Actor reads only what Facebook serves to the public, with no session of any kind. Nothing you own is ever logged in and nothing is ever at risk of being flagged.
Why do I have to give a location?
Because Marketplace is a local marketplace — the search endpoint returns an empty result for a query with no coordinates. A plain city name is enough; it is turned into coordinates for you.
Why is there no seller name?
Facebook does not include it in public search results. It is only on the individual listing page, and fetching that page per item would cost many times what a row sells for. Selling you an always-blank column would be worse than saying so.
What happens if a term matches nothing?
You get one uncharged diagnostic row for that term with errorCode: "NO_RESULTS", and the run carries on to your other terms. You are never billed for a term that returned nothing.
Will the run fail if Facebook blocks it?
No. A refused search produces an uncharged BLOCKED diagnostic row and the run still finishes as succeeded. A failed run would still bill you the start fee, which would mean paying to be told something went wrong.
Can I run this on a schedule?
Yes. Nothing is held between runs, so the same input is safe to repeat. Use listingId to work out which rows are new since last time, and postedAt to sort them.
How do I get exactly the rows I paid for?
Filter the dataset on "charged": true. Sample and diagnostic rows are always false, and the number of charged rows always equals the number of billed events.
Does it work outside the United States?
Yes. Any place Marketplace operates in works — city names in the UK, Canada, Australia, Europe, Latin America and Asia all resolve, and prices come back in the local currency.