# Pracuj.pl Scraper · Poland Jobs, Salaries & Alerts (`memo23/pracuj-scraper`) Actor

Search Pracuj.pl — Poland's #1 job board — by keyword, city & filters. Structured salary (min/max/currency), contract, work mode + AI summary as clean JSON/CSV. Incremental monitoring emits only NEW jobs; Telegram/Slack/Discord alerts included. Pure HTTP — $0.99 per 1,000 jobs.

- **URL**: https://apify.com/memo23/pracuj-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Jobs, AI, Agents
- **Stats:** 36 total users, 10 monthly users, 99.4% runs succeeded, 1 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.99 / 1,000 job results

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

## Pracuj.pl Scraper — Search, Monitor & Alert

Scrape **Pracuj.pl** — Poland's #1 job board — by keyword, city and filters, or by pasting any search URL. One clean row per job with **structured salary** (`salaryMin`/`salaryMax`, not just a string), contract type, work mode, seniority, AI summary and more. Run it on a schedule with **incremental mode** and it becomes a job monitor: only NEW and UPDATED offers are emitted (and billed), with optional **Telegram / Slack / Discord / webhook alerts**. Pure HTTP, no browser — at **$0.99 per 1,000 jobs**, this is the cheapest way to watch the Polish job market.

![How it works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-pracuj.png)

### Why use this scraper

- **Search built in** — keyword, city + radius, voivodeship, seniority, contract type, work mode, schedule, posting age. No URL crafting needed (but pasted URLs work too).
- **Filters no other Pracuj actor has** — minimum salary in PLN (not just "has salary"), one-click-apply only, direct-employers-only (skip recruitment agencies), Ukrainian-friendly offers, SuperOffers.
- **Structured salary** — `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryGross` parsed from every disclosed range. Feed dashboards without string-wrangling.
- **Incremental monitoring** — recurring runs emit only what changed (`changeStatus`: NEW / UPDATED / REPOST). Unchanged offers are skipped **before** they cost anything — typically 80–95% cheaper for daily watches.
- **Alerts out of the box** — Telegram, Slack, Discord or any webhook get a summary with job links at the end of each run. Monitor + alert = a no-code "new Pracuj jobs" bot.
- **Free contact extraction** — `extractedEmails[]` and `extractedPhones[]` regex-pulled from listing text at no charge; optional deep employer-email enrichment when you need more.
- **Everything from one fetch** — title, company, salary, AI requirements summary, per-city locations with coordinates; optional `fetchDetails` adds the full JD, skills, technologies, apply URL and phone.
- **Pure HTTP, no browser** — fast, cheap, resilient (residential proxy with auto-rotation on blocks, built in at no extra cost).

### What it does

Describe a search (or paste Pracuj.pl URLs). The actor reads the live results, paginates automatically, and emits one dataset row per job. In incremental mode it compares every offer against your private baseline from previous runs and emits only the diff.

### Supported inputs

| Input style | Example |
|---|---|
| Keyword search | `query: "python developer"` |
| City + radius | `location: "Warszawa"`, `radiusKm: 30` |
| Region | `region: "7"` (mazowieckie) — or `"17"` for abroad |
| Any filter combo | seniority, contract, work mode, schedule, posted-within, min salary… |
| Pasted URL | any `pracuj.pl/praca/...` search URL from your browser |

### Use cases

- **Job monitoring & alerts** — schedule a daily run with `incrementalMode` + Telegram and get new matching offers with links, for cents a day.
- **Labour-market & salary research** — structured salary across Polish cities, roles and stacks; benchmark without parsing "12 000–18 000 zł".
- **Recruitment & sourcing** — track who's hiring for which roles; filter agencies out with `directEmployersOnly`.
- **Lead generation** — company names + profiles, free extracted emails/phones, optional deep employer-email enrichment.
- **Aggregation & job boards** — pull fresh Polish postings into your product; `compact` mode pipes straight into an ATS or LLM.

### How it works

1. Set a search (keywords, city, filters) — or paste search URLs. Both can run together.
2. The actor reads each results page's embedded job data and paginates until your cap.
3. Each job becomes one flat row (optionally enriched from its detail page). In incremental mode, unchanged offers are dropped before any cost is incurred.
4. Optional: end-of-run alert to your Telegram / Slack / Discord / webhook with the new jobs.

**Daily monitor recipe:** set `query` + `location`, turn on `incrementalMode`, add your `telegramToken` + `telegramChatId`, and schedule the actor daily. First run builds the baseline; every run after that pings you only when something new appears.

### Input configuration

| Field | Type | Description |
|---|---|---|
| `query` | string | Search keywords, e.g. `python developer`, `księgowa`. |
| `location` | string | City, e.g. `Warszawa`. Pair with `radiusKm` (0 = city only; site default 30). |
| `region` | string | Voivodeship `1`–`16`, `17` = abroad. |
| `positionLevels` | array | Seniority: `1` trainee, `3` assistant, `17` junior, `4` mid, `18` senior, `19` expert, `5` manager/coordinator, `20` manager, `6` director, `21` president. |
| `contractTypes` | array | `0` employment, `1` specific-task, `2` commission, `3` B2B, `4` replacement, `5` agency, `6` temporary, `7` internship. |
| `workModes` | array | `full-office`, `hybrid`, `home-office`, `mobile`. |
| `workSchedules` | array | `0` full-time, `1` part-time, `2` additional/temporary. |
| `postedWithinDays` | string | `1`, `3`, `7`, `14`, `30`. |
| `minSalaryPln` | integer | Only offers paying at least this (PLN). |
| `withSalaryOnly` | boolean | Only offers that disclose salary. |
| `directEmployersOnly` | boolean | Skip recruitment-agency offers (source-side filter). |
| `oneClickApplyOnly` / `superOffersOnly` / `ukrainianFriendly` | boolean | More source-side toggles. |
| `startUrls` | array | Pasted Pracuj.pl search URLs (run alongside the search above). |
| `incrementalMode` | boolean | Emit only NEW / UPDATED / REPOST rows vs your previous runs. |
| `stateKey` | string | One baseline per saved search — name them per schedule. |
| `skipReposts` | boolean | Drop re-published offers (same company + title + workplace as an expired one). |
| `telegramToken` + `telegramChatId` | string | Your bot posts the run summary with job links. |
| `slackWebhookUrl` / `discordWebhookUrl` / `webhookUrl` (+`webhookHeaders`) | string | More alert channels; the generic webhook gets a JSON payload. |
| `notificationLimit` / `notifyOnlyChanges` | int / bool | Jobs per message (default 10); stay silent when nothing changed (default on). |
| `compact` | boolean | Core-fields-only output for AI agents / MCP / ATS. |
| `flatten` | boolean | CSV-friendly flattening (default on). |
| `fetchDetails` | boolean | Full description, responsibilities, required & optional skills, technologies, categories, apply URL, contact phone. +1 request/job, billed as `additional-data`. |
| `enrichEmails` | boolean | Employer contact-email discovery (billed per email found; misses are free). |
| `maxItems` / `maxConcurrency` / `proxy` | — | Run limits; proxy is built-in residential by default. |

### Input examples

**Basic search**

```json
{ "query": "python developer", "location": "Warszawa", "maxItems": 100 }
```

**Senior remote B2B roles paying 20k+ with salary shown, direct employers only**

```json
{
  "query": "java",
  "positionLevels": ["18", "19"],
  "contractTypes": ["3"],
  "workModes": ["home-office"],
  "minSalaryPln": 20000,
  "directEmployersOnly": true
}
```

**Daily monitor with Telegram alerts (schedule this daily)**

```json
{
  "query": "devops",
  "location": "Kraków",
  "postedWithinDays": "3",
  "incrementalMode": true,
  "stateKey": "devops-krakow",
  "skipReposts": true,
  "telegramToken": "123456:ABC…",
  "telegramChatId": "123456789"
}
```

**Paste mode**

```json
{ "startUrls": ["https://www.pracuj.pl/praca/python;kw/warszawa;wp?et=4%2C18&wm=home-office"] }
```

### How much does it cost?

Pay-per-event: **$0.009 per run start + $0.00099 per job**. Optional detail/email enrichment bills a separate `additional-data` event ($0.0008).

- **1,000 jobs ≈ $1.00** — roughly half the going rate for comparable Pracuj.pl scrapers.
- **Daily monitoring example:** a watch that scans ~600 offers and finds 15 new ones costs $0.009 + 15 × $0.00099 ≈ **$0.024/run** with incremental mode — vs ~$0.60 re-scraping everything. That's ~96% less, about **$0.72/month for a daily alert**.

### Output sample

```json
{
  "portal": "pracuj",
  "offerId": "1005006098",
  "url": "https://www.pracuj.pl/praca/...,oferta,1005006098",
  "jobTitle": "Senior Python Developer",
  "companyName": "NESS SOLUTION sp. z o.o.",
  "salaryText": "135–150 zł netto (+ VAT) / godz.",
  "salaryMin": 135,
  "salaryMax": 150,
  "salaryCurrency": "PLN",
  "salaryPeriod": "hour",
  "salaryGross": false,
  "typesOfContract": ["Kontrakt B2B"],
  "workModes": ["Praca zdalna"],
  "positionLevels": ["Starszy specjalista (senior)"],
  "isRemoteWorkAllowed": true,
  "locations": [{ "workplace": "Warszawa", "latitude": 52.23, "longitude": 21.01 }],
  "aiSummary": "Masz minimum 5 lat doświadczenia…",
  "extractedEmails": ["rekrutacja@ness.pl"],
  "extractedPhones": ["+48 601 234 567"],
  "changeStatus": "NEW",
  "publishedDate": "2026-08-04T05:55:21.007Z"
}
```

### Key output fields

| Field | Description |
|---|---|
| `offerId` / `groupId` / `url` | Pracuj offer id, group id + detail URL |
| `jobTitle` / `companyName` / `companyId` / `companyProfileUrl` / `companyLogoUrl` | role + employer |
| `salaryText` | displayed salary verbatim (when shown) |
| `salaryMin` / `salaryMax` / `salaryCurrency` / `salaryPeriod` / `salaryGross` | parsed salary — numbers, currency, per hour/month, gross vs net |
| `typesOfContract` / `workSchedules` / `workModes` / `positionLevels` | employment type, schedule, on-site/hybrid/remote, seniority |
| `isRemoteWorkAllowed` / `isOneClickApply` | flags |
| `locations` | per-city postings (workplace, offer id, URL, coordinates) |
| `aiSummary` | Pracuj's AI-generated requirements summary |
| `extractedEmails` / `extractedPhones` | free regex-extracted contacts from listing text |
| `changeStatus` | `NEW` / `UPDATED` / `REPOST` *(incremental mode)* |
| `publishedDate` / `expirationDate` | posting dates |
| `description` / `responsibilities` / `requiredSkills` / `optionalSkills` / `technologies` / `aboutProject` | full JD sections *(with `fetchDetails`)* |
| `categories` / `applyUrl` / `contactPhone` | categories, application link, recruiter phone *(with `fetchDetails`)* |
| `contactEmail` / `contactWebsite` | employer email + site *(with `enrichEmails`)* |

### FAQ

**Can I search without building a URL?** Yes — `query`, `location` and every filter above are actor inputs. Pasted URLs still work and can run in the same crawl.

**How do I set up a daily job alert?** Turn on `incrementalMode`, add a `stateKey`, connect Telegram/Slack/Discord, and schedule the actor daily. You'll only hear about offers that are new or changed since the last run — and only pay for those rows.

**Does each schedule need its own `stateKey`?** Yes, one per saved search (e.g. `python-warszawa`, `devops-krakow`), so baselines don't mix.

**Do I get the salary as numbers?** Yes — `salaryMin`/`salaryMax`/`salaryCurrency`/`salaryPeriod`/`salaryGross` are parsed from every offer that discloses pay. `salaryText` keeps the original string. You can also filter at the source with `minSalaryPln`.

**Can I skip recruitment agencies?** `directEmployersOnly: true` applies Pracuj's own employer-type filter server-side — agency listings never reach your dataset.

**How do I get the full description and apply link?** Turn on `fetchDetails` (+1 request per job, billed as `additional-data`).

**Can I use it from the Apify API or an MCP server?** Yes — standard Apify actor: REST API, scheduler, webhooks, and MCP all work. Use `compact: true` for small LLM-friendly payloads.

**Why residential proxies?** Pracuj.pl 403-blocks datacenter IPs. The actor ships with residential rotation built in — you don't configure anything.

### Your feedback

If this scraper saves you time, a **short review on this page** helps other people find it — it takes a minute and genuinely matters for a small independent tool. If something's missing or broken instead, open an issue first: bugs and feature requests usually ship within days.

### Support

Found an issue or want a field added? Open an issue on the actor's Issues tab and we'll take a look — feature requests usually ship within days.

### Additional services

Need employer contact emails beyond what's printed in listings? Turn on `enrichEmails` to append a best-effort `contactEmail` + `contactWebsite` per company (billed only when an email is found).

### Explore more scrapers

Check the publisher's profile for more job-board and directory scrapers (LinkedIn Jobs, Naukri, SEEK, Jobstreet, Indeed-style boards and more).

### 🤖 For AI Agents & LLM Apps

- `compact: true` returns only the core fields (title, company, structured salary, locations, mode, level, contract, dates, URL) — small enough to drop whole result sets into a model context.
- Stable JSON schema: absent values are `null`/empty arrays, never missing keys — safe to parse without guards.
- Works through Apify's MCP server like any actor; pair `incrementalMode` with an agent loop to reason only over what changed.

### ⚠️ Disclaimer

This actor collects only publicly available job-listing data for legitimate research, lead-generation and recruitment use. Respect Pracuj.pl's terms of service and applicable laws (including data-protection rules) in your jurisdiction. You are responsible for how you use the scraped data.

### SEO Keywords

pracuj scraper, pracuj.pl scraper, pracuj api, pracuj.pl monitor, poland job scraper, polish jobs data, job listings poland, oferty pracy scraper, praca warszawa scraper, new job alerts poland, pracuj.pl telegram alert, job monitoring poland, salary data poland, pracuj salary scraper, recruitment data poland, IT jobs poland scraper, job board scraper, employer leads poland, pracuj export, pracuj.pl rss alternative

# Actor input Schema

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

What to search for, e.g. `python developer`, `księgowa`, `kierowca C+E`. Leave empty to browse by location/filters only.

## `location` (type: `string`):

City or town, e.g. `Warszawa`, `Kraków`, `Gdańsk`. Combine with **Radius** to widen the net.

## `radiusKm` (type: `integer`):

Only with **City** set. `0` = the city itself; Pracuj.pl's own default is 30 km.

## `region` (type: `string`):

Filter by Polish region — or `Abroad` for offers outside Poland.

## `positionLevels` (type: `array`):

Seniority filter (multiple allowed). Codes verified against Pracuj.pl's own listings.

## `contractTypes` (type: `array`):

Contract filter (multiple allowed).

## `workModes` (type: `array`):

On-site / hybrid / remote / mobile (multiple allowed).

## `workSchedules` (type: `array`):

Full-time / part-time / additional (multiple allowed).

## `postedWithinDays` (type: `string`):

Only offers published in this window.

## `minSalaryPln` (type: `integer`):

Only offers with salary at or above this. Set `1` to mean "any offer that discloses salary" — or use the checkbox below.

## `withSalaryOnly` (type: `boolean`):

Skip offers that hide the salary. Ignored when **Minimum salary** is set.

## `directEmployersOnly` (type: `boolean`):

Excludes offers posted by recruitment agencies, using Pracuj.pl's own classification — applied at the source, costs no extra requests.

## `oneClickApplyOnly` (type: `boolean`):

Only offers you can apply to without leaving Pracuj.pl.

## `superOffersOnly` (type: `boolean`):

Only offers flagged as SuperOferta.

## `ukrainianFriendly` (type: `boolean`):

Only offers marked as welcoming Ukrainian-speaking candidates.

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

Full Pracuj.pl /praca/ search URLs. Each is paginated automatically until `Maximum items` is reached.

## `incrementalMode` (type: `boolean`):

Each row gets a `changeStatus` of `NEW`, `UPDATED` or `REPOST`. Typically cuts recurring-run costs by 80–95%.

## `stateKey` (type: `string`):

Name for this monitor's baseline. Use a different key per saved search (e.g. `python-warszawa`) so schedules don't share state.

## `skipReposts` (type: `boolean`):

Drop "new" offers that are really the same company+title+workplace as a previously seen offer (expired and re-published).

## `telegramToken` (type: `string`):

Your bot's token from @BotFather. Used together with **Telegram chat ID**.

## `telegramChatId` (type: `string`):

Chat/channel/group ID the bot should post to (e.g. `123456789` or `-100…` for channels).

## `slackWebhookUrl` (type: `string`):

Slack incoming-webhook URL (https://hooks.slack.com/services/…).

## `discordWebhookUrl` (type: `string`):

Discord channel webhook URL (https://discord.com/api/webhooks/…).

## `webhookUrl` (type: `string`):

POSTs a JSON payload (run id, dataset id, counts, top jobs) to this URL at the end of the run.

## `webhookHeaders` (type: `object`):

Extra HTTP headers for the generic webhook, e.g. `{"authorization": "Bearer …"}`.

## `notificationLimit` (type: `integer`):

How many job lines to include in a message (links included).

## `notifyOnlyChanges` (type: `boolean`):

Stay silent when a monitoring run finds no new or updated offers.

## `flatten` (type: `boolean`):

When enabled (default), nested objects/arrays (e.g. `locations`, `technologies`, `responsibilities`) are flattened into CSV-friendly columns. Disable to keep the full nested JSON.

## `compact` (type: `boolean`):

Core fields only — title, company, structured salary, locations, work mode, level, contract, dates, URL. Pipe straight into an LLM context or ATS without stripping extras.

## `fetchDetails` (type: `boolean`):

If enabled, opens each job's detail page to add the **full description**, **responsibilities**, **required & optional skills**, **technologies**, **job categories**, the **apply URL** and the **contact phone**. Adds one extra request per job and charges an `additional-data` event each time. Leave off for the fast/cheap search-only output.

## `enrichEmails` (type: `boolean`):

If enabled, tries to find a contact email for each employer by discovering the company's website (Clearbit) and reading its contact/about pages. Adds contactEmail + contactWebsite columns. Best-effort, billed per email found; never charged for misses. (Independent of the free `extractedEmails` column, which regex-scans the listing text at no cost.)

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

Hard cap on the number of jobs collected. Use this cap to control billing.

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

Maximum number of rows processed in parallel (mainly relevant when detail-fetch or employer-email enrichment is on). 6-12 is the sweet spot.

## `maxRequestRetries` (type: `integer`):

Number of retries before a failed request is given up.

## `proxy` (type: `object`):

Leave empty — the actor already routes all traffic through its own built-in residential proxy at no extra cost to you. Only set this if you want to use your own proxies.

## Actor input object example

```json
{
  "query": "python developer",
  "withSalaryOnly": false,
  "directEmployersOnly": false,
  "oneClickApplyOnly": false,
  "superOffersOnly": false,
  "ukrainianFriendly": false,
  "incrementalMode": false,
  "skipReposts": false,
  "notificationLimit": 10,
  "notifyOnlyChanges": true,
  "flatten": true,
  "compact": false,
  "fetchDetails": false,
  "enrichEmails": false,
  "maxItems": 10000,
  "maxConcurrency": 8,
  "maxRequestRetries": 5
}
```

# 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": "python developer"
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/pracuj-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": "python developer" }

# Run the Actor and wait for it to finish
run = client.actor("memo23/pracuj-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": "python developer"
}' |
apify call memo23/pracuj-scraper --silent --output-dataset

```

## MCP server setup

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