# Otomoto.pl Scraper (`alwaysprimedev/otomoto-scraper`) Actor

Scrape detailed car listings from otomoto.pl including price, specs, equipment, photos and seller info. Supports search URLs, brand/model filters, pagination and large-scale runs with Apify Proxy.

- **URL**: https://apify.com/alwaysprimedev/otomoto-scraper.md
- **Developed by:** [Always Prime](https://apify.com/alwaysprimedev) (community)
- **Categories:** Lead generation, SEO tools, E-commerce
- **Features:** Limited permissions
- **Stats:** 5 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 cars

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## 🚗 Otomoto.pl Scraper — Polish Car Market in One Click

> **Turn Poland's #1 automotive marketplace into a clean, structured dataset — at scale, without writing a line of code.**

[Otomoto.pl](https://www.otomoto.pl/) lists **hundreds of thousands** of cars, vans, motorcycles and trucks across Poland. This Actor extracts them all into a tidy CSV, JSON or Excel file ready for analysis, dashboards, or your own product. ⚡

***

### ✨ Why this scraper?

- 🎯 **Just paste a URL.** Take any otomoto.pl search page (filtered however you like) — the Actor walks every page and every listing automatically.
- 🧰 **No coding needed.** Configure everything from the UI: brand, model, year, price range, max items.
- 📦 **Rich, structured output.** 50+ fields per listing — far more than just title and price.
- 🛡️ **Survives blocks.** Smart retries, session rotation, captcha detection — runs for hours without babysitting.
- 💸 **Pay only for what you get.** Per-listing pricing — no surprises, no idle compute charges.
- 🔁 **Resumable.** A run that gets interrupted picks up exactly where it stopped.

***

### 🎯 Perfect for

- 📈 **Market analysts** tracking used-car prices, depreciation curves, regional spreads
- 🏭 **Dealers & importers** monitoring competition and sourcing inventory
- 💼 **Lead generation** — build a database of dealers, private sellers, and their inventory
- 🤖 **Price-comparison & valuation tools** that need fresh, daily-refreshed data
- 📊 **Researchers & journalists** studying the Polish automotive landscape
- 🛠️ **Data products** powered by automotive listings

***

### 🚀 Quick start

**Option 1 — paste a search URL (easiest)**

Open otomoto.pl, set any filters you want, copy the URL, paste it in:

```json
{
  "startUrls": [
    { "url": "https://www.otomoto.pl/osobowe/bmw/seria-3?search%5Bfilter_float_year%3Afrom%5D=2018" }
  ],
  "maxItems": 500
}
```

**Option 2 — build the search from filters**

```json
{
  "category": "osobowe",
  "brand": "bmw",
  "model": "seria-3",
  "yearFrom": 2018,
  "priceTo": 120000,
  "maxItems": 500
}
```

Hit **Start** and your dataset starts filling up. ▶️

***

### ⚙️ Input options

| Field | What it does |
| --- | --- |
| 🔗 **Start URLs** | Any otomoto.pl search, category, or detail URLs. Mix and match. |
| 🚙 **Category** | `osobowe` (cars), `dostawcze` (vans), `ciezarowe` (trucks), `motocykle`, `kampery`, `przyczepy` |
| 🏷️ **Brand / Model** | Brand and model slugs (e.g. `bmw` / `seria-3`) — used to build a search if no URL is given |
| 📅 **Year from / to** | Production year range |
| 💰 **Price from / to** | Price range in PLN |
| 🔢 **Max items** | Hard cap on listings (your cost ceiling) |
| 📄 **Max pages per start URL** | How many search-result pages to walk per seed |
| 📝 **Include description** | Full seller description (HTML stripped) |
| 🖼️ **Include images** | Full photo gallery URLs |
| 📞 **Include phone** | Seller contact tokens (encrypted by Otomoto) |
| 🌍 **Language** | `pl`, `en`, `de`, `uk`, `ru` |
| ⚡ **Max concurrency** | Parallel requests (default 10) |

***

### 📦 What you get per listing

Every record contains the full picture — vehicle specs, pricing, condition, equipment, photos, seller and location:

```json
{
  "id": "6147499760",
  "url": "https://www.otomoto.pl/osobowe/oferta/bmw-seria-3-...html",
  "title": "BMW Seria 3 320d xDrive M Sport",

  "price": 219900,
  "currency": "PLN",

  "brand": "BMW",
  "model": "Seria 3",
  "generation": "G20/G21 (2019-)",
  "year": 2024,
  "mileage": 121101,
  "fuelType": "Diesel",
  "gearbox": "Automatyczna",
  "drive": "4x4 (dołączany automatycznie)",
  "engineCapacity": 2993,
  "enginePower": 340,
  "bodyType": "Sedan",
  "color": "Czarny",
  "colorType": "Metalik",
  "doorCount": 4,
  "seatCount": 5,
  "countryOrigin": "Polska",

  "isDamaged": false,
  "isAccidentFree": true,
  "isImported": false,
  "isRegisteredInPoland": true,
  "isVerified": false,

  "features": [
    "Klimatyzacja automatyczna", "Dach panoramiczny",
    "Apple CarPlay", "Android Auto", "Asystent pasa ruchu", "..."
  ],
  "images": [
    "https://ireland.apollo.olxcdn.com/v1/files/...image",
    "..."
  ],
  "description": "✅INCHCAPE PARK to nowoczesny salon...",

  "sellerType": "professional",
  "sellerName": "Inchcape Park - Dealer BMW Premium Selection",
  "sellerActiveAds": 31,
  "sellerWebsite": "https://www.inchcape-park.pl",
  "sellerDealerPageUrl": "https://INCHCAPE-PARK.otomoto.pl/inventory",

  "address": "Warszawa",
  "city": "Warszawa",
  "region": "Mazowieckie",
  "latitude": 52.19511491,
  "longitude": 20.94118351,

  "createdAt": "2026-04-29T14:32:00Z",
  "scrapedAt": "2026-05-03T08:14:55.123Z"
}
```

🔓 **Note on VIN & phone numbers:** Otomoto encrypts these server-side. The Actor returns the encrypted tokens (`vinEncrypted`, `sellerPhonesEncrypted`) — they cannot be decrypted client-side.

***

### 💡 Pro tips

- 📐 **Set `maxItems` your first run.** Popular brands have 10,000+ active listings. The hard cap is your safety net.
- 🧩 **Split big jobs.** For very large datasets (e.g. all BMWs ever listed), run several Actors in parallel — one per year range or region. Faster *and* safer than cranking concurrency.
- 🔄 **Schedule daily runs** to track price drops, new listings, or sold cars over time.
- 🧪 **Try a small run first** (`maxItems: 50`) to confirm filters return what you expect before launching a 10k-item job.

***

### 💸 Pricing

This Actor uses **Pay-per-event** pricing — you pay a tiny fee per listing scraped, plus a small one-time start fee. No idle CPU charges, no padding. The price you see in the **Pricing** tab is exactly what you pay for the data you get. ✅

***

### 🆘 Need help?

- 💬 Have a question, or want a custom Otomoto data feed? Open an issue on the Actor's page or message the maintainer.
- 🐛 Found a listing the scraper missed or a field that came back wrong? Send the URL — fixes ship fast.

***

**Ready to put Poland's car market in your spreadsheet?** Hit **Start** and watch your dataset grow. 🚀

# Actor input Schema

## `startUrls` (type: `array`):

One or more otomoto.pl search/category/listing URLs. Examples: https://www.otomoto.pl/osobowe/bmw, https://www.otomoto.pl/osobowe/audi/a6/od-2015. Detail (oferta) URLs are also supported and will be fetched directly.

## `category` (type: `string`):

Top-level category. Used only when Start URLs is empty and a brand is provided below.

## `brand` (type: `string`):

Brand slug, e.g. bmw, audi, volkswagen. Combined with Category to build the search URL.

## `model` (type: `string`):

Model slug, e.g. seria-3, a6, golf. Used together with brand.

## `yearFrom` (type: `integer`):

Filter: production year from.

## `yearTo` (type: `integer`):

Filter: production year to.

## `priceFrom` (type: `integer`):

Filter: minimum price in Polish złoty.

## `priceTo` (type: `integer`):

Filter: maximum price in Polish złoty.

## `maxItems` (type: `integer`):

Hard cap on the number of listings to scrape. Use to control cost. Increase the run timeout accordingly — at ~100 listings/min, 1 000 items needs ~10 min.

## `maxPagesPerStartUrl` (type: `integer`):

How many search-result pages to walk for each Start URL (each page has up to ~32 listings).

## `includeDescription` (type: `boolean`):

Include the seller's free-text description (HTML stripped). Disable for slimmer output.

## `includeImages` (type: `boolean`):

Include the full image gallery URLs.

## `includePhone` (type: `boolean`):

Try to extract seller phone numbers when present in the listing payload. May require extra requests.

## `language` (type: `string`):

Site language for labels and seller location.

## `maxConcurrency` (type: `integer`):

Maximum parallel requests. Lower this if you hit blocking.

## `extendOutputFunction` (type: `string`):

Optional async JS function `({ item, $, request, json }) => Partial<Item>` to enrich/override scraped fields. Returned object is merged into the item.

## `debugLog` (type: `boolean`):

Print verbose logs (useful when developing or debugging blocks).

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.otomoto.pl/osobowe/bmw/seria-3?search%5Bfilter_float_year%3Afrom%5D=2018"
    }
  ],
  "category": "osobowe",
  "maxItems": 50,
  "maxPagesPerStartUrl": 50,
  "includeDescription": true,
  "includeImages": true,
  "includePhone": false,
  "language": "pl",
  "maxConcurrency": 10,
  "extendOutputFunction": "// async ({ item, $, request, json }) => {\n//     return { customTag: 'manual-import' };\n// }",
  "debugLog": false
}
```

# 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 = {
    "startUrls": [
        {
            "url": "https://www.otomoto.pl/osobowe/bmw/seria-3?search%5Bfilter_float_year%3Afrom%5D=2018"
        }
    ],
    "maxItems": 50,
    "extendOutputFunction": `// async ({ item, $, request, json }) => {
//     return { customTag: 'manual-import' };
// }`
};

// Run the Actor and wait for it to finish
const run = await client.actor("alwaysprimedev/otomoto-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 = {
    "startUrls": [{ "url": "https://www.otomoto.pl/osobowe/bmw/seria-3?search%5Bfilter_float_year%3Afrom%5D=2018" }],
    "maxItems": 50,
    "extendOutputFunction": """// async ({ item, $, request, json }) => {
//     return { customTag: 'manual-import' };
// }""",
}

# Run the Actor and wait for it to finish
run = client.actor("alwaysprimedev/otomoto-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 '{
  "startUrls": [
    {
      "url": "https://www.otomoto.pl/osobowe/bmw/seria-3?search%5Bfilter_float_year%3Afrom%5D=2018"
    }
  ],
  "maxItems": 50,
  "extendOutputFunction": "// async ({ item, $, request, json }) => {\\n//     return { customTag: '\''manual-import'\'' };\\n// }"
}' |
apify call alwaysprimedev/otomoto-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,alwaysprimedev/otomoto-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/CiIwHOVjGhognvUOA/builds/M9nFwviJNP8N546Ua/openapi.json
