How to Build a Facebook Marketplace Price Tracker with Python
TL;DR: This tutorial walks you through building a fully automated Facebook Marketplace price tracker in Python using the SociaVault API. By the end you'll have a working script that searches listings, stores historical prices in SQLite, detects price drops, and sends Slack or email alerts — all in under 150 lines of code.
Price tracking on Facebook Marketplace is one of the most practical applications of the SociaVault API. Whether you're hunting for a deal on used furniture, monitoring resale values for your inventory, or building a price intelligence tool for clients, the same core pattern applies: search → store → compare → alert.
As of May 2026, the SociaVault API provides three endpoints that make this straightforward. Let's build it step by step.
Prerequisites
pip install requests sqlite3 smtplib
You'll need a SociaVault API key. Get one free →
Step 1: Resolve Location Coordinates
Facebook Marketplace searches are location-based. Before searching, you need to convert a city name into latitude/longitude coordinates using the location-search endpoint.
import requests
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://api.sociavault.com/v1/scrape/facebook-marketplace'
def get_location(city: str) -> dict:
"""Resolve a city name to lat/lng coordinates."""
resp = requests.get(
f'{BASE_URL}/location-search',
params={'query': city},
headers={'x-api-key': API_KEY}
)
resp.raise_for_status()
locations = resp.json().get('locations', [])
if not locations:
raise ValueError(f'No location found for: {city}')
return locations[0]
# Example
location = get_location('Austin, TX')
print(f"City: {location['city']}")
print(f"Lat: {location['latitude']}, Lng: {location['longitude']}")
# City: Austin
# Lat: 30.2672, Lng: -97.7431
The response includes page_id, latitude, longitude, and city — you'll use latitude and longitude in the next step.
Step 2: Search Listings
With coordinates in hand, search for your target product. The search endpoint returns up to 24 listings per page with price, title, photo, and location data.
def search_listings(query: str, lat: float, lng: float,
price_max: int = None, cursor: str = None) -> dict:
"""Search Marketplace listings for a keyword near a location."""
params = {
'query': query,
'latitude': lat,
'longitude': lng,
'radius_km': 50
}
if price_max:
params['price_max'] = price_max
if cursor:
params['cursor'] = cursor
resp = requests.get(
f'{BASE_URL}/search',
params=params,
headers={'x-api-key': API_KEY}
)
resp.raise_for_status()
return resp.json()
def get_all_listings(query: str, lat: float, lng: float,
price_max: int = None, max_pages: int = 5) -> list:
"""Paginate through all search results."""
all_listings = []
cursor = None
page = 0
while page < max_pages:
data = search_listings(query, lat, lng, price_max, cursor)
listings = data.get('listings', [])
all_listings.extend(listings)
cursor = data.get('cursor')
if not cursor:
break
page += 1
import time
time.sleep(0.5) # Be respectful of rate limits
return all_listings
Step 3: Store Results in SQLite
Persist listings to a local SQLite database so you can compare prices over time. The schema tracks each listing's price history with timestamps.
import sqlite3
from datetime import datetime
DB_PATH = 'marketplace_tracker.db'
def init_db():
"""Create tables if they don't exist."""
conn = sqlite3.connect(DB_PATH)
conn.executescript('''
CREATE TABLE IF NOT EXISTS listings (
id TEXT PRIMARY KEY,
title TEXT,
query TEXT,
city TEXT,
seller_id TEXT,
first_seen TEXT,
last_seen TEXT
);
CREATE TABLE IF NOT EXISTS price_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
listing_id TEXT,
price INTEGER,
recorded_at TEXT,
FOREIGN KEY (listing_id) REFERENCES listings(id)
);
''')
conn.commit()
conn.close()
def upsert_listing(listing: dict, query: str):
"""Insert or update a listing and record its current price."""
conn = sqlite3.connect(DB_PATH)
now = datetime.utcnow().isoformat()
listing_id = listing['id']
price = listing['price']['amount']
city = listing.get('location', {}).get('city', '')
seller_id = listing.get('seller_id', '')
# Upsert the listing record
conn.execute('''
INSERT INTO listings (id, title, query, city, seller_id, first_seen, last_seen)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET last_seen = excluded.last_seen
''', (listing_id, listing['title'], query, city, seller_id, now, now))
# Always record the current price
conn.execute(
'INSERT INTO price_history (listing_id, price, recorded_at) VALUES (?, ?, ?)',
(listing_id, price, now)
)
conn.commit()
conn.close()
Step 4: Compare Prices Over Time
Query the price history to detect drops. This function returns all listings where the latest price is lower than the previous recorded price.
def find_price_drops(query: str, min_drop_pct: float = 10.0) -> list:
"""Find listings where price dropped by at least min_drop_pct percent."""
conn = sqlite3.connect(DB_PATH)
# Get the two most recent prices for each listing
rows = conn.execute('''
WITH ranked AS (
SELECT
ph.listing_id,
l.title,
l.city,
ph.price,
ph.recorded_at,
ROW_NUMBER() OVER (
PARTITION BY ph.listing_id
ORDER BY ph.recorded_at DESC
) as rn
FROM price_history ph
JOIN listings l ON l.id = ph.listing_id
WHERE l.query = ?
)
SELECT
a.listing_id,
a.title,
a.city,
b.price as old_price,
a.price as new_price,
ROUND((b.price - a.price) * 100.0 / b.price, 1) as drop_pct
FROM ranked a
JOIN ranked b ON a.listing_id = b.listing_id
WHERE a.rn = 1 AND b.rn = 2
AND a.price < b.price
AND (b.price - a.price) * 100.0 / b.price >= ?
''', (query, min_drop_pct)).fetchall()
conn.close()
return [
{
'id': row[0],
'title': row[1],
'city': row[2],
'old_price': row[3],
'new_price': row[4],
'drop_pct': row[5]
}
for row in rows
]
Step 5: Send Alerts
When a price drop is detected, send a notification via email or Slack.
Email Alert
import smtplib
from email.mime.text import MIMEText
def send_email_alert(drops: list, to_email: str):
"""Send an email summary of price drops."""
if not drops:
return
lines = ['Facebook Marketplace Price Drops Detected:\n']
for drop in drops:
lines.append(
f"• {drop['title']} ({drop['city']})\n"
f" ${drop['old_price']} → ${drop['new_price']} "
f"(-{drop['drop_pct']}%)\n"
f" https://www.facebook.com/marketplace/item/{drop['id']}\n"
)
msg = MIMEText('\n'.join(lines))
msg['Subject'] = f"🔔 {len(drops)} Marketplace Price Drop(s) Found"
msg['From'] = 'alerts@yourdomain.com'
msg['To'] = to_email
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.starttls()
server.login('alerts@yourdomain.com', 'YOUR_APP_PASSWORD')
server.send_message(msg)
Slack Alert
def send_slack_alert(drops: list, webhook_url: str):
"""Post price drop alerts to a Slack channel."""
if not drops:
return
blocks = [{"type": "header", "text": {"type": "plain_text",
"text": f"🔔 {len(drops)} Marketplace Price Drop(s)"}}]
for drop in drops:
blocks.append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": (
f"*{drop['title']}* ({drop['city']})\n"
f"~~${drop['old_price']}~~ → *${drop['new_price']}* "
f"(-{drop['drop_pct']}%)\n"
f"<https://www.facebook.com/marketplace/item/{drop['id']}|View listing>"
)
}
})
requests.post(webhook_url, json={"blocks": blocks})
Putting It All Together
Here's the complete runner script. Schedule it with cron or a task scheduler to run every few hours.
import time
def run_tracker(
search_query: str,
city: str,
price_max: int = None,
alert_email: str = None,
slack_webhook: str = None
):
print(f"[{datetime.utcnow().isoformat()}] Running tracker for '{search_query}' in {city}")
# Initialize DB on first run
init_db()
# Step 1: Get location
location = get_location(city)
lat, lng = location['latitude'], location['longitude']
# Step 2: Fetch listings
listings = get_all_listings(search_query, lat, lng, price_max)
print(f" Found {len(listings)} listings")
# Step 3: Store in DB
for listing in listings:
upsert_listing(listing, search_query)
# Step 4: Find price drops
drops = find_price_drops(search_query, min_drop_pct=10.0)
print(f" Detected {len(drops)} price drop(s)")
# Step 5: Send alerts
if drops:
if alert_email:
send_email_alert(drops, alert_email)
if slack_webhook:
send_slack_alert(drops, slack_webhook)
if __name__ == '__main__':
run_tracker(
search_query='standing desk',
city='Austin, TX',
price_max=600,
alert_email='you@example.com',
slack_webhook='https://hooks.slack.com/services/YOUR/WEBHOOK/URL'
)
Cron Schedule (Linux/Mac)
# Run every 4 hours
0 */4 * * * /usr/bin/python3 /path/to/tracker.py >> /var/log/marketplace_tracker.log 2>&1
How Often Should You Poll?
| Use Case | Recommended Interval |
|---|---|
| Hot items (electronics, sneakers) | Every 1–2 hours |
| Furniture / appliances | Every 4–6 hours |
| Vehicles | Every 12–24 hours |
| Real estate signals | Daily |
Polling too frequently wastes API credits and rarely yields more data — most sellers don't update prices multiple times per day.
Related Guides
- Facebook Marketplace API Reference — full endpoint documentation
- Product Research with Marketplace Data — find arbitrage opportunities
- Facebook Marketplace for Lead Generation — B2C use cases
Frequently Asked Questions
How often should I poll the API to track prices?
For most consumer goods, every 4–6 hours is sufficient. High-velocity categories like electronics or sneakers may warrant hourly checks. Avoid polling more than once per hour per query — it rarely yields new data and burns through your API quota.
How do I store historical price data efficiently?
SQLite works well for single-machine trackers. For production systems tracking thousands of listings, use PostgreSQL with a time-series extension like TimescaleDB, or a dedicated time-series database like InfluxDB.
Can I track a specific seller's listings?
Yes. Filter the search results by seller_id and store only listings from that seller. You can then detect when they add new listings or change prices.
What happens when a listing is sold and removed?
When a listing disappears from search results, it won't appear in future fetches. You can detect this by comparing the last_seen timestamp — if a listing hasn't been seen in 24+ hours, it's likely sold or removed.
Can I run this on a free API plan?
The free tier gives you 100 requests/day. A single tracker run for one query uses 1–5 requests depending on result count. You can comfortably track 10–20 queries per day on the free tier.
Does this work for tracking car prices?
Absolutely. Use queries like "2019 Honda Civic" or "Toyota Tacoma" and set a price_max to filter out dealership listings. See the full car price research guide at /blog/used-car-price-research-facebook-marketplace.
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.