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
requestslibrary (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
| Endpoint | Path | What it does |
|---|---|---|
| Location Search | /v1/scrape/facebook-marketplace/location-search | Resolve a city name to coordinates |
| Search | /v1/scrape/facebook-marketplace/search | Search listings by keyword + location |
| Item Detail | /v1/scrape/facebook-marketplace/item | Get 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
Related Guides
- Facebook Marketplace API overview — full endpoint reference
- Price tracking guide — building a price tracker
- Product research guide — sourcing and arbitrage
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.