Skip to main content
Thirdwatchthirdwatch
E-commerce & products

Track Craigslist Used Car Prices for Arbitrage (2026 Guide)

Scrape Craigslist used car listings across US metros. Track prices by make, model, and city. Find below-market deals with scheduled data pulls and alerts.

May 26, 2026 · 6 min read · 1,259 words
See the scraper →

Thirdwatch's Craigslist Scraper extracts used car and truck listings from any Craigslist city — title, asking price, location, neighborhood, full description, and posting date. Track pricing trends by make and model across metros, spot below-market deals before other buyers, and identify geographic price arbitrage opportunities where the same vehicle sells for significantly less in one city than another. Built for used car dealers, flippers, auto market analysts, and anyone who buys and sells vehicles at scale.

Why track Craigslist used car prices

Craigslist remains the largest peer-to-peer used vehicle marketplace in the United States. While Autotrader and CarGurus aggregate dealer inventory, Craigslist is where private sellers list their vehicles — often at prices 10-20 percent below dealer retail. According to Cox Automotive's 2025 Used Car Market Report, private-party transactions account for roughly 30 percent of all used vehicle sales in the US, and Craigslist is the dominant channel for these transactions.

The arbitrage opportunity is structural. A 2019 Honda Civic in Phoenix may list for $2,000 less than the same car in Seattle due to local supply, weather-driven demand, and seller urgency. A used car dealer who monitors prices across 10 metros can spot these differentials and buy low in one market to sell higher in another. A flipper watching a specific make and model wants instant alerts when a listing drops below fair market value. An analyst tracking depreciation curves needs historical pricing data that KBB and Edmunds estimates cannot provide at the individual-listing level. All of these require structured, recurring Craigslist data.

How does this compare to the alternatives?

Three ways to systematically track Craigslist used car prices:

Approach Reliability Setup time Maintenance
Manual browsing across metros Not scalable beyond 2-3 cities Ongoing daily effort N/A
DIY Python scraper Breaks on Craigslist changes 2-4 days You fix breakages
Thirdwatch Craigslist Scraper Maintained; handles pagination 5 minutes Thirdwatch tracks site changes

KBB, Edmunds, and CarGurus provide estimated values but not actual listing prices from private sellers. The Craigslist Scraper captures the real asking prices that private sellers post — the actual transaction layer where arbitrage exists.

How to track Craigslist used car prices in 4 steps

Step 1: How do I set up my Apify account?

Sign up at apify.com (free tier, no credit card). Copy your API token from Settings, then Integrations:

export APIFY_TOKEN="apify_api_xxxxxxxxxxxxxxxx"

Step 2: How do I pull car listings for a specific make and model?

Set category to cta (cars and trucks) and pass the make/model as query. The actor returns structured listings with price, location, and full description.

import os, requests, pandas as pd

ACTOR = "thirdwatch~craigslist-scraper"
TOKEN = os.environ["APIFY_TOKEN"]

resp = requests.post(
    f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items",
    params={"token": TOKEN},
    json={
        "city": "losangeles",
        "category": "cta",
        "query": "honda civic 2019",
        "maxResults": 100,
    },
    timeout=300,
)
df = pd.DataFrame(resp.json())
print(f"{len(df)} Honda Civic 2019 listings in Los Angeles")
print(df[["title", "price", "neighborhood", "posted_date"]].head(10))

The cta category covers cars, trucks, SUVs, and vans. For motorcycles, use mca. For auto parts, use pta.

Step 3: How do I compare prices across metros and find arbitrage?

Run the same query across multiple cities and compute price differentials:

import re

METROS = [
    {"city": "losangeles", "label": "Los Angeles"},
    {"city": "phoenix", "label": "Phoenix"},
    {"city": "dallas", "label": "Dallas"},
    {"city": "seattle", "label": "Seattle"},
    {"city": "denver", "label": "Denver"},
]

def parse_price(price_str):
    if not price_str:
        return None
    digits = re.sub(r"[^\d.]", "", price_str)
    return float(digits) if digits else None

all_cars = []
for metro in METROS:
    resp = requests.post(
        f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items",
        params={"token": TOKEN},
        json={
            "city": metro["city"],
            "category": "cta",
            "query": "toyota camry 2020",
            "maxResults": 50,
        },
        timeout=300,
    )
    cars = resp.json()
    for c in cars:
        c["metro"] = metro["label"]
        c["price_numeric"] = parse_price(c.get("price"))
    all_cars.extend(cars)

df = pd.DataFrame(all_cars)
priced = df[df["price_numeric"].notna()]

# Median price by metro
metro_medians = priced.groupby("metro")["price_numeric"].median().sort_values()
print("Median asking price by metro:")
print(metro_medians)

# Arbitrage spread
cheapest = metro_medians.iloc[0]
most_expensive = metro_medians.iloc[-1]
spread = most_expensive - cheapest
print(f"\nArbitrage spread: ${spread:,.0f} "
      f"({metro_medians.index[0]} -> {metro_medians.index[-1]})")

A spread of $1,500-3,000 on a mid-range sedan is common between Sun Belt and Pacific Northwest metros. Factor in transport costs ($500-1,000 for a carrier) to determine net arbitrage margin.

Step 4: How do I set up daily alerts for below-market deals?

Schedule daily pulls and flag listings priced below your buy threshold:

curl -X POST "https://api.apify.com/v2/schedules?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "craigslist-la-camry-daily",
    "cronExpression": "0 6 * * *",
    "timezone": "America/Los_Angeles",
    "isEnabled": true,
    "actions": [{
      "type": "RUN_ACTOR",
      "actorId": "thirdwatch~craigslist-scraper",
      "runInput": {
        "city": "losangeles",
        "category": "cta",
        "query": "toyota camry",
        "maxResults": 100
      }
    }]
  }'

In your downstream pipeline, compare each listing's price against the metro median and flag deals:

metro_median = priced[priced["metro"] == "Los Angeles"]["price_numeric"].median()
threshold = metro_median * 0.85  # 15% below median

deals = priced[
    (priced["metro"] == "Los Angeles")
    & (priced["price_numeric"] <= threshold)
    & (priced["price_numeric"] > 1000)  # filter out $1 placeholder posts
]
print(f"{len(deals)} below-market deals found:")
print(deals[["title", "price", "neighborhood", "url"]])

Add an ACTOR.RUN.SUCCEEDED webhook to trigger the analysis automatically and send deal alerts to Slack or email.

Sample output

A single record from the dataset for a Los Angeles used car listing:

{
  "title": "2020 Toyota Camry SE - Clean Title, Low Miles",
  "price": "$19,500",
  "location": "Los Angeles",
  "neighborhood": "Valley / Van Nuys",
  "description": "2020 Toyota Camry SE, 38k miles, clean title, single owner. Regular maintenance at Toyota dealer. New tires, recently detailed. Bluetooth, backup camera, lane assist. Selling because upgrading to hybrid. $19,500 OBO. Text preferred.",
  "posted_date": "2026-05-23",
  "url": "https://losangeles.craigslist.org/sfv/cta/d/van-nuys-2020-toyota-camry-se/7845678901.html"
}

price is the raw asking price as posted — "OBO" (or best offer) indicators appear in the description, not the price field. neighborhood tells you where the car is physically located, useful for estimating pickup logistics. description contains mileage, title status, condition notes, and seller contact preferences. posted_date indicates freshness — listings older than 7 days in popular segments may signal an overpriced vehicle.

Common pitfalls

Three issues surface in used car price tracking. Salvage and rebuilt titles — Craigslist does not structurally separate clean-title from salvage-title vehicles. A low-priced listing may be salvage. Scan the description for "salvage", "rebuilt", "flood", or "as-is" keywords and exclude from clean-title aggregations. Dealer vs private seller mixing — dealers post on Craigslist alongside private sellers, often at higher prices. Dealer posts tend to have phone numbers, "financing available", and multiple listings from the same poster. Filter by description patterns if your analysis targets private-party pricing only. Year and mileage extraction — these critical attributes live in the unstructured title and description fields. Parse year with a four-digit regex (2015-2026 range) and mileage with patterns like "38k miles" or "38,000 mi". Without this, price comparisons across different model years are meaningless.

The actor handles Craigslist pagination and HTML parsing. For multi-city arbitrage operations covering 10+ metros, run parallel Apify schedules and merge into a single price database. Store snapshots with timestamps so you can track how long a listing survives — fast-selling vehicles indicate strong demand at that price point, while stale listings signal overpricing or mechanical issues not visible in the data. Over 4-6 weeks of daily snapshots, you can build a statistical model of fair market value per make, model, and metro that outperforms KBB estimates for the private-party segment.

Related use cases

Frequently asked questions

How do I search for a specific car make and model on Craigslist?

Set category to cta for cars and trucks, then pass the make and model as the query parameter. For example, query set to honda civic returns Honda Civic listings. Add year or trim terms to narrow further.

Does the actor return mileage or year as structured fields?

The actor returns the full description text where sellers typically include year, mileage, and condition details. These are not parsed into separate fields because Craigslist does not structure them. Extract year and mileage from the title or description with regex.

How many used car listings can I pull per city?

Up to 500 per run via the maxResults parameter. Major metros like Los Angeles and Dallas typically have hundreds of active car listings at any time. For exhaustive coverage, run separate queries per make or model.

Can I compare prices across different cities?

Yes. Run the actor separately for each city with the same query and merge the datasets. Price differentials of 10-20 percent between metros are common for the same make, model, and year — the basis for geographic arbitrage.

How often should I scrape for used car arbitrage?

Daily for active arbitrage operations. Craigslist car listings turn over quickly — popular models sell within 3-7 days. A morning pull catches fresh overnight postings before other buyers see them.

Related

Try it yourself

100 free credits, no credit card.

About 30 real searches. Add the MCP to Claude or Cursor in two minutes.