Craigslist Multi-City Listings Scraper
Pricing
Pay per event
Craigslist Multi-City Listings Scraper
Scrape Craigslist listings across many US metro areas and categories in one run. Get titles, prices, locations, images, posted/updated dates, structured attributes, descriptions, and public reply links -- normalized into one clean dataset instead of one run per city.
Pricing
Pay per event
Rating
0.0
(0)
Developer
DevilScrapes
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
🎯 What this scrapes
Craigslist's own search pages (/search/area/<areaSlug>) and listing-detail pages (/view/d/<slug>/<postId>) -- fanned out across every metro area and category you pick, in one Actor call instead of one run per city. Point it at a single area, or union in a curated top-US-metro preset, pick one or all seven top-level categories, and get back a single normalized dataset: title, price, location, coordinates, images, posted and updated timestamps, structured attributes, description, and the public reply-flow link. Every request runs through our full anti-blocking stack by default -- rotating browser-fingerprint impersonation, session-rotated proxying, retries with backoff -- the same defenses every Devil Scrapes Actor ships, so your dataset holds up even if Craigslist changes its defenses tomorrow.
🔥 What we handle for you
- 🛡️ We rotate browser fingerprints on every request --
curl-cffiimpersonates real Chrome, Firefox, and Safari TLS/HTTP handshakes, so nothing about the traffic looks like a bare Python script, today or if Craigslist tightens up tomorrow. - 🌐 We rotate Apify Proxy sessions -- a fresh session ID and exit IP on any request that comes back looking blocked, no manual restart needed.
- 🔁 We retry with exponential backoff on
408 / 429 / 5xx, honouringRetry-After-- up to 5 attempts before we give up on a page. - 🧱 We pace to the target's rate limits and isolate faults per combo -- one slow metro or blocked category logs a warning and gets skipped; the rest of the run keeps going, and partial progress is always reported via a clear status message, never a silent empty dataset.
- 🧊 Clean, typed dataset rows -- every row is
ResultRow-validated (Pydantic), ISO-8601 timestamps, stable post IDs, ready to export as JSON, CSV, or Excel. - 💰 Pay-per-result pricing -- a small one-off
actor-startcharge, then you pay only for rows that land in your dataset.
💡 Use cases
- Reseller and arbitrage price-scanning across dozens of metros in a single run, instead of one Actor call per city.
- Multi-city lead-generation list building for local-services and real-estate outreach.
- Rental and vehicle market intelligence -- price and inventory tracked across metros side by side.
- Apartment and job aggregation, normalized into one schema instead of stitching together per-city exports.
- Posting-velocity research -- track price drops and reposts via
posted_at,updated_at, andis_edited_or_price_drop.
⚙️ How to use it
- Click Try for free at the top of the page.
- Fill in the input form — most fields have sensible defaults.
- Click Start. Output streams into the run's dataset.
- Export from Storage → Dataset as JSON, CSV, or Excel — or fetch via the API.
📥 Input
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
areas | array | no | [] | Craigslist area slugs to search (e.g. "newyork", "losangeles") from /about/sites. Combined with All top US metros below… |
allTopUsMetros | boolean | no | False | Union in a curated list of up to 25 top-US-metro Craigslist area slugs (not the full ~700-area directory) on top of… |
categories | array | no | ['sss'] | Top-level Craigslist category codes to search: sss (for sale), cta (cars & trucks), reo (real estate), apa… |
query | string | no | 'iphone' | Single search term, passed verbatim to Craigslist's search. Leave empty to browse a category with no keyword filter. |
maxResultsPerCombo | integer | no | 100 | Cap on listings collected per (area, category) combo. 300 is Craigslist's observed single-page result-set ceiling. |
fetchDetails | boolean | no | True | When enabled, fetch each listing's detail page for posted/updated dates, full description, structured attributes, and… |
postedWithinDays | integer | no | '—' | Drop listings older than this many days. Only enforceable when Fetch listing detail pages is enabled -- posting date is… |
maxRunMinutes | integer | no | 10 | Run-wide wall-clock budget. New (area, category) combos stop being started once this is exceeded; already-collected… |
proxyConfiguration | object | no | {'useApifyProxy': True} | Apify Proxy configuration. Defaults to the standard group; we layer session rotation and fingerprint impersonation on… |
Example input
{"areas": ["newyork"],"allTopUsMetros": false,"categories": ["sss"],"query": "iphone","maxResultsPerCombo": 3,"fetchDetails": true,"postedWithinDays": null,"maxRunMinutes": 5,"proxyConfiguration": {"useApifyProxy": true}}
📤 Output
Every row is one dataset item.
| Field | Type | Notes |
|---|---|---|
area_slug | string | Input area slug for this combo (e.g. 'newyork'). |
category_code | string | Input top-level category code for this combo (e.g. 'sss'). |
query | string | Input search query, verbatim; null in category-browse mode. |
post_id | string | Craigslist posting ID, parsed from the detail-URL path. |
title | string | Listing title, from the static search-result list. |
price | number | Listing price; null for priceless categories (jobs/services). |
currency | string | ISO currency code, from the joined JSON-LD offer when a price is present. |
location_text | string | Free-text location, from the static search-result list. |
latitude | number | Listing latitude, from the joined JSON-LD geo block. |
longitude | number | Listing longitude, from the joined JSON-LD geo block. |
images | array | Image URLs, from the joined JSON-LD block; empty array when none matched. |
detail_url | string | Canonical https://www.craigslist.org/view/d/ |
posted_at | string | ISO-8601 (with offset) posted timestamp; null when not fetched or unavailable. |
updated_at | string | ISO-8601 (with offset) updated timestamp, when present on the detail page. |
is_edited_or_price_drop | boolean | Best-effort: true when updated_at differs from posted_at; null when undetermined. |
description | string | Full listing description text, from the detail page's #postingbody. |
attributes | object | Structured item attributes from the detail page's .attrgroup; shape varies per category. |
reply_url | string | Public reply-flow URL as it appears on the detail page; never fetched or followed. |
repost_fingerprint | string | Best-effort hash of normalized title + area + category; not a guaranteed dedup key. |
scraped_at | string | ISO-8601 wall-clock time of row construction. |
Example output
{"area_slug": "newyork","category_code": "sss","query": "iphone","post_id": "7812345678","title": "iPhone 14 Pro 256GB Unlocked","price": 550.0,"currency": "USD","location_text": "Queens","latitude": 40.7282,"longitude": -73.7949,"images": ["https://images.craigslist.org/00A0A_abc_600x450.jpg"],"detail_url": "https://www.craigslist.org/view/d/queens/7812345678","posted_at": "2026-08-05T09:12:00-0400","updated_at": "2026-08-09T14:03:00-0400","is_edited_or_price_drop": true,"description": "Barely used, comes with box and charger...","attributes": {"condition": "like new","make": "apple"},"reply_url": "https://newyork.craigslist.org/reply/que/sss/7812345678/abc123","repost_fingerprint": "a1b2c3d4","scraped_at": "2026-08-12T12:00:00.000Z"}
💰 Pricing
Pay-Per-Event — you pay only when these events fire:
| Event | USD | What it is |
|---|---|---|
actor-start | $0.01 | One-off warm-up charge per run |
result | $0.002 | Per dataset item |
Example: 1 000 results at the rates above ≈ $2.01. No subscription, no minimum, no card to start — Apify gives every new account $5 of free credit.
🚧 Limitations
- Each
(area, category)combo returns up tomaxResultsPerCombo(max 300) -- Craigslist's observed single-page ceiling. There's no pagination past that in v1. - Total combos (
areas x categories, post-resolution) are capped at 150 per run, a FREE-tier compute guardrail -- split very large fan-outs across multiple runs. allTopUsMetrosunions in a curated list of up to 25 metro slugs, not the full ~700-area Craigslist directory.- One
queryterm per run -- no multi-keyword search in a single call. reply_urlis captured exactly as Craigslist renders it on the listing page. We never open it, solve any verification step it triggers, or de-obfuscate a masked email -- that boundary is deliberate, and Craigslist's ownrobots.txtdisallows the reply flow.repost_fingerprintis a best-effort hash (normalized title + area + category), not a guaranteed unique dedup key.postedWithinDaysonly filters whenfetchDetailsis enabled -- posting date lives on the detail page only.
❓ FAQ
Can I get a seller's phone number or email from this?
No. We surface reply_url exactly as Craigslist places it on the listing page, but we never open it, solve any verification step it triggers, or de-obfuscate a masked email. That's a deliberate scope boundary, not a technical shortfall -- Craigslist's own robots.txt disallows the reply flow, and we respect it.
Why one run instead of one run per city?
Every incumbent we benchmarked (memo23, automation-lab, solidcode, ivanvs) makes you run their Actor once per metro, then stitch the exports together yourself. We fan out across up to 20 explicit areas plus an optional curated top-US-metro preset, and every category you pick, in one call, into one normalized dataset.
What happens if one city or category is unreachable mid-run?
We isolate failures per (area, category) combo -- a dead metro or a blocked category logs a warning and gets skipped, the rest of the run keeps going, and you get a clear status message on what finished. We never fail the whole run over one bad combo, and we never hand back a silent empty dataset.
Do I need Residential proxy?
Not by default -- we route every request through the standard Apify Proxy group with session rotation and browser-fingerprint impersonation layered on top. Switch proxyConfiguration to Residential anytime you want that extra layer for larger or more aggressive runs.
How fresh are posted_at and updated_at?
Both are read live from the listing's own detail page during your run, when fetchDetails is on. Turn fetchDetails off to skip the detail-page fetch entirely and get search-list fields only, faster and cheaper.
Is this legal to run?
We only fetch what Craigslist serves on its public search and listing pages. We never touch the reply flow, never log in, and never scrape any authenticated seller feature. Match your own use case against Craigslist's terms before using the data commercially.
💬 Your feedback
Spotted a bug, hit a weird edge case, or need a new field? Open an issue on the Actor's Issues tab on Apify Console — we ship fixes weekly and we read every report.