Classified Listings avatar

Classified Listings

Pricing

from $8.00 / 1,000 results

Go to Apify Store
Classified Listings

Classified Listings

Search and track listings across Facebook Marketplace, OLX, Wallapop, Leboncoin and Custojusto through one unified API — one request shape, five sources, normalized results, plus price alerts that notify you when something new matches.

Pricing

from $8.00 / 1,000 results

Rating

0.0

(0)

Developer

Pinto Studio

Pinto Studio

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 hours ago

Last modified

Categories

Share

Classifieds Listings Search Actor

The Classifieds Listings Search Actor is a web scraping tool deployed on the Apify platform that searches and extracts listing data from Facebook Marketplace, OLX, Wallapop, Leboncoin and Custojusto through one unified input, normalizing every result into the same listing shape regardless of source.

Overview

None of these five sites publish an official public API, so every integration here is a reverse-engineered one — real endpoints and page structures discovered and verified live, not guesses. The Actor supports four operations: searching one or many sources at once, fetching a single listing's full detail, calling a source's own unique endpoint (category trees, etc.), and a schedulable "alert check" that reports only genuinely new listings since the last run.

Features

  • Five sources, one shape: every listing comes back with the same fields, whichever site it's from
  • Multi-source search: query several sources in one run, merged and sorted together
  • Price filtering & sorting: minPrice/maxPrice, and relevance/price_asc/price_desc/date
  • Listing detail: fetch one listing's full description, images and seller info
  • Per-source extras: category trees (Leboncoin, Custojusto), a richer raw listing record (OLX), a dedicated vehicles search (Wallapop)
  • Price alerts: a schedulable operation that only reports listings it hasn't seen before, with optional webhook delivery when new matches show up
  • Resilient by design: a source failing never fails the whole run — partial results are normal

Input Configuration

The operation field decides which other fields matter. All operations accept the proxy fields at the bottom (strongly recommended — see Rate Limits and Best Practices).

operation: "search" (default)

ParameterTypeDefaultDescription
sourcesarrayall 5Which sites to search: facebook, olx, wallapop, leboncoin, custojusto
querystring—Search keywords
categorystring—Source-specific category id/slug
locationstring—Source-specific location (see Parameter Options)
minPrice / maxPriceinteger—Price range, in the source's own currency
sortBystringrelevancerelevance | price_asc | price_desc | date
pageinteger1Page number (has no effect on Facebook)
limitinteger30Max listings per source, 1-100
cursorstring—Wallapop-only opaque pagination token (see below)
compareBySourcebooleanfalseInclude min/max/avg price stats grouped by source

operation: "listingDetail"

ParameterTypeDescription
sourcestringOne of the 5 source ids
listingIdstringFor Wallapop/Facebook: the plain id. For OLX/Custojusto/Leboncoin: the listing's full url from a search result instead — these sites need the slug, not just the numeric id

operation: "sourceExtra"

ParameterTypeDescription
sourcestringOne of the 5 source ids
extraNamestringcategories (Leboncoin, Custojusto) | rawState (OLX) | cars (Wallapop)
extraParamsobjectParameters for that extra, e.g. {"id": "<olx-listing-url>"}

operation: "alertCheck"

ParameterTypeDefaultDescription
sources, query, category, location, minPrice, maxPrice, sortBy——Same as search
webhookUrlstring—If new matches are found, they're POSTed here as { query, sources, matches }
alertStateKeystringdefaultKey this saved search's "seen listing ids" are kept under — use a different key per saved search

Proxy configuration (all operations)

ParameterDescription
baseProxyUrlA generic HTTP(S) proxy URL, e.g. http://user:pass@host:port
rotatingResidentialProxyUrlA residential proxy that rotates IP per connection
spainResidentialProxyUrlA residential proxy pinned to Spain (Wallapop)
portugalResidentialProxyUrlA residential proxy pinned to Portugal (OLX, Custojusto)
impitProxyMethodUrlA service that fetches a URL with a real browser TLS fingerprint — needed for reliable Leboncoin/Wallapop access
wallapopAppVersion, wallapopDeviceId, wallapopDeviceOsAdvanced overrides for Wallapop's required headers
facebookLocationIdAdvanced override for the default Facebook location scope
requestBudgetMsAdvanced override for the per-request time budget

Parameter Options

Sources (sources / source)

  • facebook — Facebook Marketplace (international)
  • olx — OLX (Portugal)
  • wallapop — Wallapop (Spain)
  • leboncoin — Leboncoin (France)
  • custojusto — Custojusto (Portugal)

Location (location)

Format is source-specific:

  • OLX / Custojusto: a city name
  • Leboncoin: a zip code or city
  • Wallapop / Facebook: "lat,lon" (e.g. "40.4168,-3.7038"); Facebook also accepts a raw Facebook-internal location id

Sort order (sortBy)

  • relevance — default
  • price_asc — lowest price first
  • price_desc — highest price first
  • date — newest first

Usage Examples

Basic search across all sources

{
"query": "iphone 13"
}

Search two sources with price filtering

{
"operation": "search",
"sources": ["olx", "custojusto"],
"query": "iphone 13",
"minPrice": 100,
"maxPrice": 400,
"sortBy": "price_asc",
"limit": 20
}

Compare prices across sources

{
"operation": "search",
"query": "playstation 5",
"compareBySource": true
}

Get one listing's full detail

{
"operation": "listingDetail",
"source": "wallapop",
"listingId": "wzvg1qglddzl"
}

Call a source's unique endpoint

{
"operation": "sourceExtra",
"source": "wallapop",
"extraName": "cars",
"extraParams": { "brand": "Renault", "minYear": 2018 }
}

Set up a price alert (run this on a schedule)

{
"operation": "alertCheck",
"sources": ["olx", "custojusto"],
"query": "iphone 13",
"maxPrice": 400,
"alertStateKey": "my-iphone-alert",
"webhookUrl": "https://your-server.example.com/hooks/new-listing"
}

Output Format

{
"operation": "search",
"query": { "query": "iphone 13", "minPrice": 100, "maxPrice": 400, "page": 1, "limit": 20, "sortBy": "price_asc" },
"sources": {
"olx": { "ok": true, "count": 20, "hasMore": true },
"custojusto": { "ok": true, "count": 18, "hasMore": true }
},
"errors": [],
"count": 38,
"listings": [
{
"id": "custojusto:45469559",
"sourceId": "45469559",
"source": "custojusto",
"title": "iPhone 14 Midnight 128GB com Caixa e Pin",
"description": "iPhone 14 Midnight (Meia-noite)...",
"price": 300,
"currency": "EUR",
"url": "https://www.custojusto.pt/...",
"images": ["https://prod-images.custojusto.pt/..."],
"location": { "district": "Braga", "county": "Esposende" },
"postedAt": "2026-10-27T13:05:33Z",
"category": "Telefones e Acessórios",
"seller": { "name": "filipa07silva", "isCompany": false },
"attributes": {}
}
]
}

location, category, seller and attributes vary in detail by source — every other field is always present, null when the source didn't provide it. A source failing (e.g. a transient anti-bot block) shows up under sources.<id>.error/errors rather than failing the whole run — 200/success with partial results is the normal, by-design outcome.

listingDetail

{ "operation": "listingDetail", "source": "wallapop", "listingId": "wzvg1qglddzl", "listing": { "...": "a listing object" } }

sourceExtra

{ "operation": "sourceExtra", "source": "leboncoin", "extraName": "categories", "data": [ "...": "source-specific" ] }

alertCheck

{
"operation": "alertCheck",
"query": { "...": "echoed params" },
"sources": ["olx", "custojusto"],
"errors": [],
"newMatchCount": 2,
"newMatches": [ "...": "listing objects" ]
}

Data Storage

  • Dataset: the full result of the run (as shown above) is pushed once per run.
  • Key-value store: the same result is also saved under OUTPUT for quick viewing in the Apify console. For alertCheck specifically, "seen listing ids" are kept in a separate, persistent named store (CL-LISTINGS-ALERTS) keyed by alertStateKey, so a later scheduled run only reports what's genuinely new — this is not the same as the run's own default key-value store, which is fresh every run.

Error Handling

  • A single source failing doesn't fail the run — check errors/sources.<id>.error in the output.
  • A hard input error (unknown source, missing required field for the chosen operation) fails the run with a descriptive message, visible in the run log and as the run's status message.
  • listingDetail and sourceExtra return a clear error if the listing/extra doesn't exist, rather than an empty success.

Performance Tips

  1. Narrow sources to what you need — searching all 5 is slower and more likely to hit a transient per-source failure than querying one or two.
  2. Use limit to cap how much each source returns per run.
  3. For Wallapop pagination beyond the first page, use the cursor a previous run's output gave you (nextCursor) instead of incrementing page — it's a single request instead of walking forward one request per page.
  4. For alertCheck, give each saved search its own alertStateKey so they don't share seen-listing state.

Rate Limits and Best Practices

  • Proxies matter more than for most scrapers here. Leboncoin and Wallapop sit behind active anti-bot protection; without a configured proxy (and for Leboncoin specifically, the impitProxyMethodUrl TLS-fingerprint fallback), requests to those two sources will frequently fail. OLX, Custojusto and Facebook work without any proxy configured, but a proxy still helps reliability under sustained use.
  • Expect occasional transient 500/failed-source entries from Leboncoin/Wallapop even with good proxies configured — these sources are actively protected and not every request gets through.
  • Don't run alertCheck more often than every few minutes per saved search — these sites don't publish new listings fast enough to need it, and it increases the chance of hitting anti-bot protection.
  • Facebook Marketplace only ever returns its first page (roughly 24-40 listings) — there is no anonymous way to page further, by design of this Actor (deeper pagination there requires a logged-in session, which this Actor doesn't use).

Integration Examples

JavaScript/Node.js

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const input = {
operation: 'search',
sources: ['olx', 'custojusto'],
query: 'iphone 13',
maxPrice: 400,
};
const run = await client.actor('YOUR_ACTOR_ID').call(input);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((run) => {
run.listings.forEach((listing) => {
console.log(`${listing.title} — ${listing.price} ${listing.currency} (${listing.source})`);
});
});

Python

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run_input = {
"operation": "search",
"sources": ["olx", "custojusto"],
"query": "iphone 13",
"maxPrice": 400,
}
run = client.actor("YOUR_ACTOR_ID").call(run_input=run_input)
items = client.dataset(run["defaultDatasetId"]).list_items().items
for item in items:
for listing in item["listings"]:
print(f"{listing['title']} — {listing['price']} {listing['currency']} ({listing['source']})")

Support

If you have any questions or encounter any issues, please consult the Apify documentation or reach out to us through one of the following channels: