Back to Blog
Tutorials

How to Build a Facebook Marketplace Price Tracker with Python

May 21, 2026
8 min read
S
By SociaVault Team
facebook marketplaceprice trackerpythonautomation

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 CaseRecommended Interval
Hot items (electronics, sneakers)Every 1–2 hours
Furniture / appliancesEvery 4–6 hours
VehiclesEvery 12–24 hours
Real estate signalsDaily

Polling too frequently wastes API credits and rarely yields more data — most sellers don't update prices multiple times per day.



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.


Get your free API key →

View API docs →

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.