Craigslist Listings Scraper
Pricing
from $1.37 / 1,000 results
Craigslist Listings Scraper
Search public Craigslist listings by city, category, and optional keywords, then collect listing IDs, titles, prices, dates, coordinates, neighborhoods, images, and source URLs. Optional detail-page requests add available descriptions and attributes to the dataset.
Pricing
from $1.37 / 1,000 results
Rating
0.0
(0)
Developer
Axiom Works
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
6 days ago
Last modified
Categories
Share
Returns: one row per Craigslist listing with id, sourceUrl, title, price, postedAt, city, category, neighborhood, bedrooms, bathrooms, squareFeet.
Input: {"cities": ["sfbay"], "category": "apa", "searchQuery": "studio", "searchQueries": ["1 bedroom"], "maxItems": 50}
Best for: apartment and rental monitoring, used-goods price tracking, multi-keyword lead lists.
{"id": "7964382795", "title": "Vintage Cannondale Road Bike “Made in the U.S.A”", "price": null, "postedAt": "2026-10-15T03:37:43.000Z", "city": "sfbay", "category": "bicycles - by owner", "categoryAbbr": "bik", "neighborhood": "hayward / castro valley", "imageCount": 8}
Empty or invalid targets (unknown city, unknown category, no matches) end the run successfully with 0 results and a status message saying what to change.
Collect public Craigslist search listings from one or more cities into a structured dataset. Select a category, add search words if useful, and set an exact output limit. The Actor reads the public search result feed, decodes listing information, and can visit individual public listing pages for descriptions and attributes. Each result includes a stable posting ID and a public source URL. No Craigslist login is needed.
What does Craigslist Listings Scraper do?
Craigslist Listings Scraper searches the public Craigslist listings feed for each city you supply. It recognizes city hostnames such as sfbay, austin, and newyork, along with Craigslist search URLs. It resolves city and category identifiers using Craigslist's public reference catalogs, then requests search pages in date order. The Actor decodes the compact response into ordinary records suitable for spreadsheets, databases, alerts, and analysis.
The search can cover a broad category, such as all for sale, or a narrower category such as bicycles, cars, jobs, or apartments. searchQuery further narrows the result set. Price and age filters are applied to decoded listings before output. Results from several cities share one dataset, and posting IDs are deduplicated across the run. maxItems applies to the entire run, not separately to each city.
By default (includeDetails is true) the Actor also requests each public listing page to extract its description and displayed attributes. This increases requests and run time. Detail pages can disappear after the search response is generated; in that case the listing is skipped rather than emitted without a description. The Actor never follows reply or account flows and does not collect contact information.
What data can you get?
| Field | Meaning |
|---|---|
id | Stable Craigslist posting identifier. |
sourceUrl | Public listing URL built from the feed's slug and UUID. |
title | Listing headline. |
price | Numeric asking price, when present. |
priceText | Asking price as shown in the search feed. |
postedAt | Posting time in UTC ISO format. |
city | Craigslist city hostname used for the search. |
category | Category name from the reference catalog. |
categoryAbbr | Category abbreviation for that listing. |
latitude, longitude | Approximate feed coordinates, when supplied. |
neighborhood | Neighborhood or location description, when supplied. |
imageCount | Count of image identifiers in the feed. |
imageUrls | Image URLs derived from those identifiers. |
description | Public page description when details are enabled and available. |
attributes | Public page attribute labels and values when available. |
attributeFlags | Unlabelled page attributes, such as “cryptocurrency ok” or “delivery available”, copied verbatim when details are enabled; otherwise null. |
dealSignal | Optional AI judgment: underpriced, fair, overpriced, or unknown. |
matchedQuery | Search keyword that found the listing. |
bedrooms | Bedroom count from the listing page, null if not stated. |
bathrooms | Bathroom count from the listing page, null if not stated. |
squareFeet | Floor area in ft2 from the listing page, null if not stated. |
Values unavailable in a particular listing are returned as null or, for images, an empty array. Coordinates are approximate listing location data rather than a verified street address. A category requested for the search is a search scope: the output categoryAbbr names the actual category of each result, which can be narrower. The Actor preserves the public listing ID so repeated runs can be compared without relying on changing titles or prices.
Recent additions: searchQueries runs several keywords per city in one run, matchedQuery records the keyword that found each listing, and bedrooms, bathrooms and squareFeet are parsed from the listing page when includeDetails is true and the listing states them (otherwise null).
How to use
Enter one or more city hostnames in cities. Add a category abbreviation in category, such as sss for all for sale, bia for bicycles, cta for cars and trucks, apa for apartments, or jjj for jobs. Add searchQuery when you need particular words. Set maxItems to the exact maximum number of unique records you want. The default limit is 50.
Run the Actor and open its dataset. The overview table exposes the ID, title, price, posted time, city, category, neighborhood, image count, source URL, and AI signal. Export the dataset using Apify's normal dataset formats or retrieve it through the API. When includeDetails is false, descriptions and attributes are empty and runs are faster.
For monitoring, combine a specific city and category with postedWithinHours. Search results are sorted newest first unless you set sortBy. The Actor stops paging when it reaches older records. For research across cities, list several city hostnames and leave enough room in maxItems for all of them. The city order is respected, so a full first city may consume the limit before the next city is reached. Narrow the search or run cities separately if you need an equal sample from each one.
The Actor uses modest pacing between search pages and detail requests. It retries transient blocks with backoff and a fresh proxy session. Public listings can be edited or deleted at any time, so a source URL may cease to load after it was returned. For an invalid city, the Actor warns and continues with the remaining cities. A search with no matches completes with zero dataset items and a warning.
Input
| Input | Required | Default | Description |
|---|---|---|---|
cities | Yes | Prefill: sfbay | Array of city hostnames or Craigslist search URLs. |
category | No | sss | Search category abbreviation. |
searchQuery | No | Empty | Search words. |
searchQueries | No | Empty | Extra search words run in every city besides searchQuery; results are de-duplicated across queries and each record's matchedQuery shows which keyword found it. |
maxItems | No | 50 | Maximum unique output records across all cities. |
minPrice | No | Unset | Keep priced listings at or above this amount. |
maxPrice | No | Unset | Keep priced listings at or below this amount. |
postedWithinHours | No | Unset | Keep only recent posts and stop paging after older ones. |
sortBy | No | newest | newest, relevance, priceAsc or priceDesc. Use priceAsc or priceDesc with maxItems to get the cheapest or most expensive listings in each city. |
includeDetails | No | true | Request each public detail page for description and attributes. |
enrichWithAI | No | false | Adds an AI deal signal to each listing. Costs extra per enriched result. When false, dealSignal is null and no AI call is made. |
maxJevCalls | No | 500 | Upper limit on AI calls in the run. Only used when enrichWithAI is true. |
proxyConfiguration | No | Apify proxy | Proxy settings for the target requests. |
Example input:
{"cities": ["sfbay", "austin"],"category": "bia","searchQuery": "road bike","maxItems": 30,"minPrice": 100,"maxPrice": 2000,"includeDetails": true}
The price filter uses the numeric price provided by Craigslist. Listings without a numeric price are not excluded by that filter. Search terms and category filters are sent to the public search endpoint. The time and price filters are then checked by the Actor before it pushes a record. If a category abbreviation is invalid, the Actor fails with a clear error rather than silently switching categories.
The optional AI processor is TypeSafe Jev, a third-party service. When you set enrichWithAI to true, Jev judges whether the listing appears underpriced, fairly priced, overpriced, or unknown from its title, price, category, and available attributes. This is an estimate, not a verified market valuation. On an AI service error or rate limit, the listing is still returned and dealSignal stays empty. AI processing does not change the source fields.
Output
This example is an excerpt of a real item from a local prefill run. The listing is public and may later be removed or edited:
{"id": "7973855220","sourceUrl": "https://www.craigslist.org/view/d/menlo-park-raleigh-capri-30-road-bike/196JW6rPSXf8LEpYRAMKwa","title": "Raleigh Capri 3.0 road bike (105)","price": 400,"postedAt": "2026-10-01T20:19:06.000Z","city": "sfbay","category": "bicycles - by owner","categoryAbbr": "bik","latitude": 37.4379,"longitude": -122.181,"neighborhood": null,"imageCount": 10,"imageUrls": ["https://images.craigslist.org/01515_kF0wPOcCGOa_0CI0t2_600x450.jpg"],"description": null,"attributes": null,"dealSignal": null,"matchedQuery": "road bike","bedrooms": null,"bathrooms": null,"squareFeet": null}
The example shortens imageUrls for readability; the actual record contains all ten image URLs. postedAt is normalized to UTC. The Actor does not infer missing prices, addresses, or seller details. In the default mode, sourceUrl is built from data in the search response; detail page fetching is optional.
How much does it cost?
This Actor is charged per result. Each pushed listing is one result event. A listing with successful Jev enrichment also has one enriched-result event, and only when enrichWithAI is true; it is off by default. Review the Actor's Pricing tab for current terms before running a large collection. Set maxItems to control the maximum number of results, and use filters to narrow the run. The Actor does not charge a result event for skipped duplicates, filtered listings, empty searches, or errors.
Use with AI agents (MCP)
Add Apify's MCP server to an AI client with this server URL:
https://mcp.apify.com/?tools=axiomworks/craigslist-listings-scraper
The Actor's input and dataset schemas describe the available parameters and output fields to the client. Give the agent a city, a category, and a result limit so its request stays specific. Example prompts:
- Find recent public road bike listings in the
sfbayCraigslist city. Return title, price, posting time, and source URL for up to 20 listings. - Collect public car listings from
austinwith thectacategory and show ID, title, price, city, and source URL for up to 15 records. - Compare bicycle listing titles, prices, and posting times from
sfbayandaustinusing thebiacategory and a 30 item limit.
The AI client receives structured data from the Actor. It should treat dealSignal as an optional estimate and use the public source URL to check any listing that matters for a decision. A missing deal signal means enrichWithAI was off, the AI call was unavailable, or the call limit was reached.
Integrations and API
You can call the Actor from an Apify integration, a scheduled task, or your own program. The run's default dataset holds the listing records. Use an Apify token through your normal secret management process in API examples below. Replace the placeholder token with your own environment variable at execution time.
Python example:
from apify_client import ApifyClientimport osclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("axiomworks/craigslist-listings-scraper").call(run_input={"cities": ["sfbay"], "category": "bia", "searchQuery": "road bike", "maxItems": 20})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["id"], item["title"], item["sourceUrl"])
JavaScript example:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('axiomworks/craigslist-listings-scraper').call({cities: ['austin'], category: 'cta', maxItems: 20,});const { items } = await client.dataset(run.defaultDatasetId).listItems();items.forEach((item) => process.stdout.write(`${item.id} ${item.title}\n`));
cURL example:
curl -X POST 'https://api.apify.com/v2/acts/axiomworks~craigslist-listings-scraper/run-sync-get-dataset-items' \-H "Authorization: Bearer $APIFY_TOKEN" \-H 'Content-Type: application/json' \-d '{"cities":["sfbay"],"category":"bia","searchQuery":"road bike","maxItems":20}'
The synchronous endpoint is useful for smaller runs. For larger searches, start a run and fetch its dataset once the run finishes. Stable IDs let you merge later runs into a table without creating a new row for every repeated listing. Retain the source URL with each row so a user can inspect the public page directly.
Search URLs preserve the case of their query text and override the category input for that target. A malformed URL fails input validation. A temporary source failure finishes with zero results and a clear status message. The Actor checks the remaining result budget before requesting a detail batch, reducing unused page requests when a run reaches its spending limit.
FAQ
Does this require a Craigslist account? No. The search feed and optional listing pages used here are public.
Can I search several cities in one run? Yes. Pass an array of city hostnames. The Actor processes them in order and applies one shared maxItems limit.
Will maxItems: 7 return exactly seven records? It returns seven when at least seven matching, unique listings exist. It never pushes more than seven. If fewer exist, the dataset contains only the listings found.
Why is the description empty? Descriptions are empty only when includeDetails is set to false. Listings whose page cannot be read are skipped.
Why is a price empty? Some Craigslist posts have no numeric asking price. The Actor preserves that absence rather than estimating a value.
Does the AI signal prove a bargain or detect fraud? No. It is a third-party AI judgment based on the fields available to the Actor. Verify important claims on the public listing and through your usual due diligence.
Can the output include seller contact information? No. This Actor only collects public search and listing page fields and does not enter reply flows.
What happens when a city or search has no matches? The Actor finishes with zero records and logs a warning. An unknown city is skipped, so other valid cities can still return results.
Is it legal to scrape Craigslist?
Public page access and the legal rules for collecting or using data depend on your location, purpose, and the target site's terms. Craigslist's terms and robots guidance may restrict automated access. Review them and seek legal advice for your use case. Use the Actor at modest rates, avoid personal contact flows, and do not use the results to send unwanted messages. This Actor is not affiliated with, endorsed by, or sponsored by Craigslist. It is an independent tool that reads public pages only.
Feedback
If a field stops filling, a city no longer resolves, or the search response changes, report the input you used, the approximate run time, and the affected field. The public search endpoint uses a compact internal format that can change. A reproducible report helps distinguish a source format change from a removed listing or a narrow query. Avoid including account credentials or private details in feedback.