Polish Jobs Aggregator - every portal in one feed avatar

Polish Jobs Aggregator - every portal in one feed

Pricing

from $3.00 / 1,000 results

Go to Apify Store
Polish Jobs Aggregator - every portal in one feed

Polish Jobs Aggregator - every portal in one feed

Searches Poland's biggest job boards at once - pracuj.pl, OLX, praca.gov.pl, RocketJobs and optionally justjoin.it — and returns one deduplicated feed with a single schema.

Pricing

from $3.00 / 1,000 results

Rating

0.0

(0)

Developer

Paweł

Paweł

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

2 days ago

Last modified

Share

🇵🇱 Polish Jobs Aggregator — search or watch five job boards at once

One input, five Polish job boards, one clean feed. Search once, or leave it running as a standing watch that tells you only what is new since last time.

Everything runs inside this single Actor. You do not need to own, rent, or subscribe to any other Actor.


🚀 What it does

Searches five boards in parallelpracuj.pl, praca.gov.pl (the government database), OLX, RocketJobs.pl and justjoin.it
Speaks one schemaThe five boards disagree on nearly every field name. You get 38 identical fields, whichever board an offer came from.
Compares salaries honestlyMonthly, hourly and daily rates are normalised to a monthly band, so 130 PLN/h and 21 000 PLN/mth sit in the same column.
Merges duplicatesThe same job posted on three boards becomes one record that keeps all three links.
Filters like a human wouldKeep only offers that actually mention your field; drop shift work; drop a city you did not ask for; drop anything older than N days.
Watches, not just searchesTurn on watch mode and every run returns only the offers that are NEW, that CHANGED, or that came back.

👥 Who it is for

  • Job seekers with a narrow field. "Chemical technology near Kraków, no shift work" is not a query any single board can answer. This is.
  • Recruiters and sourcers who need one deduplicated market view instead of five browser tabs.
  • Analysts tracking salaries, contract types, or how fast openings appear and disappear.
  • Anyone building a job alert — watch mode is designed to run on a schedule and be read like an inbox.

🎛️ Two ways to run it

1. Search — the whole matching feed, every run

{
"keyword": "magazynier",
"location": "Warszawa",
"maxItemsPerPortal": 25
}

Returns ~90 offers from four boards, deduplicated. Verified run: 92 records in 47 seconds.

2. Watch — only what changed

{
"keywords": ["technolog", "laborant", "chemik", "kontrola jakości"],
"location": "Kraków",
"includeTerms": ["chemi", "laborator", "technolog", "kontrola jakości", "analiz"],
"excludeTerms": ["praktyki", "staż bezpłatny"],
"excludeShiftWork": true,
"strictLocation": true,
"incremental": true,
"maxItemsPerPortal": 15
}

The first run records a baseline and returns everything that matched — 14 offers in the verified run, filtered down from 38 raw hits:

Unified 38 records from 4 portals
strictLocation: kept 28 of 38 in "Kraków"
Watch filters: 2815 (12 off-topic, 1 shift work, 0 too old)
Dedupe: 1514
First run — 14 offers recorded as the baseline

Every later run on the same input returns only what changed. Nothing new? One informational record saying so:

{ "noResults": true, "reason": "Nothing new since the last run of this watch." }

Schedule it daily in Apify and it becomes a job alert that never repeats itself.


📋 More examples


🔍 The filters, and what they really do

OptionWhat it does
keywords[]Several phrases, each run as its own search, merged and deduplicated. A niche is rarely one word.
includeTerms[]Keep an offer only if its full text mentions one of these. This is the relevance filter a job board cannot do — boards match titles, this reads the description.
excludeTerms[]Kill a recurring false positive: praktyki, staż, an agency you never want to hear from.
excludeShiftWorkDrops shift and night work, including the spelled-out forms (dwie zmiany, system trzyzmianowy) while keeping jedna zmiana. Also switches on detail fetching, because the one authoritative schedule field only appears there.
strictLocationOnly the city you asked for. Off by default: what a board usually "leaks" is the commuter belt (a Warsaw search returns Pruszków, Piaseczno), and most people want that.
maxAgeDaysIgnore anything published longer ago than this. Offers with no date are kept — plenty of boards omit it.
dedupeThresholdHow similar two offers must be to count as one. 0.85 is the tested default.

On shift work — read this before relying on it. There is exactly one authoritative schedule field across the five boards: praca.gov.pl's zmianowość. pracuj.pl looks like a second one but is not (its field says "Pełny etat" / "Część etatu" — an employment fraction, nothing about shifts). So the filter also reads the full offer description, where the signal genuinely is: on a warehouse search, 17 of 92 offers stated shift work in their text. Every returned offer is tagged with what was found and where it came from:

{ "shiftSignal": "shift", "shiftSignalSource": "description" }

shiftSignal: null means no board said anything — it is not a claim of day work. If you would rather drop those too, set unknownScheduleIsShift: true, but expect a much shorter list.


📦 What you get

38 flat fields per offer — flat on purpose, so a CSV or Google Sheets export stays readable.

Positiontitle, description, seniority[], skills[], contractType, workingTime, workMode (remote / hybrid / office / mobile), shiftSignal, shiftSignalSource EmployercompanyName, companyUrl, contactPhone, contactEmail (praca.gov.pl only) Locationcity, region, allCities[] (a posting open in five cities lists five), latitude, longitude MoneysalaryMin, salaryMax, salaryCurrency, salaryPeriod, salaryGross, salaryText, plus salaryMinMonthly / salaryMaxMonthly for comparison, and salaryPeriodInferred when a period had to be deduced Provenanceurl, applyUrl, sourcePortal, sourceId, duplicates[], duplicateCount, isCrossPortal, sourcePortals[] DatespublishedAt, expiresAt, scrapedAt Watch mode addschangeType (NEW / UPDATED / REAPPEARED / EXPIRED / UNCHANGED), firstSeenAt, lastSeenAt, isRepost, repostOfId

Sample record:

{
"sourcePortal": "justjoin.it",
"title": "Senior Fullstack Developer (Python + Angular)",
"companyName": "Ingenious.Build",
"city": "Kraków",
"allCities": ["Warszawa", "Kraków", "Wrocław", "Gdańsk", "Poznań"],
"workMode": "remote",
"salaryMin": 130,
"salaryMax": 170,
"salaryCurrency": "PLN",
"salaryPeriod": "hour",
"salaryGross": false,
"salaryMinMonthly": 21840,
"salaryMaxMonthly": 28560,
"contractType": "b2b",
"seniority": ["senior"],
"skills": ["PHP", "PHP 8", "DDD", "REST API", "Laravel"],
"shiftSignal": null,
"shiftSignalSource": null,
"publishedAt": "2026-08-18T17:00:20Z",
"url": "https://justjoin.it/job-offer/...",
"applyUrl": "https://grnh.se/8j8j36m05us",
"duplicateCount": 0,
"isCrossPortal": false
}

Every run also writes a RUN_SUMMARY to the key-value store: what each portal returned, what each filter removed, and what the deduplicator merged. If a run comes back short, that record says which stage is responsible.


🗺️ The boards, and how each one is read

BoardWhat it isHow it is read
pracuj.plThe largest general boardSearch pages, Polish residential IP (Cloudflare)
praca.gov.plGovernment database, ~46 000 vacanciesIts public API
OLXLocal and blue-collar workSearch pages, then the site's own offer API
RocketJobs.plNon-IT, ~15 500 active offersSitemap of active offers, then each offer page
justjoin.itIT only, ~9 900 active offersSitemap of active offers, then each offer page

RocketJobs and justjoin are read from the public site because their JSON API was withdrawn — it answers 503 on every path, /health included. The sitemap route is cheaper anyway: two requests enumerate the entire board, offers are filtered on the URL before anything is downloaded, and each offer page carries the complete record — salary band, required skills, seniority and the employer's own application link.

A failing board never fails the run. It is reported instead, with the distinction that matters: SUCCEEDED, EMPTY (it ran and had nothing for you) or FAILED (it broke). A board that is down will never be presented to you as an empty job market.


🔁 How duplicates are merged

Tuned on a 3 247-offer experiment, not on guesswork:

  • Parenthetical content is compared, not deleted. Deleting it merged (Angular, Java) with (Java+React) — two different openings at one software house.
  • Seniority is a veto when both sides state it, and a penalty when only one does.
  • Salary only vetoes when both sides quote money, because outside IT only about a third of offers do.
  • Complete-linkage clustering — single linkage chained unrelated roles together through one shared middle record.
  • A shared placeholder is not a shared identity. praca.gov.pl anonymises employers as literally "kontakt przez PUP", and six unrelated warehouses sharing that string were being fused into one offer.

Merged records keep every source link in duplicates[]. Prefer them separate? Set keepDuplicatesSeparate: true.


❓ FAQ

Do I need any other Actor, subscription or API key? No. Everything runs here.

Do I need a proxy? pracuj.pl and OLX need a Polish residential IP; the default input already asks for one. The other three boards work without any proxy, and are not sent through one by default so you do not burn residential traffic for nothing.

How does watch mode remember? In a named key-value store, keyed by your query and your filters. Change the search or a filter and you get a fresh baseline rather than silently inheriting the old one. resetState: true forgets a baseline on purpose.

Why did my watch return nothing? Because nothing changed — that is the normal outcome. RUN_SUMMARY shows the whole funnel if you want to confirm.

Can I get the full description? extractFullDetails: true. It is slower and costs more, but it also gives you applyUrl, which is the strongest signal for spotting the same job on two boards.

Are salaries comparable across offers? Within one currency, yes — that is what salaryMinMonthly is for. Across currencies, no: the boards quote some IT offers in EUR, USD or CHF and no exchange rate is applied. The currency is always on the record.


Public listing data only. No logins, no paywalls, no private profiles. Offer enumeration uses each site's own published sitemaps. praca.gov.pl publishes employer contact details, which for sole traders is personal data — if you use those fields, that is your GDPR responsibility, so keep them out of anything you redistribute.


🛠️ Something missing?

Another board, a different filter, a webhook into your ATS or a Slack alert on every new match — open an issue on the Actor page.