Back to Blog
General

How to Scrape Facebook Marketplace with Python in 2026

May 26, 2026
9 min read
S
By SociaVault Team
facebook marketplacepythonweb scrapingapi

How to Scrape Facebook Marketplace with Python in 2026

TL;DR: Facebook has no official Marketplace API, but the SociaVault API gives you structured JSON access to listings, prices, and item details. This guide shows you how to use it with Python — from location resolution to full item extraction — including a working price monitoring loop you can run today.

Facebook Marketplace is a goldmine of real-time pricing data, but accessing it programmatically has always been a pain. Facebook's official APIs don't cover Marketplace, and DIY scraping with Selenium or Playwright breaks constantly as Facebook updates its frontend.

The practical solution in 2026 is to use a maintained scraping API. SociaVault provides three production-ready endpoints for Marketplace data, and this guide walks through all of them with working Python code.


Prerequisites

You'll need:

  • Python 3.8+
  • The requests library (pip install requests)
  • A SociaVault API key (free at sociavault.com)

All examples use only the standard requests library — no heavy dependencies.


The Three Endpoints

EndpointPathWhat it does
Location Search/v1/scrape/facebook-marketplace/location-searchResolve a city name to coordinates
Search/v1/scrape/facebook-marketplace/searchSearch listings by keyword + location
Item Detail/v1/scrape/facebook-marketplace/itemGet full data for a single listing

Base URL: https://api.sociavault.com
Auth: x-api-key header


Step 1: Resolve a Location

Before you can search listings, you need geographic coordinates. The location search endpoint takes a city name or zip code and returns structured location data including latitude, longitude, and a page_id used internally by Marketplace.

import requests

API_KEY = "your_api_key_here"
BASE_URL = "https://api.sociavault.com"

def get_location(query: str) -> dict:
    """Resolve a city name or zip code to Marketplace location data."""
    response = requests.get(
        f"{BASE_URL}/v1/scrape/facebook-marketplace/location-search",
        params={"query": query},
        headers={"x-api-key": API_KEY},
        timeout=15
    )
    response.raise_for_status()
    locations = response.json().get("locations", [])
    if not locations:
        raise ValueError(f"No location found for query: {query}")
    return locations[0]

# Example usage
location = get_location("Austin, TX")
print(location)

Sample response:

{
  "page_id": "110774405606108",
  "name": "Austin, Texas",
  "city": "Austin",
  "state": "Texas",
  "country": "US",
  "latitude": 30.2672,
  "longitude": -97.7431,
  "radius_km": 40
}

The latitude and longitude values are what you pass to the search endpoint.


Step 2: Search Listings

With coordinates in hand, you can search for any keyword across Marketplace listings in that area. The search endpoint supports optional price range filtering and returns up to 24 listings per page with a cursor for pagination.

def search_listings(
    query: str,
    latitude: float,
    longitude: float,
    price_min: int = None,
    price_max: int = None,
    radius_km: int = 40,
    cursor: str = None
) -> dict:
    """Search Facebook Marketplace listings by keyword and location."""
    params = {
        "query": query,
        "latitude": latitude,
        "longitude": longitude,
        "radius_km": radius_km,
    }
    if price_min is not None:
        params["price_min"] = price_min
    if price_max is not None:
        params["price_max"] = price_max
    if cursor:
        params["cursor"] = cursor

    response = requests.get(
        f"{BASE_URL}/v1/scrape/facebook-marketplace/search",
        params=params,
        headers={"x-api-key": API_KEY},
        timeout=15
    )
    response.raise_for_status()
    return response.json()

# Example usage
location = get_location("Austin, TX")
results = search_listings(
    query="standing desk",
    latitude=location["latitude"],
    longitude=location["longitude"],
    price_min=50,
    price_max=500
)

for listing in results["listings"]:
    print(f"{listing['title']} — ${listing['price']['amount']}{listing['location']['city']}")

Sample listing object:

{
  "id": "1234567890123456",
  "title": "Uplift V2 Standing Desk - 60x30",
  "price": {
    "amount": 350,
    "currency": "USD",
    "formatted_amount": "$350"
  },
  "primary_photo": {
    "url": "https://scontent.xx.fbcdn.net/v/...",
    "width": 720,
    "height": 720
  },
  "location": {
    "city": "Austin",
    "state": "TX",
    "latitude": 30.2891,
    "longitude": -97.7341
  },
  "delivery_types": ["local_pickup"],
  "seller_id": "987654321",
  "listed_at": "2026-05-15T14:22:00Z"
}

Paginating Through All Results

The cursor field in the response lets you fetch the next page. Here's a helper that collects all pages:

import time

def get_all_listings(query: str, latitude: float, longitude: float, max_pages: int = 10) -> list:
    """Fetch all pages of search results for a query."""
    all_listings = []
    cursor = None
    page = 0

    while page < max_pages:
        data = search_listings(query, latitude, longitude, cursor=cursor)
        listings = data.get("listings", [])
        all_listings.extend(listings)

        cursor = data.get("cursor")
        if not cursor:
            break  # No more pages

        page += 1
        time.sleep(0.5)  # Be respectful of rate limits

    return all_listings

# Fetch up to 5 pages of standing desk listings in Austin
location = get_location("Austin, TX")
all_desks = get_all_listings(
    "standing desk",
    location["latitude"],
    location["longitude"],
    max_pages=5
)
print(f"Found {len(all_desks)} listings")

Step 3: Get Item Details

Once you have a listing id from search results, you can fetch the full item detail — including the complete description, all photos, seller profile, and category-specific attributes.

def get_item(item_id: str) -> dict:
    """Fetch full details for a single Marketplace listing."""
    response = requests.get(
        f"{BASE_URL}/v1/scrape/facebook-marketplace/item",
        params={"id": item_id},
        headers={"x-api-key": API_KEY},
        timeout=15
    )
    response.raise_for_status()
    return response.json()

# Example usage
item = get_item("1234567890123456")
print(f"Title: {item['title']}")
print(f"Price: {item['price']['formatted_amount']}")
print(f"Condition: {next((a['value'] for a in item['attributes'] if a['label'] == 'Condition'), 'N/A')}")
print(f"Seller: {item['seller']['name']} ({item['seller']['rating']}★, {item['seller']['reviews_count']} reviews)")
print(f"Description: {item['description'][:200]}...")

Sample item response (abbreviated):

{
  "id": "1234567890123456",
  "title": "Uplift V2 Standing Desk - 60x30",
  "description": "Selling my Uplift V2 standing desk. Used for 18 months, excellent condition...",
  "price": { "amount": 350, "currency": "USD", "formatted_amount": "$350" },
  "attributes": [
    { "label": "Condition", "value": "Used - Good" },
    { "label": "Category", "value": "Furniture" },
    { "label": "Brand", "value": "Uplift" }
  ],
  "photos": [
    {
      "url": "https://scontent.xx.fbcdn.net/v/photo1.jpg",
      "width": 1080,
      "height": 1080
    }
  ],
  "seller": {
    "id": "987654321",
    "name": "Sarah M.",
    "rating": 4.8,
    "reviews_count": 23,
    "response_rate": "95%"
  },
  "location": { "city": "Austin", "state": "TX", "zip": "78701" },
  "listed_at": "2026-05-15T14:22:00Z"
}

Practical Use Case: Price Monitoring Loop

Here's a complete script that monitors a category for price changes over time. It stores listings in a SQLite database and prints alerts when new listings appear below a target price.

import sqlite3
import time
import requests
from datetime import datetime

API_KEY = "your_api_key_here"
BASE_URL = "https://api.sociavault.com"

def setup_db(db_path: str = "marketplace.db") -> sqlite3.Connection:
    conn = sqlite3.connect(db_path)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS listings (
            id TEXT PRIMARY KEY,
            title TEXT,
            price INTEGER,
            city TEXT,
            seller_id TEXT,
            listed_at TEXT,
            first_seen TEXT,
            last_seen TEXT
        )
    """)
    conn.commit()
    return conn

def upsert_listing(conn: sqlite3.Connection, listing: dict):
    now = datetime.utcnow().isoformat()
    conn.execute("""
        INSERT INTO listings (id, title, price, city, seller_id, listed_at, first_seen, last_seen)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen
    """, (
        listing["id"],
        listing["title"],
        listing["price"]["amount"],
        listing["location"]["city"],
        listing.get("seller_id", ""),
        listing.get("listed_at", ""),
        now,
        now
    ))
    conn.commit()

def is_new_listing(conn: sqlite3.Connection, listing_id: str) -> bool:
    row = conn.execute("SELECT first_seen, last_seen FROM listings WHERE id = ?", (listing_id,)).fetchone()
    if not row:
        return True
    # New if first_seen == last_seen (just inserted) — handled by upsert logic
    return False

def monitor(query: str, city: str, price_alert: int, interval_seconds: int = 3600):
    """
    Monitor Marketplace for new listings below a price threshold.
    Runs indefinitely, checking every `interval_seconds`.
    """
    conn = setup_db()

    # Resolve location once
    loc_resp = requests.get(
        f"{BASE_URL}/v1/scrape/facebook-marketplace/location-search",
        params={"query": city},
        headers={"x-api-key": API_KEY},
        timeout=15
    )
    loc_resp.raise_for_status()
    location = loc_resp.json()["locations"][0]
    lat, lng = location["latitude"], location["longitude"]

    print(f"Monitoring '{query}' in {location['name']} — alerting below ${price_alert}")

    while True:
        try:
            search_resp = requests.get(
                f"{BASE_URL}/v1/scrape/facebook-marketplace/search",
                params={"query": query, "latitude": lat, "longitude": lng, "price_max": price_alert},
                headers={"x-api-key": API_KEY},
                timeout=15
            )
            search_resp.raise_for_status()
            listings = search_resp.json().get("listings", [])

            new_count = 0
            for listing in listings:
                if is_new_listing(conn, listing["id"]):
                    new_count += 1
                    price = listing["price"]["amount"]
                    print(f"[ALERT] New listing under ${price_alert}: '{listing['title']}' — ${price} in {listing['location']['city']}")
                    print(f"        ID: {listing['id']}")
                upsert_listing(conn, listing)

            print(f"[{datetime.utcnow().strftime('%H:%M:%S')}] Checked {len(listings)} listings, {new_count} new")

        except requests.RequestException as e:
            print(f"[ERROR] Request failed: {e}")

        time.sleep(interval_seconds)

if __name__ == "__main__":
    monitor(
        query="macbook pro",
        city="Austin, TX",
        price_alert=800,
        interval_seconds=3600  # Check every hour
    )

Run this script and it will alert you every time a MacBook Pro under $800 appears in Austin. Adjust the query, city, and price threshold for your use case.


Error Handling Best Practices

A few things to handle in production:

import requests
from requests.exceptions import HTTPError, Timeout, ConnectionError

def safe_search(query, lat, lng):
    try:
        resp = requests.get(
            f"{BASE_URL}/v1/scrape/facebook-marketplace/search",
            params={"query": query, "latitude": lat, "longitude": lng},
            headers={"x-api-key": API_KEY},
            timeout=15
        )
        resp.raise_for_status()
        return resp.json()

    except HTTPError as e:
        if e.response.status_code == 429:
            print("Rate limit hit — sleeping 60 seconds")
            time.sleep(60)
        elif e.response.status_code == 401:
            raise ValueError("Invalid API key") from e
        else:
            print(f"HTTP error: {e.response.status_code}")
        return None

    except Timeout:
        print("Request timed out — retrying in 10 seconds")
        time.sleep(10)
        return None

    except ConnectionError:
        print("Connection error — check your network")
        return None

Key things to handle:

  • 429 Too Many Requests — back off and retry
  • 401 Unauthorized — invalid API key, fail fast
  • Timeouts — retry with exponential backoff
  • Empty results — not an error, just no listings found


Frequently Asked Questions

Do I need to log in to Facebook to use the API?

No. SociaVault accesses publicly visible Marketplace data — the same listings any unauthenticated user can see. You don't need a Facebook account or cookies.

What Python version is required?

Python 3.8 or higher. The code uses f-strings and requests, both of which are available in any modern Python environment.

How many requests can I make per day?

Free tier: 100 requests/day. Starter: 1,000/day. Pro: 10,000/day. Each search returns up to 24 listings, so 1,000 requests can cover up to 24,000 listing records per day.

Can I run this on a server or cloud function?

Yes. The script works anywhere Python runs — a VPS, AWS Lambda, Google Cloud Functions, or a simple cron job. For scheduled monitoring, a cron job calling the script every hour is the simplest setup.

How do I search multiple cities at once?

Loop over a list of cities, resolve each one with the location search endpoint, and run your search for each set of coordinates. Store results with the city name so you can compare across markets.

Is the data real-time?

Yes. SociaVault fetches data live when you make a request — there's no cache. You always get the current state of Marketplace at the time of your request.


Get started with a free account at sociavault.com — 50 free credits, no credit card required.

Found this helpful?

Share it with others who might benefit

Ready to Try SociaVault?

Start extracting social media data with our powerful API. No credit card required.