# Facebook Marketplace Scraper — Search Results Export (`devilscrapes/facebook-marketplace-scraper`) Actor

Export public Facebook Marketplace search results — title, price, seller, city/state, and delivery type — for one or more search queries and a buy-location. No login, no browser automation. Deduplicated listings; pay only for results that land.

- **URL**: https://apify.com/devilscrapes/facebook-marketplace-scraper.md
- **Developed by:** [DevilScrapes](https://apify.com/devilscrapes) (community)
- **Categories:** Social media, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

<div align="center">
  <img src=".actor/icon.svg" width="160" alt="Devil Scrapes mark" />

## Facebook Marketplace Scraper

**$5.20 / 1 000 listings**  ·  pay only for results  ·  no credit card to try

*Captchas are our cardio.* 😈

Export public Facebook Marketplace search results — title, price, seller, city/state, and delivery type — for one or more search terms and a buy-location. No login, no browser automation.

</div>

***

Give this Actor a search term (`bike`, `sofa`, whatever your buyers are hunting for) and a buy-location, and it walks Marketplace's own search results the same way a logged-out browser would — talking to the internal GraphQL endpoint the search page itself loads, handling the persisted-query id Meta rotates on every deploy, and handing you back structured JSON instead of a page you'd have to scroll and screenshot yourself. Pricing is per listing landed — a narrow query at a quiet location still costs you only the flat start fee.

### 🎯 What this scrapes

Public Facebook **Marketplace search results** — the same listings you'd see running that search logged out. For each `queries` entry we:

- Search Marketplace from your chosen `latitude`/`longitude`/`radius_km`.
- Walk the search results page by page, up to `max_pages_per_query`.
- Emit one row per listing: title, price, seller, location, and delivery options.

Listing detail pages, seller profiles, messaging, and category browsing without a search term are out of scope — see Limitations below.

### 🔥 Features

- 🛡️ **We rotate browser fingerprints** via `curl-cffi` impersonation so Facebook's servers see a real browser's TLS handshake, not a bare Python client.
- 🔁 **We retry with exponential backoff** on `408`/`429`/`503` and network errors — up to 5 attempts per request, honouring `Retry-After` when Facebook sends it.
- 🔧 **We self-heal the persisted-query id.** Meta rotates the internal `doc_id` this endpoint depends on with every deploy; when a search comes back with a hard error, we re-derive the current id straight from the search page's own JS bundles, cache it, and retry — no manual patch, no waiting on us to ship a fix.
- 🧱 **We isolate failures per query.** A search term that fails doesn't take down the rest of your batch — it's logged and reported on its own, and the run keeps going.
- 🧊 **We keep the dataset clean and deduplicated** — Pydantic-validated rows, ISO-8601 timestamps, and the same listing never billed twice even if it turns up under two different search terms in one run.
- 💰 **You pay only for results that land.** A quiet search that genuinely finds nothing costs only the small `actor-start` warm-up fee — never billed per listing for listings that weren't there.

### 🧭 Use cases

- **Resale/arbitrage sourcing** — track new listings for a product category across one or more buy-locations to spot underpriced inventory fast.
- **Local market research** — see what's actually selling (and for how much) in a category near a given city, without manually paging through Marketplace.
- **Price monitoring** — watch a search term's price spread over time to inform your own listing price.
- **Lead generation for local services** — find people selling items that signal a need (moving boxes, old furniture, used equipment) in your service area.

### ⚙️ How to use it

1. Click **Try for free** (or run via API/CLI) and open the input form.
2. Enter one or more `queries` — the search terms you'd type into Marketplace yourself.
3. Set `latitude`/`longitude`/`radius_km` to the buy-location you want results centered on. Defaults to New York City, 500 km radius.
4. Set `max_pages_per_query` if you want fewer or more results than the default `3` pages per search.
5. Leave `proxy_configuration` on its default (Residential, US). Facebook serves a login wall to datacenter IPs, so this one is required rather than optional — we measured it.
6. Run it. Each query is processed in turn; results land in the dataset as they're scraped, so you can start exporting before the run finishes.

### 📥 Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `queries` | array of string | — | Required. One or more Marketplace search terms. |
| `latitude` | number | `40.7128` (NYC) | Buy-location latitude, `-90..90`. |
| `longitude` | number | `-74.0060` (NYC) | Buy-location longitude, `-180..180`. |
| `radius_km` | integer | `500` | Search radius around the buy-location, `1..500`. |
| `max_pages_per_query` | integer | `3` | Stop paginating a query's results after this many pages, `1..50`. |
| `proxy_configuration` | object | Residential, US | Apify Proxy configuration. Measured in the cloud: the plain runner IP and the shared datacenter pool both get Facebook's login wall; Residential/US returns real listings. |

```json
{
  "queries": ["bike", "sofa"],
  "latitude": 40.7128,
  "longitude": -74.0060,
  "radius_km": 500,
  "max_pages_per_query": 2,
  "proxy_configuration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US" }
}
```

### 📤 Output

| Field | Type | Notes |
|---|---|---|
| `query` | string | Echo of the input search query this listing was found under. |
| `listing_id` | string | Facebook Marketplace listing id. |
| `title` | string | Listing title. |
| `listing_url` | string | Constructed link to the listing on Marketplace. |
| `price_formatted` / `price_amount` | string | null / number | null | Human-readable and numeric listing price. |
| `strikethrough_price_formatted` | string | null | Original (pre-discount) price, when the seller shows one. |
| `creation_time` | string (ISO-8601) | Listing creation time. |
| `city` / `state` | string | null | Reverse-geocoded listing location. |
| `seller_name` / `seller_id` | string | null | Seller info — sometimes null; Marketplace occasionally omits the seller block entirely. |
| `delivery_types` | array of string | e.g. `["IN_PERSON", "PUBLIC_MEETUP"]`. |

```json
{
  "query": "bike",
  "listing_id": "1378988563664222",
  "title": "Lightweight & Comfortable City Bike - $160",
  "listing_url": "https://www.facebook.com/marketplace/item/1378988563664222/",
  "price_formatted": "PLN160",
  "price_amount": 160.0,
  "strikethrough_price_formatted": "PLN190",
  "creation_time": "2026-08-19T00:00:00+00:00",
  "city": "Palo Alto",
  "state": "CA",
  "seller_name": null,
  "seller_id": null,
  "delivery_types": ["IN_PERSON", "PUBLIC_MEETUP"]
}
```

### 💰 Pricing

Pay-per-event. No subscription, no seat fee — you're billed for a run's warm-up and for every listing that actually lands in your dataset.

| Event | Price | Trigger |
|---|---|---|
| `actor-start` | $0.20 | Once per run, the first time any query's search results are successfully read (whether that yields listings or a genuine zero-result search). |
| `listing-scraped` | $0.005 | Once per unique listing written to the dataset. |

At the default settings that works out to **$5.20 per 1,000 listings** ($0.20 start + 1,000 × $0.005). A search that genuinely finds nothing still costs the $0.20 start fee and nothing more — you're never billed per listing for listings that weren't there. The same listing turning up under two different search terms in one run is billed once, not twice.

### 🚧 Limitations

- **Public Marketplace search results only.** No login, no cookies beyond the anonymous bootstrap request, and no account of ours is used to reach anything.
- **No listing detail pages.** You get the primary thumbnail's data path, not the full photo gallery, seller profile, or full description text.
- **No messaging or making offers.** Purely a read-only export of what's already publicly visible.
- **No category browsing without a search term, and no saved searches.** Every run needs at least one `queries` entry.
- **No historical/date-range seeking.** The Actor walks forward from the current search cursor only; it doesn't jump to an arbitrary past date.
- **Result density varies.** A narrow query/location combination can legitimately return one listing, or zero — that's a real, successful answer, not a failure, and it's priced accordingly (you only pay for what lands).
- If Facebook's servers can't be reached at all, or the search page comes back as a login wall/challenge, that query is reported as failed rather than as an empty (but successful) result — the two are never conflated.

### ❓ FAQ

**Does this need a Facebook account or access token?**
No. It reads the same public search results anyone can see logged out.

**Can it scrape listing detail pages, seller profiles, or messages?**
No — search results only. See Limitations.

**Why did one of my `queries` come back with zero rows?**
Either that search genuinely has no matching listings right now near your buy-location (a real, successful zero), or the search page didn't resolve at all (login wall/challenge) — the run's status message tells you which, per query.

**What proxy should I use?**
Residential, pinned to the US — and that is the default, so you do not need to do anything. We measured it in the cloud: Facebook hands the plain Apify runner IP and the shared datacenter pool a login wall and zero rows, while a US residential exit returns real listings. The country is pinned on purpose, because a geo-random exit would quietly return a different region's Marketplace rather than an error.

**Will I get charged twice if the same listing shows up under two search terms?**
No. Listings are deduplicated across the whole run — a repeat is never pushed to the dataset or billed twice.

### 🙋 Your feedback

Found a query this doesn't handle right, or want a field we don't capture yet? Message **DevilScrapes** on Apify or open an issue from the Actor's Store page — we read every one and ship fixes fast.

# Changelog

This Actor's version history is a separate document: https://apify.com/devilscrapes/facebook-marketplace-scraper/changelog.md

# Actor input Schema

## `queries` (type: `array`):

Marketplace search terms, e.g. <code>bike</code> or <code>sofa</code>. One or more.

## `latitude` (type: `number`):

Latitude of the buy-location search is centered on.

## `longitude` (type: `number`):

Longitude of the buy-location search is centered on.

## `radius_km` (type: `integer`):

Search radius around the buy-location, in kilometers.

## `max_pages_per_query` (type: `integer`):

Stop paginating a query's search results after this many refetch pages.

## `proxy_configuration` (type: `object`):

Apify Proxy. <strong>Defaults to Residential (US)</strong> — measured in the cloud on 2026-09-19: the Apify runner's own egress and the shared datacenter pool both hit Facebook's login wall (0 rows), while Residential/US returned real listings. Residential is required here, not optional.

## Actor input object example

```json
{
  "queries": [
    "bike",
    "sofa"
  ],
  "latitude": 40.7128,
  "longitude": -74.006,
  "radius_km": 500,
  "max_pages_per_query": 3,
  "proxy_configuration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

Full set of scraped listings — one item per listing. Streamed JSON via the Apify dataset API.

## `datasetItemsCsv` (type: `string`):

Same data, CSV export. Drop straight into a spreadsheet or warehouse loader.

## `datasetView` (type: `string`):

Open the run's dataset in the Console with the Overview view applied.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "queries": [
        "bike",
        "sofa"
    ],
    "latitude": 40.7128,
    "longitude": -74.006,
    "radius_km": 500,
    "max_pages_per_query": 3,
    "proxy_configuration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/facebook-marketplace-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "queries": [
        "bike",
        "sofa",
    ],
    "latitude": 40.7128,
    "longitude": -74.006,
    "radius_km": 500,
    "max_pages_per_query": 3,
    "proxy_configuration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/facebook-marketplace-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "queries": [
    "bike",
    "sofa"
  ],
  "latitude": 40.7128,
  "longitude": -74.006,
  "radius_km": 500,
  "max_pages_per_query": 3,
  "proxy_configuration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call devilscrapes/facebook-marketplace-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,devilscrapes/facebook-marketplace-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/EWzQWp5nsFVmzEQBx/builds/QTP5wufQTqSS7BcHu/openapi.json
