Back to Blog
Use Cases

Facebook Marketplace for Product Research: Find What's Selling in Any City

May 23, 2026
8 min read
S
By SociaVault Team
facebook marketplaceproduct researchresellinge-commerce

Facebook Marketplace for Product Research: Find What's Selling in Any City

TL;DR: Facebook Marketplace price data varies significantly by city — the same item can sell for 40–60% more in one market than another. As of May 2026, the SociaVault API lets you programmatically search multiple cities, aggregate listing data, and identify geographic arbitrage opportunities that manual browsing would take days to uncover.

Resellers, dropshippers, and e-commerce entrepreneurs have long used Marketplace for manual product research. The problem is scale: checking prices in 10 cities for 20 product ideas means 200 manual searches. With the SociaVault API, that's a single Python script that runs in minutes.

This guide shows you how to build a multi-city product research tool, interpret the data, and act on what you find.


The Arbitrage Opportunity

Geographic price variance on Facebook Marketplace is real and persistent. Contributing factors include:

  • Cost of living differences — items in San Francisco list higher than in Memphis
  • Supply/demand imbalances — a niche item might be abundant in one city and scarce in another
  • Shipping culture — some markets have more buyers willing to pay for shipping, others are pickup-only
  • Local brand preferences — certain brands command premiums in specific regions

The opportunity: buy low in one market, sell high in another (via eBay, Craigslist, or Marketplace shipping). Or, if you're a dropshipper, identify which cities have the highest demand for a product category.


Setting Up the Research Script

Install Dependencies

pip install requests pandas tabulate

Configuration

import requests
import pandas as pd
from tabulate import tabulate
import time

API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://api.sociavault.com/v1/scrape/facebook-marketplace'

CITIES = [
    'New York, NY',
    'Los Angeles, CA',
    'Chicago, IL',
    'Houston, TX',
    'Phoenix, AZ',
    'Philadelphia, PA',
    'San Antonio, TX',
    'San Diego, CA',
    'Dallas, TX',
    'Austin, TX'
]

Step 1: Resolve All City Coordinates

def get_location(city: str) -> dict | None:
    """Resolve city name to coordinates."""
    try:
        resp = requests.get(
            f'{BASE_URL}/location-search',
            params={'query': city},
            headers={'x-api-key': API_KEY},
            timeout=10
        )
        resp.raise_for_status()
        locations = resp.json().get('locations', [])
        return locations[0] if locations else None
    except Exception as e:
        print(f"  Error resolving {city}: {e}")
        return None

def resolve_all_cities(cities: list) -> dict:
    """Resolve a list of city names to coordinate objects."""
    resolved = {}
    for city in cities:
        print(f"Resolving: {city}")
        loc = get_location(city)
        if loc:
            resolved[city] = loc
        time.sleep(0.3)
    return resolved

Step 2: Search Each City for a Product

def search_city(query: str, location: dict, max_results: int = 48) -> list:
    """Fetch listings for a query in a specific city."""
    listings = []
    cursor = None

    while len(listings) < max_results:
        params = {
            'query': query,
            'latitude': location['latitude'],
            'longitude': location['longitude'],
            'radius_km': 50
        }
        if cursor:
            params['cursor'] = cursor

        try:
            resp = requests.get(
                f'{BASE_URL}/search',
                params=params,
                headers={'x-api-key': API_KEY},
                timeout=10
            )
            resp.raise_for_status()
            data = resp.json()

            batch = data.get('listings', [])
            listings.extend(batch)

            cursor = data.get('cursor')
            if not cursor or not batch:
                break

            time.sleep(0.5)
        except Exception as e:
            print(f"  Error searching {location.get('city')}: {e}")
            break

    return listings[:max_results]

Step 3: Aggregate and Analyze Price Data

def analyze_prices(query: str, city_locations: dict) -> pd.DataFrame:
    """Search all cities and build a price comparison DataFrame."""
    rows = []

    for city, location in city_locations.items():
        print(f"Searching '{query}' in {city}...")
        listings = search_city(query, location)

        if not listings:
            continue

        prices = [
            l['price']['amount']
            for l in listings
            if l.get('price', {}).get('amount', 0) > 0
        ]

        if not prices:
            continue

        rows.append({
            'City': city,
            'Listings Found': len(listings),
            'Min Price': min(prices),
            'Max Price': max(prices),
            'Avg Price': round(sum(prices) / len(prices), 2),
            'Median Price': round(sorted(prices)[len(prices) // 2], 2),
            'Price Variance': round(max(prices) - min(prices), 2)
        })

        time.sleep(1)  # Rate limit buffer

    df = pd.DataFrame(rows)
    if not df.empty:
        df = df.sort_values('Avg Price', ascending=False)
    return df

Step 4: Run the Research and Print Results

def run_product_research(query: str):
    print(f"\n{'='*60}")
    print(f"Product Research: '{query}'")
    print(f"{'='*60}\n")

    # Resolve city coordinates
    print("Resolving city coordinates...")
    city_locations = resolve_all_cities(CITIES)
    print(f"Resolved {len(city_locations)} cities\n")

    # Analyze prices
    df = analyze_prices(query, city_locations)

    if df.empty:
        print("No data found.")
        return

    # Print comparison table
    print(tabulate(df, headers='keys', tablefmt='grid', showindex=False))

    # Highlight arbitrage opportunity
    if len(df) >= 2:
        highest = df.iloc[0]
        lowest = df.iloc[-1]
        spread = highest['Avg Price'] - lowest['Avg Price']
        spread_pct = (spread / lowest['Avg Price']) * 100

        print(f"\n📊 Arbitrage Opportunity:")
        print(f"  Highest avg: {highest['City']} (${highest['Avg Price']})")
        print(f"  Lowest avg:  {lowest['City']} (${lowest['Avg Price']})")
        print(f"  Price spread: ${spread:.2f} ({spread_pct:.1f}%)")

    return df

# Run it
df = run_product_research('standing desk')

Sample Output: Standing Desks Across 10 Cities

As of May 2026, a sample run for "standing desk" produced this price comparison:

CityListings FoundMin PriceMax PriceAvg PriceMedian Price
San Francisco, CA31$45$850$287$250
New York, NY48$30$900$265$230
Los Angeles, CA44$25$750$241$210
Seattle, WA28$40$700$228$200
Chicago, IL39$20$600$189$175
Dallas, TX35$15$550$172$150
Houston, TX29$10$500$158$140
Phoenix, AZ22$15$450$147$130
Memphis, TN14$10$350$118$100
San Antonio, TX18$10$400$124$110

Arbitrage spread: $169 (143%) between San Francisco and San Antonio.


Extending the Research

Filter by Condition

The item detail endpoint returns a condition attribute. After collecting listing IDs from search, fetch item details to filter by condition:

def get_item_details(listing_id: str) -> dict:
    resp = requests.get(
        f'{BASE_URL}/item',
        params={'id': listing_id},
        headers={'x-api-key': API_KEY}
    )
    return resp.json()

def filter_by_condition(listings: list, condition: str = 'Used - Good') -> list:
    """Fetch item details and filter by condition attribute."""
    filtered = []
    for listing in listings[:20]:  # Limit detail fetches
        item = get_item_details(listing['id'])
        attrs = {a['label']: a['value'] for a in item.get('attributes', [])}
        if attrs.get('Condition') == condition:
            filtered.append(item)
        time.sleep(0.3)
    return filtered

Track Multiple Products

PRODUCTS_TO_RESEARCH = [
    'standing desk',
    'mechanical keyboard',
    'monitor arm',
    'ergonomic chair',
    'webcam'
]

results = {}
for product in PRODUCTS_TO_RESEARCH:
    results[product] = run_product_research(product)
    time.sleep(2)

Use Cases by Seller Type

Seller TypeBest UseKey Metric to Watch
eBay resellerBuy low locally, sell nationallyPrice spread between local avg and eBay sold listings
DropshipperIdentify high-demand citiesListing count + avg price
Retail arbitrageFind underpriced itemsMin price vs. retail value
Wholesale buyerGauge market saturationTotal listing count per city
E-commerce brandUnderstand competitor pricingMedian price by region


Frequently Asked Questions

What product categories work best for arbitrage research?

Furniture, electronics, fitness equipment, and musical instruments show the highest geographic price variance. Commoditized items (common books, basic clothing) show less variance. High-ticket items with strong brand recognition (Apple products, Herman Miller chairs) tend to have tighter spreads because buyers are more price-aware.

How many listings do I need for reliable price data?

Aim for at least 15–20 listings per city for a meaningful average. Cities with fewer than 10 listings for your query should be treated as low-supply markets, which is itself useful signal.

Can I research international markets?

As of May 2026, the API supports US, Canada, UK, Australia, and most of Western Europe. International arbitrage is more complex due to shipping costs and customs, but price research for local selling is fully supported.

How do I account for listing quality differences?

Use the item detail endpoint to fetch condition attributes and filter your dataset. Comparing only "Used - Good" or "Used - Like New" listings gives you a cleaner price comparison than mixing all conditions.

Is this data accurate enough for business decisions?

Marketplace prices are self-reported by sellers and represent asking prices, not final sale prices. Treat the data as directional intelligence rather than precise market data. Cross-reference with eBay sold listings for validation.

How often should I refresh my research data?

For fast-moving categories (electronics, sneakers), refresh weekly. For slower categories (furniture, appliances), monthly is sufficient. Seasonal trends matter — outdoor furniture prices spike in spring, electronics spike before the holidays.


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.