---
title: "Craigslist scraper"
description: "Scrape Craigslist searches for housing, cars, jobs and for-sale posts: title, price, neighborhood and post URL as JSON. One API request from Python, Node.js or MCP."
canonical: https://www.zenrows.com/scrape/craigslist
updated: 2026-10-20
---

# Craigslist scraper

The Zenrows Craigslist scraper turns a Craigslist search URL into structured JSON: the search title, and for every post its position, title, price, neighborhood or city and post URL. It works on housing, cars, jobs and for-sale searches in any Craigslist city. Fetch in Adaptive Stealth Mode opens the page and Extract returns the fields. Run it from an API call, the CLI or an AI agent.

- Site: craigslist.org
- Returns: Posts with titles, prices, neighborhoods and URLs

## Run it: one Craigslist search in, JSON out

```json Input
{
  "url": "https://sfbay.craigslist.org/search/apa",
  "mode": "auto",
  "extract": "auto"
}
```

```json Output
{
  "search_context": {
    "title": "Apartments / Housing For Rent in SF Bay Area",
    "total_listings_on_page": 283
  },
  "listings": [
    {
      "position": 1,
      "title": "3 bedroom 1.5 townhouse,remodeled,low price, 2,675",
      "price": 2675,
      "location": "oakland east",
      "url": "https://www.craigslist.org/view/d/oakland-bedroom-15/pZRbPTHgMwpomw3RxUnDPs"
    },
    {
      "position": 2,
      "title": "The lifestyle you've been dreaming of: Explore our 2 BR.",
      "price": 2853,
      "location": "Fremont",
      "url": "https://www.craigslist.org/view/d/fremont-the-lifestyle-youve-been/wew5qgKcZ5qYQ31BMRQBac"
    },
    {
      "position": 3,
      "title": "Cute 2 bdrm 2 ba house near Corralitos",
      "price": 3650,
      "location": "Watsonville Corralitos",
      "url": "https://www.craigslist.org/view/d/freedom-cute-bdrm-ba-house-near/8pKbLSgx7A1Lxd9Xc82EPr"
    }
  ]
}
```

Sample output for a Craigslist apartments search in the SF Bay Area, built from a live request on Oct 20, 2026, trimmed for length.

## What data can you scrape from Craigslist?

A Craigslist search page lists every matching post with the same few facts: what it is, how much it costs and where it is. These are the fields, with the path where each lives in the JSON.

| Field | What it holds | Example |
|---|---|---|
| `search_context.title` | The category and area of the search | `Apartments / Housing For Rent in SF Bay Area` |
| `search_context.total_listings_on_page` | How many posts the page listed | `283` |
| `listings[].position` | The post's place in the results | `1` |
| `listings[].title` | The post title as the poster wrote it | `Cute 2 bdrm 2 ba house near Corralitos` |
| `listings[].price` | Asking price or monthly rent, as a number | `3650` |
| `listings[].location` | Neighborhood, city or address the poster gave | `Watsonville Corralitos` |
| `listings[].url` | The post page, with Craigslist's post ID at the end | `https://www.craigslist.org/view/d/…/8pKbLSgx7A1Lxd9Xc82EPr` |

Post pages add the full description, photos, the posted and updated dates and, in housing and cars, attributes such as bedrooms, square feet, odometer or model year. Request the `url` of each post to collect them.

## How to scrape Craigslist with Zenrows

1. **Create a free Zenrows account** and copy your API key from the dashboard.
2. **Pick the city and category.** Every Craigslist city has its own subdomain (`sfbay`, `newyork`, `chicago`, `losangeles`) and every category its own code: `apa` for apartments, `cta` for cars and trucks, `sss` for all for sale, `jjj` for jobs.
3. **Add your filters to the URL.** Run the search on Craigslist, then copy the URL with `query`, `min_price`, `max_price`, `postal` and `search_distance` already in it.
4. **Send it with the default configuration**: Adaptive Stealth Mode to open the page and Extract to return the fields. Read `listings` from `parsed`.
5. **Send the post URLs to Batch** when you need descriptions and attributes, and run the same searches each day to catch new posts.

```python Python
# pip install requests
import json
import requests

params = {
    "apikey": "YOUR_ZENROWS_API_KEY",
    "url": "https://sfbay.craigslist.org/search/apa",
    "mode": "auto",     # Adaptive Stealth Mode
    "extract": "auto",  # structured JSON
}
response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
response.raise_for_status()

data = response.json()["parsed"]
print(data["search_context"]["title"])
for post in data["listings"]:
    print(post["position"], post["price"], post["location"], post["title"])
```

```javascript Node.js
// Node 18+ (built-in fetch)
const params = new URLSearchParams({
  apikey: "YOUR_ZENROWS_API_KEY",
  url: "https://sfbay.craigslist.org/search/apa",
  mode: "auto",
  extract: "auto",
});

const response = await fetch(`https://api.zenrows.com/v1/?${params}`, {
  signal: AbortSignal.timeout(180_000),
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);

const { parsed } = await response.json();
const underBudget = parsed.listings.filter((post) => post.price > 0 && post.price <= 2500);
console.table(underBudget.map(({ title, price, location }) => ({ title, price, location })));
```

```bash cURL
curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fsfbay.craigslist.org%2Fsearch%2Fapa&mode=auto&extract=auto"

# Only the fields you need
curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fsfbay.craigslist.org%2Fsearch%2Fapa&mode=auto&extract=auto&extract_fields=listings.title%2Clistings.price%2Clistings.location%2Clistings.url"
```

```python Python SDK
# pip install zenrows
from zenrows import ZenRowsClient

client = ZenRowsClient("YOUR_ZENROWS_API_KEY")
response = client.get(
    "https://sfbay.craigslist.org/search/cta?query=toyota+tacoma",
    params={"mode": "auto", "extract": "auto"},
)
for post in response.json()["parsed"]["listings"]:
    print(post["price"], post["location"], post["url"])
```

## Input

The scraper takes one Craigslist URL per request, plus the options below. The same parameters go into every task of a Batch job, so the input for many cities is one search URL per city with these settings.

| Parameter | Type | Default | Description |
|---|---|---|---|
| `url` | string | required | A Craigslist search (`https://CITY.craigslist.org/search/CATEGORY` with any filters) or a post page |
| `mode` | string | `auto` | Adaptive Stealth Mode: starts with the lightest setup and adds rendering and protected-site handling only when the page needs them |
| `extract` | string | `auto` | Returns the page's fields as JSON in `parsed`, next to the raw `html` |
| `extract_fields` | string | all fields | Comma-separated paths to keep, such as `listings.title,listings.price` |
| `proxy_country` | string | any | Two-letter country code. The city is set by the subdomain, so this is optional; use `us` if you want US traffic for US cities |
| `wait_for` | string | none | A CSS selector to wait for before the page is read |
| `session_id` | integer | none | Reuse the same session across several requests, up to 10 minutes |

```json Batch input
{
  "type": "regular",
  "status": "closed",
  "zenrows_params": {
    "mode": "auto",
    "extract": "auto"
  },
  "tasks": [
    { "url": "https://sfbay.craigslist.org/search/apa", "external_id": "sfbay-apartments" },
    { "url": "https://sfbay.craigslist.org/search/cta", "external_id": "sfbay-cars" },
    { "url": "https://newyork.craigslist.org/search/apa", "external_id": "newyork-apartments" }
  ]
}
```

## Output

Every response is JSON with two keys: `html`, the page as Craigslist served it, and `parsed`, the structured record. A search page returns `search_context` and a `listings` array in the order Craigslist shows the posts. The sample below holds real values from the SF Bay Area apartments search on Oct 20, 2026.

```json Search results
{
  "search_context": {
    "title": "Apartments / Housing For Rent in SF Bay Area",
    "total_listings_on_page": 283
  },
  "listings": [
    {
      "position": 4,
      "title": "Microwave, Large Closets, Studio",
      "price": 2367,
      "location": "san jose west",
      "url": "https://www.craigslist.org/view/d/campbell-microwave-large-closets-studio/5cMETLtm2FQ3aYeh9GBcXT"
    },
    {
      "position": 8,
      "title": "One Bedroom Apartment",
      "price": 1995,
      "location": "San Lorenzo",
      "url": "https://www.craigslist.org/view/d/san-lorenzo-one-bedroom-apartment/oP5BcFvtTmskbbBBmXr9D4"
    },
    {
      "position": 15,
      "title": "*Spacious 1-bedroom, 1-bath apartment*",
      "price": 1800,
      "location": "743 Hillside Blvd, Daly City, CA",
      "url": "https://www.craigslist.org/view/d/daly-city-spacious-bedroom-bath/7vJreptcCXoquPM94Y5fuT"
    },
    {
      "position": 27,
      "title": "Covered Garage Parking, Hardwood Style Flooring, EV Parking Stations",
      "price": 0,
      "location": "redwood city",
      "url": "https://www.craigslist.org/view/d/redwood-city-covered-garage-parking/pnmzbU9MnuAx94ri5TSGQw"
    }
  ]
}
```

Posters write titles and locations freely, so expect mixed case, abbreviations and the odd `0` price. Clean them after you load the data, for example with pandas: `pd.json_normalize(data["listings"])`. For an LLM, ask for the page as Markdown instead.

## Scrape Craigslist from your coding agent

Claude Code, Cursor, Codex and any MCP client can scrape Craigslist from a prompt.

The agent sends the same Fetch and Extract request and reads the same `listings`, so "find one-bedrooms under $2,000 in the East Bay" becomes one search URL and a filter on price and location. Connect the Zenrows MCP server once, or install the CLI and let the agent run it in the terminal.

```bash Connect the MCP server (Claude Code)
claude mcp add zenrows -e ZENROWS_API_KEY=YOUR_ZENROWS_API_KEY -- npx -y @zenrows/mcp
```

```json MCP config (Cursor, Claude Desktop, Windsurf)
{
  "mcpServers": {
    "zenrows": {
      "command": "npx",
      "args": ["-y", "@zenrows/mcp"],
      "env": { "ZENROWS_API_KEY": "YOUR_ZENROWS_API_KEY" }
    }
  }
}
```

```bash Or run the CLI
npx -y @zenrows/cli init
zenrows extract "https://sfbay.craigslist.org/search/apa"
```

```text Then ask your agent
Use Zenrows to read https://sfbay.craigslist.org/search/apa and list
the posts in Oakland, Berkeley or Hayward priced between $1,500 and
$2,500. Show title, price, location and link, cheapest first, and
skip posts with a price of 0.
```

## Scrape Craigslist at scale with Batch and Crawl

**Batch** runs a list of Craigslist URLs as one job, in parallel, with your own ID on each result. The usual input is one search per city and category, such as apartments in 20 metros or cars and trucks in every city you buy from, run once or twice a day. A second job over the post URLs collects descriptions and attributes.

**Crawl** follows the links for you. Start it on a search page, keep only links that contain `/view/`, and it returns every post page it reaches.

```bash Batch (cURL)
curl -X POST "https://async.api.zenrows.com/v1/jobs" \
  -H "X-API-Key: YOUR_ZENROWS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "regular",
    "status": "closed",
    "zenrows_params": { "mode": "auto", "extract": "auto" },
    "tasks": [
      { "url": "https://sfbay.craigslist.org/search/apa", "external_id": "sfbay-apartments" },
      { "url": "https://newyork.craigslist.org/search/apa", "external_id": "newyork-apartments" }
    ]
  }'

# Read the results when the job finishes
curl "https://async.api.zenrows.com/v1/jobs/JOB_ID/results?status=successful" \
  -H "X-API-Key: YOUR_ZENROWS_API_KEY"
```

```bash Batch (CLI)
# searches.jsonl: one {"url": "...", "external_id": "...", "extract": "auto"} per line
zenrows batch create searches.jsonl --wait
zenrows batch results <job-id> --out craigslist-posts.jsonl
```

```bash Crawl (cURL)
curl -X POST "https://api.zenrows.com/v1/crawls" \
  -H "X-API-Key: YOUR_ZENROWS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://sfbay.craigslist.org/search/apa",
    "depth": 1,
    "max_pages": 300,
    "include_patterns": ["/view/"]
  }'
```

**Scheduled jobs** run the same Batch job on a timetable, on Zenrows' side: every few hours, once a day, at set times on chosen days, or once at a future date. Each run keeps its own results, a webhook can tell your app when a run finishes, and you can pause or resume the schedule at any time, with no cron server to keep alive.

```bash Scheduled job (cURL)
# Runs every day on Zenrows' side; each run keeps its own results
curl -X POST "https://async.api.zenrows.com/v1/jobs" \
  -H "X-API-Key: YOUR_ZENROWS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "type": "scheduled",
      "status": "closed",
      "schedule": {
        "rate": {
          "every": 1,
          "unit": "day"
        }
      },
      "zenrows_params": {
        "mode": "auto",
        "extract": "auto"
      },
      "tasks": [
        {
          "url": "https://sfbay.craigslist.org/search/apa",
          "external_id": "craigslist"
        }
      ]
    }'

# Or at set times: "schedule": { "calendar": { "times_of_day": ["09:00"],
#   "cadence": { "weekly": { "days": ["mon", "thu"] } } }, "timezone": "Europe/Madrid" }
```

## What teams do with Craigslist data

| Use case | Data they pull | Who runs it |
|---|---|---|
| Local rent tracking | Rent, title and neighborhood from apartments searches, per city and day | Landlords, property managers and housing researchers |
| Used car sourcing | Price, title and location from cars and trucks searches, matched to make and model | Dealers, flippers and car buying services |
| Resale and arbitrage | Prices for a product across cities in the for-sale categories | Resellers and marketplace sellers |
| Local market research | Post counts and price ranges by category and city over time | Analysts and local businesses |
| Gig and job market signals | Job and gig post titles and locations by city | Labor market researchers and staffing teams |
| Alerts for new posts | New posts that match a query and price range, checked on a schedule | Buyers, renters and the apps built for them |

## Tips for scraping Craigslist

- **One subdomain per city.** `sfbay.craigslist.org`, `newyork.craigslist.org` and `chicago.craigslist.org` are separate sites. A national dataset is one search per city, sent as a Batch job.
- **Know the category codes.** `apa` apartments, `roo` rooms, `cta` cars and trucks, `sss` all for sale, `jjj` jobs, `ggg` gigs. Swap the code in `/search/CODE` to change category.
- **Put filters in the URL.** `query`, `min_price`, `max_price`, `postal` and `search_distance` are plain URL parameters, for example `/search/cta?query=toyota+tacoma&max_price=20000`.
- **Dedupe on the post ID.** The last part of each post URL is Craigslist's ID. Reposts often keep the same title with a new ID, so match on title and price as well when you count supply.
- **Clean the `0` prices.** Some posters leave the price at zero or write it only in the title. Filter or parse them before you compute averages.
- **Run searches on a schedule.** Craigslist posts expire and get deleted, so a daily pass that stores every post with its first-seen date is the only way to keep history.

## Frequently asked questions

### Is it legal to scrape Craigslist?

Collecting publicly available posts from Craigslist, such as titles, prices and locations shown to every visitor, is common practice for market research. Craigslist's terms of use forbid automated collection and the company has taken scrapers to court, so read the terms carefully and get legal advice before you build on it. Posts can contain personal data, which laws such as the CCPA and the GDPR protect, so do not collect contact details you have no legitimate reason to use.

### Is there an official Craigslist API?

No. Craigslist does not offer a public API for reading posts. Its bulk posting interface is for approved customers who publish ads, not for searching or downloading them. Teams that need Craigslist data request the public search pages with a Craigslist scraper API.

### What Craigslist pages can I scrape?

Search pages in every category, in every city subdomain, with any filters in the URL: housing, rooms, cars and trucks, for sale, jobs, gigs and services. Post pages open with the same request.

### Can I scrape Craigslist in several cities at once?

Yes. Each city is its own subdomain, so build one search URL per city and send the list as a Batch job. Your external_id on each task tells you which city each result came from.

### Can I scrape Craigslist cars and trucks?

Yes. Use the cta category ( /search/cta ) with a query for make and model and price filters in the URL. The search returns title, price, location and post URL; the post page holds attributes such as model year and odometer.

### How do I scrape Craigslist with Python?

Send a GET request to https://api.zenrows.com/v1/ with your API key, the Craigslist search URL, Adaptive Stealth Mode and Extract turned on, as in the Python example above. response.json()["parsed"]["listings"] is a list with one dictionary per post. The zenrows package on PyPI wraps the same request.

### How many Craigslist posts can I scrape?

Send one URL per request, or every city and category you track as a Batch job that runs them in parallel. One Batch job accepts more than 10,000 URLs, sent inline or as a CSV file.

### Can AI agents scrape Craigslist with Zenrows?

Yes. Connect the Zenrows MCP server to Claude, Cursor, Codex or any MCP client, or give your agent the Zenrows CLI. Ask in plain language, such as "used Tacomas under $20,000 near Sacramento", and the agent calls Fetch and Extract for you.

### Can I scrape Craigslist on a schedule?

Yes. Create a scheduled Batch job with your Craigslist URLs and the same settings as a single request, and Zenrows runs it on its side: every few hours, once a day, at set times on chosen days, or once at a future date. Each run keeps its own results, a webhook can notify your app when a run finishes, and the schedule can be paused or resumed at any time.

### Which integrations work with the Craigslist scraper?

Zenrows works with n8n, Make, Zapier, Clay, Dify, LangChain, LlamaIndex, CrewAI and Mastra, plus the Python, Node.js and Go SDKs and any HTTP client. A scheduled n8n or Zapier workflow that sends new posts to Slack or Google Sheets is the most common setup. See the integrations page for the full list.

### What format does the data come in?

JSON by default, with the structured record in parsed and the original page in html . You can also ask for the page as Markdown or plain text, which is what most LLM and RAG pipelines want.

All scrapers: https://www.zenrows.com/scrape. Start free: https://app.zenrows.com/register. Docs: https://docs.zenrows.com
