Craigslist Scraper - Cars, Housing, Jobs & For Sale avatar

Craigslist Scraper - Cars, Housing, Jobs & For Sale

Pricing

from $1.20 / 1,000 posting records

Go to Apify Store
Craigslist Scraper - Cars, Housing, Jobs & For Sale

Craigslist Scraper - Cars, Housing, Jobs & For Sale

Scrape Craigslist postings in any of 700+ cities worldwide: price, title, posting text, attributes, photos, coordinates, neighbourhood and posted date. Search by city, section, keyword and price, or paste search and posting links. Incremental monitoring, resume, MCP export.

Pricing

from $1.20 / 1,000 posting records

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

1

Bookmarked

6

Total users

5

Monthly active users

20 hours ago

Last modified

Share

Craigslist Scraper: Postings, Prices, Photos & Attributes

Craigslist Scraper turns craigslist.org into clean structured data across 700+ city sites in 79 countries. Get price, title, posting text, the section's own attribute list, photos, coordinates, neighbourhood and posted date for cars, housing, jobs, for sale, services and every other section. Search by city and section, or paste search and posting links. Watch a search across scheduled runs for new, updated and gone postings, resume an interrupted pull, and export to JSON, CSV or Excel, or pipe results into your apps through MCP connectors.

Why This Scraper?

  • A whole search in one read. Craigslist answers an entire search's result set in a single read, so a search with several thousand matches comes back complete and you are never charged twice for the same posting.
  • Coordinates, neighbourhood and posted time on every row, not only on postings read in full. You are not charged extra to know where a posting is or when it went up.
  • The section's real attributes, once you read a posting in full. Cars come back with make and model, year, odometer, title status, fuel, transmission, drivetrain, paint and body type. Housing comes back with bedrooms, bathrooms, move-in date, laundry, parking and pet rules. Jobs come back with compensation, employment type and job title.
  • Every city, not just the US. Name a city as sfbay, as SF bay area, or by pasting any link from it. London, Toronto, Berlin, Sydney, Tokyo and 700 more resolve the same way.
  • Change monitoring across scheduled runs. Classify every posting as NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED, with state kept separately per search so two monitoring campaigns never collide.
  • MCP connector export. Pipe a copy of every posting into Notion or another connector you already use, without changing the dataset output.
  • Runs that tell a block apart from an empty search. A run where every read was refused, or where the source rejects the city or section outright, fails with a message that says which; a search that legitimately matches nothing still succeeds.

Use Cases

  • Marketplace and resale price research: track asking prices on a make, model, category or neighbourhood over time to spot underpriced listings.
  • Housing search automation: watch new apartment or room listings in a metro area on a schedule and get only what is new since the last check.
  • Local lead generation: monitor gigs, services or job postings in a city and export the leads straight into Notion or another connector.
  • Sourcing across cities: run the same category search over several cities in one job to compare what is available and at what price.
  • Market and academic research: build a dataset of classified-ad volume, pricing and category mix for a city or region.

Data You Get

Sample shape: values are illustrative placeholders, not from a live posting.

FieldExample
kind"posting"
recordId / postingId"7900000000"
postingUuid"AbCdEfGhIjKlMnOpQrStUv"
url"https://www.craigslist.org/view/d/example-city-bicycle/AbCdEfGhIjKlMnOpQrStUv"
title"Example road bicycle, lightly used"
price / priceString / currency625 / "$625" / "USD"
categoryAbbr / categoryName"bik" / "bicycles - by owner"
postedDate"2026-09-04T00:00:00+00:00"
cityHost / cityName"sfbay" / "SF bay area"
subareaAbbr"eby"
subareaName"east bay" (detail reads only)
neighborhood / locationDescription"example neighbourhood" / "example town"
latitude / longitude37.7749 / -122.4194
imageUrls / imageCount["https://images.craigslist.org/00d0d_example_1200x900.jpg"] / 4
detailLoadedtrue (full read) or false (result row only)
description"Bought new in 2012. Lightly used, excellent condition." (detail reads only)
attributes / attributesByKey[{"key": "condition", "label": "condition", "value": "good", "type": "key_value"}] / {"condition": "good"} (detail reads only)
bodyHtml / rawDetailthe description's raw HTML, and the complete unmodified posting payload (detail reads only)
displayAddress"100 Example Street" (detail reads only, often blank)
updatedDate / createdDate"2026-09-05T00:00:00+00:00" (detail reads only)
hasContactInfotrue (detail reads only)
changeType / changedFields"NEW" / [] (incremental mode only)
firstSeenAt / lastSeenAt"2026-09-04T00:00:00+00:00" (incremental mode only)

A result row (detailLoaded: false) carries everything above the description line except subareaName and hasContactInfo, which only come from a full read. Reading a posting in full, either through the detail toggle or by pasting its own link, adds description, attributes, attributesByKey, bodyHtml, rawDetail, displayAddress, updatedDate, section, subareaName and hasContactInfo, and sets detailLoaded: true. A handful of convenience fields ride only on the compact search row and carry over when that row is later read in full: currency, bedrooms and sqft on housing, odometer on vehicles, salary, company and jobTitle on jobs, monthlyPaymentString on financed vehicles, and eventFirstDate, eventLastDate and openHouses where the section has them. A posting URL pasted directly in URL mode has no search row behind it, so it never carries those convenience fields at the top level, including currency; the same information is still present inside attributes and attributesByKey whenever the section reports it (for example attributesByKey.bedrooms or attributesByKey.auto_miles), and companyName is set directly from the posting payload on job postings that name a company. Contact data. The actor reports hasContactInfo so you know whether a poster left a way to be reached, and returns the posting text as written. It does not open the source's reply relay, so it never collects a personal email address or phone number from a contact route.

How to Use

  1. Pick a mode: search (cities, section and filters) or url (paste Craigslist links).
  2. For a city search, set Cities to one or more craigslist cities (sfbay, SF bay area, newyork) and Section to a code or name (sss for sale, hhh housing, jjj jobs, or a narrower code like cta cars+trucks or apa housing for rent). The source requires a section, so leaving it blank uses sss. Add a keyword, a price range, Posted by, Only postings with photos or Only postings from today as needed.
  3. For URL mode, paste any mix of search links (re-runs that exact search) and posting links (reads that one posting in full); the city-search filters above are ignored for a pasted link.
  4. Set Max items to bound the run, turn on Read the full posting text and attributes if you need the description and attribute list on search results too, then click Start. Download the dataset as JSON, CSV or Excel, or read it through the API.

Bicycles under $500 across two cities:

{
"mode": "search",
"cities": ["sfbay", "newyork"],
"category": "bia",
"query": "bicycle",
"maxPrice": 500,
"hasImage": true,
"maxItems": 20
}

A pasted posting link plus a pasted search link:

{
"mode": "url",
"startUrls": [
"https://www.craigslist.org/view/d/example-city-bicycle/AbCdEfGhIjKlMnOpQrStUv",
"https://sfbay.craigslist.org/search/apa?max_price=3000"
],
"maxItems": 30
}

Recurring monitoring of an apartment search, with expirations:

{
"mode": "search",
"cities": ["sfbay"],
"category": "apa",
"incrementalMode": true,
"emitExpired": true,
"stateKey": "sfbay-apartments",
"maxItems": 0
}

Full posting text and attributes on a jobs search:

{
"mode": "search",
"cities": ["newyork"],
"category": "jjj",
"fetchDetails": true,
"maxItems": 10
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/craigslist-classifieds-scraper").call(run_input={
"mode": "search", "cities": ["sfbay"], "category": "sss", "query": "bicycle"
})
for posting in client.dataset(run["defaultDatasetId"]).iterate_items():
print(posting["title"], posting["priceString"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/craigslist-classifieds-scraper').call({
mode: 'search', cities: ['sfbay'], category: 'sss', query: 'bicycle',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Naming a city or section

A city resolves by its site name (sfbay, newyork, london), by its display name (SF bay area, new york, vancouver, BC) or by pasting any link from it; matching ignores case and punctuation. 700+ cities across 79 countries are shipped; an unrecognised city is skipped and logged, and the run fails with the list if none resolve. A section works the same way: the eight top sections are sss (for sale), hhh (housing), jjj (jobs), ggg (gigs), bbb (services), ccc (community), eee (events) and rrr (resumes), and narrower codes such as cta (cars+trucks) or apa (apartments / housing for rent) work too. Sub-area narrowing (for example, east bay inside SF bay area) is accepted by the source and then ignored, so setting it would silently do nothing; sub-area is still reported on every record, so filter it yourself downstream. Setting a price bound drops postings that carry no price, because the source filters them out server side.

Resume and recurring monitoring

Resume (resumeFromRunId) continues one interrupted run: paste its run or dataset ID and the actor skips every posting already collected there, so you don't pay twice. Incremental mode (incrementalMode) is for running the same search again and again, for example daily: the first run returns every match as NEW, and later runs return only NEW, UPDATED and REAPPEARED postings, each carrying changeType, changedFields, firstSeenAt and lastSeenAt. UNCHANGED postings are suppressed and not billed unless Emit unchanged is on. Emit expired only marks a posting EXPIRED after a run that scanned the whole tracked search: not resumed, not stopped by Max items, and with no search or posting read left unfinished. REAPPEARED can only follow a prior EXPIRED: it fires when a posting that was tombstoned gone in an earlier complete scan turns up again, so it never fires on a first sighting and never fires "by default" just because incremental mode is on. stateKey names or shares one monitoring campaign; leave it empty and the actor derives a key from the search and filter settings, so two differently configured searches never share a baseline by accident.

Send results into your apps (MCP connectors)

Optionally pipe the scraped postings into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.

What gets written to the connector: a condensed, human-readable summary of each posting, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
  3. For Notion, also set notionParentPageUrl to the page where postings should be created.
  4. Use maxNotifyListings to cap how many postings are written to each connector per run.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
modestringsearchsearch (cities + section + filters) or url (paste craigslist links).
citiesarray["sfbay"]City search: one or more craigslist cities, as a site name (sfbay), a display name (SF bay area) or any link from that city.
categorystringsssCity search: the section to read, by code or by name. The source requires a section, so this is never empty.
querystringno default (prefill: bicycle)City search: keyword to match in the posting. Leave empty to browse the whole section.
minPriceintegerno defaultCity search: lowest price to return, in the city's own currency.
maxPriceintegerno defaultCity search: highest price to return, in the city's own currency. Setting either bound drops postings that carry no price.
soldBystringallCity search: all, owner or dealer.
hasImagebooleanfalseCity search: keep only postings that carry at least one photo.
postedTodaybooleanfalseCity search: keep only postings published today.
sortBystringrelCity search: rel, date, dateoldest, priceasc or pricedsc.
startUrlsarrayno default (prefill: a sample search link)URL mode: craigslist search links or posting links, in either the per-city or the shared form.
urlsarrayno defaultAlias for startUrls, accepted under the name other Apify actors use for URL mode.
maxItemsinteger20Stop after this many records (0 = no limit). With several cities, each gets an equal share and the last one takes the remainder.
fetchDetailsbooleanfalseAlso read every posting in full: the posting text, the attribute list, the display address and the update time. Adds one Posting detail event per posting read. Posting links pasted in URL mode are always read in full and always charge this event, regardless of this setting.
resumeFromRunIdstringno defaultContinue one interrupted run: paste its run ID or dataset ID and postings already collected there are skipped. For recurring monitoring use incrementalMode instead.
incrementalModebooleanfalseTurn on for daily or recurring monitoring of the same search: the first run returns everything as NEW, later runs return only NEW, UPDATED and REAPPEARED.
stateKeystringno defaultOptional, incremental mode only. Name a monitoring campaign to keep its state stable, or deliberately share it. Leave empty to derive the key from the search and filter settings.
emitUnchangedbooleanfalseIncremental mode only. Also return postings unchanged since the last run, marked UNCHANGED (those rows are billed too).
emitExpiredbooleanfalseIncremental mode only. Also return tracked postings no longer found, marked EXPIRED. Produced only after a complete, uncapped, non-resumed scan.
mcpConnectorsarrayno defaultOptional MCP connectors to pipe a copy of each posting into. Authorize one under Apify, Settings, API & Integrations, then pick it here.
notionParentPageUrlstringno defaultNotion only: URL or id of the page under which posting pages are created. Required to enable the Notion export; ignored by other connectors.
maxNotifyListingsinteger50Cap on postings written to each connector per run. Does not affect the dataset.
proxyConfigurationobjectno default (prefill: Apify Proxy)Apify Proxy settings for the connection this actor reads through.

Output Example

Sample shape: values are illustrative placeholders, not from a live posting.

{
"kind": "posting",
"recordId": "7900000000",
"postingId": "7900000000",
"postingUuid": "AbCdEfGhIjKlMnOpQrStUv",
"title": "Example road bicycle, lightly used",
"url": "https://www.craigslist.org/view/d/example-city-bicycle/AbCdEfGhIjKlMnOpQrStUv",
"price": 625,
"priceString": "$625",
"currency": "USD",
"categoryId": 68,
"categoryAbbr": "bik",
"categoryName": "bicycles - by owner",
"section": "for sale",
"postedDate": "2026-09-04T00:00:00+00:00",
"updatedDate": "2026-09-05T00:00:00+00:00",
"createdDate": "2026-09-04T00:00:00+00:00",
"cityHost": "sfbay",
"cityName": "SF bay area",
"areaId": 1,
"subareaAbbr": "eby",
"subareaName": "east bay",
"neighborhood": "example neighbourhood",
"locationDescription": "example town",
"displayAddress": "100 Example Street",
"latitude": 37.7749,
"longitude": -122.4194,
"imageUrls": ["https://images.craigslist.org/00d0d_example_1200x900.jpg"],
"imageCount": 1,
"detailLoaded": true,
"description": "Bought new in 2012. Lightly used, excellent condition.",
"bodyHtml": "Bought new in 2012.<br>Lightly used, excellent condition.",
"attributes": [{"key": "condition", "label": "condition", "value": "good", "type": "key_value"}],
"attributesByKey": {"condition": "good"},
"rawDetail": {},
"hasContactInfo": true,
"notices": null,
"repostOf": null,
"seeMyOther": null,
"changeType": "NEW",
"changedFields": [],
"firstSeenAt": "2026-09-04T00:00:00+00:00",
"lastSeenAt": "2026-09-04T00:00:00+00:00"
}

Plan Requirement

The default Apify Proxy setting works out of the box, and the actor rotates to a fresh connection automatically when one is refused. The Pricing tab shows the current per-record and per-detail-read rates for your plan tier.

FAQ

How much does it cost?

You pay per posting returned, plus one Posting detail surcharge for each posting read in full (a pasted posting link, or the detail toggle switched on). The Pricing tab shows the current rates. Use Max items to cap the cost of any run.

This actor collects only publicly available postings. You are responsible for how you use the data: follow Craigslist's terms and the laws that apply to you, and get legal advice before any commercial redistribution. It reads public pages only: it does not post, reply, flag or log in.

Can I get only new or changed postings on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental changes for scheduled runs. Later runs return only NEW, UPDATED and REAPPEARED postings; UNCHANGED ones are suppressed and not billed unless you turn on Emit unchanged.

Why does Section never come back empty?

The source refuses a search that names no section, so leaving Section blank does not mean "everything": the actor fills in sss (for sale) for you. Narrow it explicitly with a code such as cta, apa or jjj, or with a section display name.

Why did my run fail instead of returning an empty dataset?

If every read was refused, or the city or section named is one the source does not accept, the run fails with a message that says which. A search that read successfully but matched nothing still succeeds, with a status note saying so, so "no postings today" is never confused with "nothing could be read".

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push postings into Notion or another connector you've authorized.

🔗 Want more classifieds data?

Pair this actor with these related scrapers from the same team:

🚗 EasyAuto123 Scraper
Scrape EasyAuto123 vehicle listings into clean structured data. Extract prices, VINs...
🚗 Marktplaats.nl Scraper
From $1/1K. Scrape Marktplaats.nl listings by keyword or URL. Extract title, price...
🚗 AutoTrader UK Scraper
Pull structured vehicle listings from autotrader.co.uk at scale. Search by filters or use...
🏷️ 2dehands & 2ememain
Scrape classifieds from 2dehands.be and 2ememain.be by keyword, filters, or URLs. Returns...
🏷️ OLX Global
From $1/1K. Fast OLX scraper across 9 countries, including Poland, Romania, Portugal...
🚗 Autotrader US
Scrape US car listings from Autotrader by make, model, ZIP, price, year, body style...

👉 Browse all abotapi scrapers

💬 Support & custom scrapers

  • 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • 🛠️ Need another site, extra fields or a private build? Email contact@abotapi.com or message Telegram @abotapi.
  • ⭐ Enjoying it? A quick review on the actor page helps other users find it.