SEEK Job Listings $0.99/1K — JobsDB Jobs, New Zealand Jobs avatar

SEEK Job Listings $0.99/1K — JobsDB Jobs, New Zealand Jobs

Pricing

from $0.99 / 1,000 jobs

Go to Apify Store
SEEK Job Listings $0.99/1K — JobsDB Jobs, New Zealand Jobs

SEEK Job Listings $0.99/1K — JobsDB Jobs, New Zealand Jobs

$0.99/1K | 8 job markets — SEEK AU & NZ, JobStreet MY/SG/PH/ID, JobsDB HK/TH. The public API stops at 500; this goes past it. Australia job listings, Australian jobs, Hong Kong jobs, Philippines jobs, JobStreet job listings, JobsDB job listings, APAC job listings. Brisbane jobs, Gold Coast jobs.

Pricing

from $0.99 / 1,000 jobs

Rating

0.0

(0)

Developer

Snow Leo Data

Snow Leo Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

SEEK, JobStreet & JobsDB Scraper — eight markets, past the 500 cap

$0.99 per 1,000 jobs. One Actor for the whole SEEK family: SEEK Australia and New Zealand, JobStreet Malaysia, Singapore, the Philippines and Indonesia, JobsDB Hong Kong and Thailand.

8 markets in one runall served by the same API — elsewhere on this store they are three separate Actors
The public API stops at 500 jobs per querymeasured: page 5 at 100 rows is the last with data, page 6 returns an empty list
This Actor goes past itmeasured: 1,500 unique jobs in 120 s from a single run, zero duplicates
41 fieldssalary parsed into numbers, address split into suburb/city/state/postcode, contacts pulled out of the advert body
$0.99 / 1,000the separate SEEK and JobStreet Actors on this store charge $2.00

The 500 wall

Ask the search API for page 6 at 100 rows per page and it hands back an empty list. So one query can never give you more than 500 jobs, whatever limit you set — and a market like Australia has far more than that.

This Actor splits a wide search until every slice fits under the wall. How the split is chosen, and why slices may overlap, is set out further down.

Who this is for

Recruiters and agencies across APAC. One run, eight markets, one bill. A search for nurse jobs covers Sydney and Kuala Lumpur. Turn on Only jobs not seen before and a daily schedule charges you only for what appeared since yesterday.

Job boards and aggregators. Every row carries the full advert text and a share link, so a listing can be republished without a second fetch.

Pay research. salary_label is the employer's own wording, never inferred. Adverts that state no pay come back empty rather than guessed.

Quick start

Press Start. With no search term you get a 300-job sample of the newest Australian listings.

{
"query": "software engineer",
"markets": ["AU-Main", "NZ-Main"],
"where": "Sydney",
"salaryMin": 100000,
"postedWithinDays": 7,
"maxItems": 0
}

What the SEEK job board actually carries

The SEEK job board is the front door to the Australian job market, and SEEK job ads are what this Actor collects — SEEK job data as rows: title, employer, the advert body and the pay field as the employer filled it in. Australian job listings are the bulk of any wide run, and Australian employers from mining to aged care post here rather than on a careers site of their own, so Australian vacancies that never reach an ATS are still in reach, Perth jobs and Canberra jobs included.

The same API serves the rest of the family. The JobStreet job board covers the Singapore job market and the Malaysia job market, and the JobsDB job board covers Hong Kong and Thailand — eight markets, one run, one bill. Hong Kong jobs data and Thai adverts land in the same 41 columns as an Australian row, so nothing has to be reconciled afterwards.

SEEK salary data is the field most scrapers get wrong. salary_label is the employer's own wording taken from the job detail record, the numeric bounds are parsed out of it, and an advert that states no pay comes back empty rather than guessed.

Asia Pacific jobs arrive in one schema: the same 41 fields whether the row came from SEEK, JobStreet or JobsDB, so the eight markets merge without a mapping step of your own.

Australia hiring data is what a wide run adds up to — who is advertising, for what, where and at what pay. The raw job ads data is kept as the employer wrote it, headline, body and bullet points, and the dataset a run leaves behind is a vacancy data API you read by URL rather than a file you have to move. Each row keeps the job ad text as the employer published it, so a job listings feed built from a run reads the way the advert did. Recruitment agency data comes with it: where an agency placed the advert, the agency is the company value.

Finding an industry in the data

SEEK publishes no taxonomy endpoint — /classifications answers 404 — so this Actor reads the tree back out of live results. Job classification data therefore lands on every row as classification and sub_classification, and the name SEEK uses is rarely the name you would have searched for:

What you are afterclassification on the row
Retail jobs dataRetail & Consumer Products
Teaching jobs dataEducation & Training
Driver jobs data, warehouse jobs dataManufacturing, Transport & Logistics
Construction jobs dataConstruction
Accounting jobs dataAccounting
Mining jobs dataMining, Resources & Energy
Healthcare jobs data, nursing and aged careHealthcare & Medical
Engineering jobs dataEngineering
Sales jobs dataSales

Twenty-seven top-level classifications came back on the day this page was last updated. Pass the ones you want into Classifications and the walk splits that branch alone, which costs less than filtering a wide run afterwards.

work_types carries SEEK's own employment terms, so contract jobs data, casual and part-time rows separate without anyone reading the advert; part time jobs data is that field, not a keyword guess. Salary range data is two numbers with a currency and a period, parsed from the employer's own line and left empty when there was no line. Recruiter contact data — emails, phones and links — is pulled out of the advert body, because most employers put the contact in the text and not in a field, which makes it employer contact data you can act on without opening the advert.

One endpoint serves all eight markets: the SEEK jobs API behind seek.com.au also answers for JobStreet and JobsDB, so JobStreet job listings and Singapore jobs data come back in the same 41 fields as an Australia jobs API call, and Asia job listings need no second Actor.

Fields

41 per row. The ones that do the work:

FieldWhat it is
title, teaser, bullet_pointsheadline, one-line summary, the advert's own highlights
company, company_logoemployer and logo
location_suburb, location_city, location_state, location_postcodethe address split up, so you can filter by state instead of matching strings
salary_min, salary_max, salary_currency, salary_periodpay parsed into numbers — $120,000 – $150,000 per year becomes 120000, 150000, AUD, year
salary_labeland the employer's own wording, kept as published
work_types, work_arrangementFull time / Contract, and On-site / Hybrid / Remote
classification, sub_classificationSEEK's own category tree
description_text, description_markdown, description_htmlthe full advert in three forms; pick one to keep rows small
contact_emails, contact_phones, contact_urlspulled out of the advert body — most employers put the contact in the text, not in a field
listed_at, posted_days_ago, expires_atwhen it appeared and when it comes down
change_typein incremental runs: NEW, UPDATED or UNCHANGED
urlstraight to the listing

Empty means the employer did not publish it. A salary line with no figure in it comes back with no currency either — an invented AUD would be data that was never there.

Built for monitoring, and for agents

onlyNew remembers what it delivered in a named store that survives between runs, and marks every row NEW, UPDATED or UNCHANGED. Unchanged jobs are not returned at all unless you ask for them with emitUnchanged — you already paid for those once. An advert counts as updated when its title, company, salary or the first 2,000 characters of its body change; invisible edits further down do not churn your bill.

compactOutput returns 18 core fields and no advert body — the shape an LLM pipeline actually consumes. excludeEmptyFields drops what the employer left blank instead of handing you twenty nulls. descriptionFormat keeps one of text, markdown or HTML rather than all three, which is most of a row's weight.

Salary, and why most scrapers get it wrong

SEEK's salaryLabel is a free-text field, not a salary. Employers put whatever they like in it. Real examples pulled from the live API:

"$120,000 – $140,000 per year" a salary
"$55 - $65 per hour + super" a salary
"Suited to detail-oriented engineers with strong QA" marketing copy
"Weekly pay cycle" not a figure
"Attractive package" not a figure

Copy that field into a salaryText column and a third of your "salaries" are advertising slogans. This Actor parses it and fills salary_min, salary_max, salary_currency and salary_period only when there is actually money in the string. A line with no figure comes back with no currency and no period — never a plausible-looking guess.

Currency falls back to the market's own (AUD on the Australian site, MYR on the Malaysian one) when the employer wrote only $. An explicit code in the string always wins.

What this Actor does not have

The competing SEEK Actor also exposes applicant counts per listing. That number is not reachable through the public search API or the job detail query — probed, not assumed — so this Actor does not pretend to have it. It also does not send Telegram or Slack notifications; use Apify's own integrations for that.

Remote is a field, not a word

remoteOnly uses SEEK's own work-arrangement value. Searching the advert text for "remote" instead would match "no remote work" and "remote location" just as happily — tests/test_logic.py keeps a trap job for exactly that case.

Filters run before you are charged

Keywords, exclusions, location, classification, work type, remote, posting age, pay-stated-only, employer allow and block lists. A row a filter removes never reaches the dataset and never appears on the bill. Dataset views can only choose columns — no view on Apify can drop rows — so filtering lives in the input, where it is also cheaper.

maxItems counts what you are given, not what was looked at. Ask for 30 construction jobs and you get 30 construction jobs: the Actor keeps walking the source until your filters have passed that many rows, and the industries you pick are handed to SEEK as a search constraint rather than being matched afterwards. Filters work the same whether or not you ask for job descriptions.

How the split works

Why a limit setting cannot help you

The 500 ceiling belongs to the source, not to your input. maxItems says how many rows you want; it cannot make the API hand over page 6. A scraper that issues one query and pages through it stops at 500 rows whatever the input says.

What the Actor does instead

It asks how many jobs match, and if the answer is over the wall the search is cut into slices that each fit under it:

  1. By classification — SEEK's own category tree, read from the results of your own search rather than a hard-coded list, because every market files jobs differently.
  2. By location — state and metro names, again taken from the results.
  3. By salary band — under 40k, 40–70k, 70–100k, 100–150k, 150k and up.
  4. By posting age — 1, 3, 7, 14 and 31 days.

Each step runs only when the one before it leaves a slice still too large.

walk: 620 unique jobs in 85 s, 28 queries, split by classification, where, salaryrange

The run report shows exactly which splits were used.

Why slices are allowed to overlap

A job in Sydney that pays $120,000 appears in both the location slice and the salary slice. Rather than building a perfect partition — impossible when the taxonomy is not published — the walk deduplicates by listing id as it goes. You are charged once per job.

Eight markets, one run

Which sites these actually are

MarketSiteCountry
SEEK Australiaseek.com.auAU
SEEK New Zealandseek.co.nzNZ
JobStreet Malaysiajobstreet.com.myMY
JobStreet Singaporejobstreet.com.sgSG
JobStreet Philippinesjobstreet.com.phPH
JobStreet Indonesiajobstreet.co.idID
JobsDB Hong Konghk.jobsdb.comHK
JobsDB Thailandth.jobsdb.comTH

They share one search API, which is why one Actor covers them all. Elsewhere on this store they are sold as three separate Actors.

Six of the eight are Southeast Asia and Hong Kong — Malaysia, Singapore, the Philippines, Indonesia, Thailand and Hong Kong — so a Southeast Asia jobs dataset and an Australia one come out of the same run with the same 41 columns, rather than out of three scrapers you then have to reconcile.

Which endpoint is being read

The list comes from SEEK's own jobsearch v5 endpoint — the same /api/jobsearch/v5/search call the site's search page makes in your browser — and the advert body from the site's GraphQL job view. That is why the fields are the site's own: work type, classification, work arrangement and the advertised salary range arrive as SEEK stores them, not as guesses from the advert text. The seek jobsearch v5 response is JSON, so there is no HTML parsing and no headless browser anywhere in the run.

Running several at once

Pick as many as you like in Markets. The Actor walks them one after another and stops when your limit is reached, so a limit of 500 across two markets does not mean 500 from each. Every row carries market and country_code, so a combined run stays sortable.

Fields in detail

Pay, parsed and preserved

salary_label keeps the employer's exact wording. salary_min, salary_max, salary_currency and salary_period are filled only when that wording actually contains money. See the section above on why that distinction matters more here than on most boards.

Addresses split up

location is the label as SEEK prints it — Doncaster East, Melbourne VIC. Alongside it you get location_suburb, location_city, location_state and location_postcode, so filtering by state is a comparison rather than a string match.

Contacts out of the advert body

Many employers, and almost every recruitment agency, put an email or a phone number in the advert text rather than in a field. contact_emails, contact_phones and contact_urls collect what is there. Measured on 300 live rows: 99 carried at least one contact.

Running it on a schedule

Paying only for what changed

Only jobs not seen before keeps delivered ids in a named key-value store that survives between runs. Every row then carries change_type:

  • NEW — not seen in any previous run.
  • UPDATED — seen before, but the title, company, salary or the first 2,000 characters of the body changed.
  • UNCHANGED — seen before and identical; not returned unless you ask.

Invisible edits deeper in the advert do not churn your bill, which is the point of hashing only the part a reader would notice.

Where the output can go

The run page hands you the dataset as JSON, CSV, XML or Excel, which is the whole workflow for a one-off market map. Anything repeatable goes through the API instead:

https://api.apify.com/v2/datasets/<datasetId>/items?format=csv&clean=true

You do not have to track dataset ids. This address always answers with the last finished run of the Actor, which is what a nightly job wants:

https://api.apify.com/v2/acts/<actorId>/runs/last/dataset/items?format=json

No-code pipelines, and the webhook underneath them

Apify maintains a connector for the tools most buyers already run — Make, n8n, Zapier and Slack — and a webhook for everything else: when a run finishes, Apify calls your URL with the run id and the dataset id in the payload.

In Make and n8n the scenario is the same two blocks every time: run the Actor, then read its dataset. Name the storage if the pipeline depends on it — unnamed storages on Apify are temporary by design, named ones stay.

Three things people build with this

A candidate feed for one market. Pick a single market, set the search term and the salary floor, schedule it daily with the delta switch on, and new adverts land in Slack or a sheet with the apply link already attached.

Pay benchmarking across eight markets. Run the same search term against SEEK, JobStreet and JobsDB, keep only the adverts that state pay, and you have a salary distribution per country that nobody had to guess at.

A regional job board. Every row carries the advert body as text or HTML, the location broken up, the work type and the direct apply URL, so a listing can be republished without a second fetch.

Questions people ask

How many jobs can one run return?

As many as your limit allows. The 500 ceiling applies to a single query, not to a run. A measured run returned 1,500 unique jobs in 120 seconds across a single market, and the walk continues until the search is exhausted or your limit is.

Why do so few jobs have a salary?

Because most advertisers do not publish one, and because SEEK's salary field is free text that often holds marketing copy instead of pay. Measured on 300 live rows: 65 had a real figure. This Actor reports what is there and nothing more.

What is the difference between Remote only and searching for "remote"?

remoteOnly reads SEEK's own work-arrangement value. A text search for the word also matches "no remote work" and "remote location", which is why this Actor keeps a deliberately trapped job in its test suite for that exact case.

How do I get only new jobs each morning?

Turn on Only jobs not seen before and schedule the Actor daily. The first run fills the memory; every run after that returns and bills for new or changed listings only.

What does a first run cost?

The form starts at 300 jobs, which is under thirty cents. With no search term at all the Actor treats the run as a trial and keeps it to that same starting size, so an accidental click cannot pull a whole market.

Can I feed this straight into an LLM?

Turn on Compact output for 18 core fields with no advert body, and Drop empty fields to leave out what the employer never filled in. Set Description format to markdown if you do want the body in a form a model reads cleanly.

Does it need proxies?

No. It calls the same public endpoints the sites' own search pages call, keeps a shared per-host pace and backs off when asked. Nothing in the run depends on a proxy or a browser, which is why it is fast and why it costs what it costs.

A run succeeded but the dataset is empty — is that a failure?

Not necessarily, and the Actor always says which case it is. The run report carries empty_reason and the log carries a warning naming it: nothing-changed (the incremental mode working as asked — every job had already been delivered by an earlier run, so nothing was written and nothing was charged), filtered-out (your filters removed everything that was collected), source-returned-nothing (the search matched no jobs on that market at all), or charge-limit-reached (the run's spending limit was already used up). A silent empty dataset is treated as a defect, not as a normal outcome: tests assert that light runs with any single filter return rows.

A number this README deliberately does not quote

SEEK's totalCount looks like "how many jobs exist", and it is not: the same query returns 182,021 at the default page size, 168,267 at 100 rows and 17,153 at 1 row, and it drifts between identical calls. So this README never states a site total — only jobs this Actor actually collected and counted. tests/test_live.py asserts that the field is unreliable, so nobody is tempted to quote it later.

Reproducing the numbers

python3 tests/run_all.py --live

Every figure above comes from that run.