# OLX Scraper (`s-r/olx-scraper`) Actor

- **URL**: https://apify.com/s-r/olx-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## OLX Scraper

Search OLX and get the ads back as rows: title, asking price as a number, the seller's town, whether the item is new or used, and whether the price is negotiable. Covers **Poland, Romania, Bulgaria, Ukraine and Portugal** from one actor.

No login, no cookie, no API key. It reads the ordinary results page from an exit inside the market's own country.

### What you get

- **Five markets, one actor.** Each OLX country runs its own domain *and its own search path*: `/oferty/` in Poland, `/oferte/` in Romania, `/ads/` in Bulgaria and Portugal, `/uk/list/` in Ukraine. Pick a market and the right shape is used
- **Currency read off the price, not assumed.** Bulgaria has moved to the euro, so `olx.bg` now prints `139.99 €`. Anything with BGN hard-coded is mislabelling every Bulgarian row today
- **Prices parsed per row, not per locale.** Portugal writes `1.400 €` meaning fourteen hundred; Bulgaria writes `139.99 €` meaning a hundred and forty. Same character, opposite meanings, same website. Both come out right
- **`price_type` instead of a bare null.** A free ad, a swap-only ad and an ad with no stated price are three different things, and none of them is a parse failure
- **`negotiable` as its own flag**, because "6 800 zł do negocjacji" is an opening position rather than a price
- **Condition in the market's own wording** where the seller set it, read from the card rather than translated
- **Ad id, town and posting date** on every row, so consecutive runs diff cleanly

### Why OLX

OLX is the classifieds market across Central and Eastern Europe and Portugal. In Poland it is where used goods are actually bought and sold, the way Marktplaats is in the Netherlands or Kleinanzeigen is in Germany. For second-hand pricing in those countries there is no meaningful second source.

Because it is one platform under many domains, the same query can be run across five countries and compared directly. That is the part most tooling misses: the country-scoped actors on the Store cover one market each, so a cross-border comparison means running several and reconciling their formats yourself. The formats are the hard part, and they are not consistent even within OLX.

### Input

| Field | Type | Required | Default | What it does |
|---|---|---|---|---|
| `query` | string | one of the two | `rower` | What to search for, in the market's language |
| `market` | select | no | `pl` | `pl`, `ro`, `bg`, `ua` or `pt` |
| `url` | string | one of the two | – | An OLX search or category URL. Takes precedence |
| `price_min` | integer | no | – | Lower bound in the market's currency |
| `price_max` | integer | no | – | Upper bound in the market's currency |
| `limit` | integer | no | `50` | Ads to return, 1 to 1000. A page carries about 50 |
| `retries` | integer | no | `3` | Retry attempts per page |

### Output

```json
{
  "position": 1,
  "ad_id": "1023770665",
  "url": "https://www.olx.pl/d/oferta/rower-elektryczny-mtb-gorski-storm-...",
  "title": "Rower Elektryczny MTB Górski Storm E-Stella 3.0 Bafang M410 720WH",
  "price": 6800.0,
  "price_text": "6 800 zł do negocjacji",
  "price_type": "asking price",
  "negotiable": true,
  "currency": "PLN",
  "location": "Bełchatów",
  "posted_label": "26 sierpnia 2026",
  "condition": "Nowe",
  "is_promoted": false,
  "image": "https://ireland.apollo.olxcdn.com/...",
  "market": "pl",
  "query": "rower"
}
```

### Use cases

**Second-hand pricing in a country you do not live in.** Run your product term, filter to `price_type: asking price`, and take the median. `condition` lets you separate the new-in-box listings from the used ones, which usually sit in different price bands.

**Cross-border arbitrage.** The same query in `pl`, `ro` and `ua` returns three price distributions in three currencies. Traders live on that spread, and running it from one actor means the parsing is consistent across all three rather than three different scrapers' idea of a number.

**Watching supply in a category.** Schedule a query and count rows per run. `posted_label` shows how fresh the top of the results is, and diffing `ad_id` between runs tells you what appeared and what sold.

**Finding dealers among private sellers.** A seller posting many `Nowe` items in the same category is trading, not clearing out a garage. Group by `location` and `condition` to find them.

**Lead lists.** `ad_id`, `title`, `location` and `url` are enough to build a contact list for a category, with the ad page one fetch away.

### How it compares

| | this actor | `solidcode/olx-brazil-scraper` | `piotrv1001/olx-listings-scraper` |
|---|---|---|---|
| Per 1.000 ads | **$1,00** | $1,00 | $1,00 |
| Actor-start fee | $0,001 | declared | $0,00005 |
| Markets in one actor | **5 (PL, RO, BG, UA, PT)** | Brazil | not stated |
| Currency derived per row | **yes** | – | – |
| Free / swap / negotiable separated | **yes** | – | – |
| Monthly users | new | **53** | **36** |

Honest about the other side: this is a crowded category and the two leaders have 53 and 36 monthly users against this actor's none. On row price all three are identical, so there is no reason to switch unless you need more than one market or you have been bitten by the price formats.

### Pricing

Two events. `run_start` costs $0,0010 per run. `listing` costs $0,0010 per ad written to the dataset, which is $1,00 per 1.000. Pages that stay blocked never reach the dataset and are never billed. All pricing is pay-per-event, with no per-compute-unit charges.

### Limits and gotchas

- **The search path differs per country.** If you paste a URL, paste it from that country's own site. A Polish path on the Romanian domain returns a 404 page that still renders the site chrome, so it looks like a working request with no results.
- **Bulgaria prices in euro now.** `currency` is read from the price text, so Bulgarian rows come back as EUR. If you have historical BGN data, the changeover is yours to reconcile.
- **`posted_label` is not a timestamp.** OLX writes dates in the market's own language ("26 sierpnia 2026", "Обновено на 31 август 2026 г."), and it is returned verbatim rather than parsed through a month-name table per market that would silently be wrong somewhere.
- **Condition is optional for sellers.** Coverage runs from about 97% in Romania down to roughly 23% in Bulgaria. The summary reports it per run so you know before you filter on it.
- **A free ad is 0, a swap ad is null.** `price_type` distinguishes them. Filter to `asking price` before averaging anything.
- **OLX never 404s a search term.** A misspelled query returns a "no exact results" page carrying suggested ads instead, so the run comes back full of listings that do not match what you asked for. There is no signal in the response to catch this: check that `title` actually contains your term before trusting a run with a hand-typed query.
- **Promoted ads repeat across pages.** The walk deduplicates on `ad_id`, so a deep run returns slightly fewer rows than pages times 50. That is correct, not loss.

### FAQ

**Which OLX countries are supported?**
Poland, Romania, Bulgaria, Ukraine and Portugal, through the `market` field. OLX runs more domains than that; these five are the ones verified working.

**Why is my Bulgarian result in euro?**
Because that is what OLX shows there now. The currency comes from the price on the page rather than a table in the code.

**Why do some ads have no price?**
Check `price_type`. Swap-only ads have no asking price at all, and a small number of ads state none. Free ads come back as 0 with `price_type: free`.

**Can I filter by price?**
Yes, `price_min` and `price_max`, in the market's own currency.

**How many ads can I get in one run?**
Up to 1000, about 20 pages.

**Does it return seller phone numbers?**
No. The results page does not carry them and this actor reads only the results page.

### Related Actors

- [Marktplaats Scraper](https://apify.com/s-r/marktplaats-scraper) — the same job for the Dutch market
- [DBA.dk Scraper](https://apify.com/s-r/dba-scraper) — Danish classifieds
- [Willhaben Scraper](https://apify.com/s-r/willhaben-scraper) — Austrian classifieds across four verticals

# Actor input Schema

## `query` (type: `string`):

What to search for. Use the market's own language for the best results. Optional if you pass a URL instead.

## `market` (type: `string`):

Which OLX site to read. This also sets the search path, the exit country and the default currency.

## `url` (type: `string`):

An OLX search or category URL to read instead of a term. Paste it from your browser after applying any filters. Takes precedence.

## `price_min` (type: `integer`):

Lower price bound in the market's own currency.

## `price_max` (type: `integer`):

Upper price bound in the market's own currency.

## `limit` (type: `integer`):

How many ads to return, 1 to 1000. A page carries about 50.

## `retries` (type: `integer`):

Retry attempts per page, each with a rotated user agent and TLS fingerprint.

## Actor input object example

```json
{
  "query": "rower",
  "market": "pl",
  "url": "https://www.olx.pl/oferty/q-rower/",
  "limit": 50,
  "retries": 3
}
```

# Actor output Schema

## `results` (type: `string`):

One row per classified ad.

## `summary` (type: `string`):

Ads returned, pages fetched, the split of price types, how many are negotiable, the currencies actually seen, and the market with its exit country.

## `errors` (type: `string`):

Per-page failures with a code and a redacted message.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "query": "rower",
    "market": "pl",
    "limit": 50,
    "retries": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/olx-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "query": "rower",
    "market": "pl",
    "limit": 50,
    "retries": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/olx-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "query": "rower",
  "market": "pl",
  "limit": 50,
  "retries": 3
}' |
apify call s-r/olx-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/olx-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/vk9bzQBjThbdbi4WJ/builds/dmsdVfx6IU89x6OGB/openapi.json
