# SEEK Jobs Scraper - Australia & New Zealand (`amitaviv/seek-jobs-scraper`) Actor

Scrape SEEK job ads in Australia and New Zealand by keyword, filters or pasted search URL: company, location, salary text and parsed range, work type, listing date, optional full description. Goes past SEEK's 500-result cap with a coverage report. Only-new mode. $0.80 per 1,000 jobs on Bronze.

- **URL**: https://apify.com/amitaviv/seek-jobs-scraper.md
- **Developed by:** [Amit Aviv](https://apify.com/amitaviv) (community)
- **Categories:** Jobs, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.64 / 1,000 job delivered ($0.80 per 1,000 on bronze)s

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## SEEK Jobs Scraper (Australia & New Zealand)

Job ads from seek.com.au and seek.co.nz, from keywords with filters or from search URLs pasted from your browser: title, company, location, salary as written and as numbers, work type, classification, listing date and, if you ask for it, the full description. It reads SEEK's JSON search service over plain HTTP, with no browser, so a search without descriptions takes seconds; you pay only for delivered jobs, and a run that delivers nothing because of an error ends as Failed, never as a green empty run.

Two things it is built for:

- **More than SEEK's 500 results per search.** SEEK serves only the first 500 results of any search. Ask for more and the actor also runs narrower searches (classification, sub-classification, state, work type, salary band), removes the duplicates and tells you in the run summary how well they covered the search, checked against a sample.
- **Company and job data, not people's contact details.** SEEK's recruiter fields and advertiser phone number are never read, and passages of ad text that give an e-mail address, a phone number or a named contact person are replaced by `[contact details removed]` where the filter detects them.

### What you get

- Job: id, URL, title, teaser, bullet points, listing date, featured flag
- Company: name, SEEK advertiser id, SEEK company page, logo
- Place: location, area, country
- Classification and sub-classification with SEEK's ids, work types, work arrangement (on-site, hybrid, remote)
- Salary: the text as the advertiser wrote it, plus minimum, maximum, period and currency when the text is clearly an amount
- With **Include full description**: description as HTML and plain text, expiry date, verified-advertiser flag, and from the company's SEEK profile industry, size, website, rating and number of reviews
- One record per job ad, as JSON, CSV or Excel, or through the API.
- A run summary (`OUTPUT_SUMMARY` in the run's key-value store): what was requested, delivered and charged, how SEEK understood your location, the coverage of each search, which inputs failed or were ignored, and why the run stopped.

What it does not return: SEEK's recruiter and phone fields, applicant counts, anything behind a login, and other SEEK-group sites (Jobstreet, JobsDB). E-mail addresses, phone numbers and named contact persons inside ad text are removed where detected; see "Limits" for what the filter misses.

### How to use

1. Type keywords (for example `data analyst`), or search on seek.com.au / seek.co.nz, set the filters you want there and copy the URL from the browser.
2. Paste into **Keywords or SEEK search URLs**. Several entries are fine; each is one search.
3. Set **Max jobs per search** and press Start.
4. Download the results from the Output tab, or read them from the API.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `queries` | list of text | none | Keywords or SEEK search URLs. Duplicates are ignored. Entries from other sites, single job pages, the home page, and URLs with a path part, a parameter or a filter value the actor does not understand (for example `daterange=abc`, an unknown work type or classification id, two locations) are skipped and listed in the summary under `ignoredInputs` with the reason, so a wider search than you asked for is not run. May be empty if `location` or `classification` is set. For a whole country use the URL `https://www.seek.com.au/jobs`. |
| `location` | text | empty | For keywords. A place as SEEK names it, e.g. `All Sydney NSW`, `All Melbourne VIC`, `Queensland QLD`, `All Auckland`. A city name without "All" means the central suburb and nearby areas, slightly fewer jobs (`Melbourne VIC` was understood as "Melbourne VIC 3000": 1,514 accountant jobs against 1,547 for `All Melbourne VIC`). The summary shows how SEEK understood it (`resolvedLocation`) and carries a warning when SEEK read a name without "All" or a postcode as a single suburb (`Wellington` in New Zealand: 116 nurse jobs, `All Wellington`: 158; country `AU` with `Auckland` gave "New Auckland QLD 4680"). |
| `country` | `AU` or `NZ` | `AU` | For keywords. |
| `classification` | choice | any | For keywords. One of SEEK's 30 classifications, by name or id. |
| `workType` | choice | any | For keywords: `full-time`, `part-time`, `contract`, `casual`. |
| `workArrangement` | choice | any | For keywords: `on-site`, `hybrid`, `remote`. |
| `salaryMin`, `salaryMax` | number | empty | For keywords: SEEK's own annual salary filter. |
| `dateRange` | number | empty | For keywords: SEEK's date filter in calendar days of Sydney time. `1` = listed today (since midnight in Sydney), `2` = today and yesterday. For a daily schedule use `2` or `3`: at 01:15 Sydney time `1` matched 43 jobs in all Australia and `2` matched 17,430. |
| `maxItemsPerQuery` | number | 100 | Stop after this many jobs for each search. Up to 500 they are the search's newest, except that SEEK puts up to two featured ads at the top of each page of 100 (`isFeatured: true`). With a maximum above 500 on a search that has more, the first 500 are the newest and the rest come from the narrower searches, classification by classification, each newest first: if the maximum is below the search's total, what you get past 500 is not a cut by date. A search can deliver slightly fewer than the maximum when some of its jobs were already delivered by an earlier search in the same run. Above 500 narrower searches are added (see "Past SEEK's 500-result cap"). `maxResults` and `maxItems` are accepted as other names for it. |
| `includeDescription` | yes/no | no | Full description and company profile facts: one more request per job (about 4 to 5 jobs per second) and one `job-detail` charge per job delivered with a description. |
| `onlyNew` | yes/no | no | Monitoring: return and charge only jobs that earlier runs have not delivered. Read "Monitoring" before scheduling it. |
| `monitorKey` | text | empty | Name of the monitor memory, to keep two schedules apart. |
| `proxyConfiguration` | proxy | off | Not needed in our tests. Turn on Apify Proxy, or give your own proxy URLs, only if your runs fail as blocked. |

A URL carries its own filters; the filter fields above apply to keywords only (if you set them next to nothing but URLs, the summary carries a warning). URL filters understood: keywords, location, classification and sub-classification, one or several work types, work arrangement, salary range, date range, distance, company name and advertiser id. Input that was run (run `emKzcT5UqCFb7eX7w`):

```json
{
  "queries": ["https://www.seek.com.au/data-analyst-jobs/in-All-Sydney-NSW?daterange=7"],
  "includeDescription": true,
  "maxItemsPerQuery": 60
}
```

### Example result

Taken from run `emKzcT5UqCFb7eX7w` on 2026-10-13, unedited:

```json
{
  "id": "95223691",
  "url": "https://www.seek.com.au/job/95223691",
  "title": "Technical Business Analyst",
  "company": "Ignite",
  "advertiserId": "22869919",
  "companyUrl": "https://au.seek.com/companies/ignite-1223788",
  "companyLogoUrl": "https://bx-branding-gateway.cloud.seek.com.au/e85b0d7d-80e8-4932-8423-71513ea012a6.1/jdpLogo",
  "location": "Sydney NSW",
  "area": "All Sydney NSW",
  "country": "AU",
  "classification": "Information & Communication Technology",
  "classificationId": "6281",
  "subClassification": "Business/Systems Analysts",
  "subClassificationId": "6283",
  "workTypes": [
    "Contract/Temp"
  ],
  "workArrangement": "Hybrid",
  "salaryText": "AUD 1000 - 1100 per day",
  "salaryMin": 1000,
  "salaryMax": 1100,
  "salaryPeriod": "day",
  "salaryCurrency": "AUD",
  "listedAt": "2026-10-13T01:28:06Z",
  "teaser": "12-Month Contract with Extension Prospects   Hybrid Work Model | 2 Days in Office   Sydney NSW      About the Role :     We are seeking an",
  "bulletPoints": [
    "12-Month Contract",
    "Government Client",
    "Hybrid Opportunity"
  ],
  "isFeatured": false,
  "descriptionHtml": "<strong>12-Month Contract with Extension Prospects</strong><br><strong>Hybrid Work Model | 2 Days in Office</strong><br><strong>Sydney NSW</strong><br> <br> <strong>About the Role :</strong><br> <br> We are seeking an experienced <strong>Technical Business Analyst</strong> to support the delivery of critical infrastructure and telecommunications programs within a government environment.<br> <strong><br> Key Responsibilities:</strong><br><ul><li>Elicit, analyse, and document business and <strong>technical requirements</strong></li><li>Manage requirements traceability throughout the project lifecycle</li><li><strong>Collaborate</strong> with stakeholders, architects, suppliers, and delivery teams</li><li>Support solution design, testing, and implementation activities</li><li>Analyse <strong>network</strong>, <strong>system</strong>, and <strong>business processes</strong> to identify risks and improvements</li><li>Assist with procurement and integration of technology solutions<strong>&nbsp;</strong></li></ul><strong>Skills and Experience Required:&nbsp;</strong><br><ul><li>Proven experience as a <strong>Business Analyst</strong> or <strong>Systems Analyst</strong> within technology, telecommunications, or critical infrastructure environments</li><li>Strong understanding of <strong>network technologies, backend systems, and integrations</strong></li><li>Experience supporting<strong> large-scale telecommunications</strong> and mission-critical environments</li><li>Excellent stakeholder engagement and communication skills</li><li>Self-motivated, proactive, and comfortable working in complex technical environments</li><li>Government experience is desirable<br>&nbsp;</li></ul>Rates on offer will be commensurate with your skills and experience. Please note that a Baseline AGSVA Security Clearance is required for this opportunity.<br><br>[contact details removed]<br><br><br>Ignite is Australia's preferred partner in recruitment and technology solutions across private and public sectors.",
  "descriptionText": "12-Month Contract with Extension Prospects\nHybrid Work Model | 2 Days in Office\nSydney NSW\n\nAbout the Role :\n\nWe are seeking an experienced Technical Business Analyst to support the delivery of critical infrastructure and telecommunications programs within a government environment.\n\nKey Responsibilities:\n- Elicit, analyse, and document business and technical requirements\n- Manage requirements traceability throughout the project lifecycle\n- Collaborate with stakeholders, architects, suppliers, and delivery teams\n- Support solution design, testing, and implementation activities\n- Analyse network, system, and business processes to identify risks and improvements\n- Assist with procurement and integration of technology solutions\n\nSkills and Experience Required:\n- Proven experience as a Business Analyst or Systems Analyst within technology, telecommunications, or critical infrastructure environments\n- Strong understanding of network technologies, backend systems, and integrations\n- Experience supporting large-scale telecommunications and mission-critical environments\n- Excellent stakeholder engagement and communication skills\n- Self-motivated, proactive, and comfortable working in complex technical environments\n- Government experience is desirable\n\nRates on offer will be commensurate with your skills and experience. Please note that a Baseline AGSVA Security Clearance is required for this opportunity.\n\n[contact details removed]\n\nIgnite is Australia's preferred partner in recruitment and technology solutions across private and public sectors.",
  "expiresAt": "2026-11-12T12:59:59.999Z",
  "advertiserVerified": true,
  "companyIndustry": "Employment Services",
  "companySize": null,
  "companyWebsite": null,
  "companyRating": null,
  "companyReviewCount": null,
  "query": "https://www.seek.com.au/data-analyst-jobs/in-All-Sydney-NSW?daterange=7",
  "scrapedAt": "2026-10-13T15:21:57.782Z"
}
```

| Field | Meaning |
|---|---|
| `id`, `url` | SEEK's job id (stable between runs) and the ad's page |
| `company`, `advertiserId` | Company name as shown in SEEK's search results (an employer or a recruitment agency) and SEEK's id for the advertiser. The ad's own page can show a longer legal or branch name ("Adecco" in search results, "Adecco Australia Pty Ltd" on the ad): 4 of 10 records compared in a review differed this way. |
| `companyUrl`, `companyLogoUrl` | The company's SEEK page and logo, when it has them |
| `location`, `area`, `country` | Place as shown, SEEK's area name, `AU` or `NZ` |
| `classification*`, `subClassification*` | SEEK's job category names and ids |
| `workTypes`, `workArrangement` | e.g. `["Full time"]`; `On-site`, `Hybrid` or `Remote` |
| `title` | The ad's title (passes the contact filter like all ad text) |
| `salaryText` | Pay as the advertiser wrote it; `null` when the ad shows none (just over half of the ads in our sample) or when the field held only contact details |
| `salaryMin`, `salaryMax`, `salaryPeriod`, `salaryCurrency` | Parsed from `salaryText` when it is clearly an amount (`hour`, `day`, `week`, `month`, `year`); otherwise all four are `null`. In a 2,172-job sample 996 ads showed a salary text and about 800 were parsed. |
| `listedAt` | When the ad was listed (UTC) |
| `teaser`, `bulletPoints` | The short texts shown in search results |
| `descriptionHtml`, `descriptionText` | Only with **Include full description**, otherwise `null`. Also `null` (and not charged) in the rare case that nothing but contact details was left of the text |
| `expiresAt`, `advertiserVerified` | Only with **Include full description**, otherwise `null` |
| `companyIndustry`, `companySize`, `companyWebsite`, `companyRating`, `companyReviewCount` | Only with **Include full description**, and only for companies with a SEEK profile |
| `query`, `scrapedAt` | The search that found the job, and when |

Fields SEEK does not provide for a given job are `null`, never missing. `advertiserId` is `null` for ads placed as "Private Advertiser".

### Pricing

Pay per event. You pay for what is delivered, not for time or memory.

| Event | Free | Bronze | Silver | Gold and above |
|---|---|---|---|---|
| Actor start (once per run) | $0.0036 | $0.003 | $0.0027 | $0.0024 |
| Job delivered (`job`) | $0.00096 | $0.0008 | $0.00072 | $0.00064 |
| Full description (`job-detail`), per job delivered with a description, only when asked for | $0.00084 | $0.0007 | $0.00063 | $0.00056 |

Worked examples on the Bronze plan:

- 500 jobs without descriptions: 500 × $0.0008 + $0.003 = **$0.403**.

- The same 500 jobs with full descriptions: 500 × ($0.0008 + $0.0007) + $0.003 = **$0.753**.

- A scheduled monitoring run that finds 12 new jobs: 12 × $0.0008 + $0.003 = **$0.0126**. A run that finds nothing new: **$0.003**.

- Jobs that are not delivered are not charged. A job found by two of your searches, or listed twice by SEEK (sponsored ads are), is delivered and charged once.

- With descriptions on, an ad that SEEK took down between the search and the description request is not delivered and not charged. An ad whose description cannot be delivered is delivered without it and without the `job-detail` charge.

- The start event is charged for every run, including runs that fail on bad input, and again when a run is resurrected.

- The maximum total charge you set for a run is respected: the actor stops before it would go over, keeps what was delivered, and says so in the status message. (Run `VLhRZuUbOuH4eJzQc`: limit $0.0105 with descriptions gave exactly 5 jobs = $0.003 + 5 × $0.0015.)

### Past SEEK's 500-result cap

SEEK returns at most 500 results for one search, whatever the search matches. When `maxItemsPerQuery` is above 500 and the search has more, the actor first collects the search's own first 500 results (newest first, with SEEK's featured ads at the top of each page), then asks SEEK how many results narrower searches have and splits until each part fits: by classification, then sub-classification, then state (Australia), then work type, then annual salary band. Jobs that turn up more than once are delivered once. The parts are collected in classification order, so a run that stops at your maximum before the last part holds the newest 500 jobs plus the jobs of the first classifications, not the newest N jobs (run `KXYPsrM4fFb8gGNEn`: `electrician` in Perth, 1,392 results, maximum 1,000).

How complete is the result? It depends on the search:

- **Searches without keywords** (a location, a classification, a whole state) split cleanly in our runs. Every job in Tasmania (run `Hc4jKcRduXPb8cAwE`): SEEK count 2,398, 30 parts, 2,169 distinct jobs in 51 seconds; all 497 jobs of the sample were found again by the parts, `complete: true`. Every job in Hobart (run `hPuLG5a6Rfzee7vNS`): 1,021 jobs, sample 498, none missed. A gap in SEEK's counts can still set `complete: false` on such a search (Administration & Office Support in Western Australia, run `xgVh9biRUlUZ7Xedi`: sample 495, none missed, `notCovered: 10`).
- **Keyword searches are approximate.** SEEK matches keywords differently once a classification filter is added, so the narrower searches miss a few percent of the jobs the plain search lists, and include some related jobs the plain search does not list. `accountant` in Melbourne (run `2afwsjNGZsWpsesUE`): 1,400 jobs delivered; of the 496 newest jobs of the plain search the parts had missed 20 (4%), so `complete: false`, with 36 older jobs estimated missing from the sample. `teacher` in New Zealand (run `fyzp7d1YFbVdJECvM`): 782 jobs, 17 of 496 missed. The missed jobs of the sample are delivered, because the plain search's own results always are.

The run summary reports this for each search under `completeness`:

| Field | Meaning |
|---|---|
| `complete` | `true`: every part was collected to its end, the counts add up and the sample check found nothing missing. `false`: something is or may be missing, see the next fields; the status message then says "not fully covered". `null`: the search stopped early (your maximum, charge limit or timeout), so nothing is claimed. |
| `siteCount` | SEEK's own count for the search. It counts sponsored ads twice, so it runs about 5 to 11% above the number of distinct jobs. |
| `uniqueFound` | Distinct jobs found |
| `split`, `tiles`, `countRequests`, `searchPages` | Whether the search was split, into how many parts, and the requests made |
| `sampleChecked`, `sampleMissing` | Split searches that ran to the end: how many of the plain search's newest results (up to 500) were looked for in the parts, and how many the parts did not contain. Those are delivered anyway; the share tells you what to expect among the older jobs that only the parts reach. |
| `estimatedMissing` | A lower bound, not a count: the larger of that share applied to the jobs beyond the sample and `notCovered`. In a review of `accountant` in Melbourne the sample estimate was 33 and the count gap 41, and an independent enumeration found at least 48 jobs missing. |
| `truncated`, `truncatedTiles` | Parts that still had more than 500 results after every split, with their filters |
| `notCovered` | The amount by which a search's count exceeds the sum of its parts (for example ads without a state). An estimate that can understate; trust the sample check more. |

Also measured: all Information & Communication Technology jobs in Australia (run `MQFIXyo2cqfBRWAs0`, earlier build): 6,357 jobs, `complete: false`. A whole-country snapshot (about 168,000 results) has not been run.

### Reliability and failure behaviour

| What happened | Run status | Status message |
|---|---|---|
| Jobs delivered | Succeeded | `Scraped 200 jobs from 1 query` |
| SEEK has no jobs for your search (or does not know the location) | Succeeded | `0 results: SEEK has no jobs for this query` |
| Some searches worked, some failed | Succeeded | `Scraped 120 jobs from 2 queries — 1 of 2 queries failed ("…": …)` |
| A search was not, or may not be, fully covered | Succeeded | `… — not fully covered (see completeness in OUTPUT_SUMMARY)` |
| A proxy of your own that does not work | **Failed** | `No jobs delivered: network error (ECONNREFUSED). No results were charged.` |
| Nothing delivered because of errors or blocking | **Failed** | `No jobs delivered: SEEK blocked every attempt; try again later. No results were charged.` |
| Input the actor cannot use | **Failed**, within seconds | `Nothing to do: … Add at least one entry to "queries", e.g. …` |
| Maximum charge reached | Succeeded | `… — stopped at the maximum charge set for this run` |
| Run timeout close | Succeeded | `… — stopped just before the run timeout; raise the timeout to get the rest` |

- Requests are retried up to 3 times with increasing pauses on network errors, HTTP 429 and 5xx.
- Descriptions are requested 2 at a time. SEEK limits that service: an earlier build that asked 4 at a time (about 8 per second) was refused with "too many requests" after about 1,400 descriptions, twice. At the present pace a run delivered 3,000 descriptions in 10.5 minutes without being limited (run `MddadKPK8tHEfX7Va`), and two such runs at the same time each passed 1,700 (`YwKgbSCzfvNtIOpJQ`, `zuURcgu4wDqk4Wq7N`). If SEEK does answer "too many requests", the actor pauses all description requests for 30 seconds, then 1, 2 and 4 minutes, and only then ends that search as blocked, keeping what it delivered. This pause was tested offline only: we did not provoke the limit again.
- Large description runs one after another can still be cut short. On 2026-10-13 a 2,000-description run started two minutes after another one (and next to a third run fetching 319 descriptions) had two requests time out after 1,593 jobs, each after 4 attempts of 30 seconds. It ended as Succeeded with `Scraped 1593 jobs from 1 query — 1 of 1 query failed ("…": network error (ETIMEDOUT))`, 1,593 jobs charged. Resurrected a few minutes later it delivered the remaining 407 without a retry and without charging any job twice (run `PEgkHpqpa85zQojYP`). So: read the status message, and resurrect or re-run a run that says a query failed.
- Jobs are saved page by page. A run that is aborted, times out or is resurrected keeps what was delivered and never delivers or charges the same job twice (run `FFbFdfzBGcvI08DFY`, with descriptions: aborted at 200 jobs, summary written, resurrected, finished with 450 jobs, 450 distinct, 450 `job` and 450 `job-detail` charged).
- Measured on 2026-10-13: about 130 test runs from Apify's servers, all but one without a proxy, made more than 20,000 requests to SEEK; apart from the description limit and the timeouts described above none was blocked and none needed a retry. That is one day's evidence, not a guarantee.

### Limits

- Australia and New Zealand only.
- SEEK's count and the number of distinct jobs differ (see `siteCount`), so "fewer than the site shows" is normal.
- SEEK's keyword search returns loose matches when sorted by date, which is the order this actor uses: `registered nurse` also returned "Relief General Manager - Victoria", and `accountant` a "2027 Economics Teacher". These are SEEK's own results; filter on `title` or `classification` if you need strict matches.
- Past 500 results, keyword searches are approximate (see "Past SEEK's 500-result cap").
- Parsed salary is cautious: free-text pay such as `1200/day` or `Competitive` stays in `salaryText` with the number fields `null`. Amounts are not converted between hour, day and year. Relocation payments, sign-on bonuses and allowances are not read as the salary. Of 1,857 different salary texts in our runs 1,482 (80%) were parsed; a review that read 45 random parsed ones found them correct, and the misreadings it found elsewhere ("PD allowance" read as a day rate, "$5K Relocation" read as the salary) are fixed.
- The contact filter is pattern based. What it found and did not find, measured on 2,000 delivered descriptions of jobs in Tasmania (run `FADbc4KGg16XrrzOl`): 921 (46%) had a passage replaced, and our scan found no e-mail address and no complete phone number left (SEEK itself masks some numbers, as in "02 •••• •19"; a masked number of a company's recruitment team stayed in one ad) in descriptions, teasers, titles, bullet points or salary texts. Names are harder. An independent review of the previous build found a contact person's name left standing next to the removed line in 92 of 2,000 Tasmanian descriptions (4.6%; mostly the Tasmanian Government's "Name: … / Position: … / Phone: …" block) and in 1 of 600 from other regions. This build removes those forms: lines such as "Name: …" and "Position: …" and lines that are a person's name, with or without a job title, around a removed contact line. Checked against the original text of the 121 ads concerned and of 11 more found in our own run, none kept such a name line; a fresh run of the final rules over Tasmania (run `PEgkHpqpa85zQojYP`, 2,000 descriptions) left no such line either, and one named person in running text ("send an email to …", a form added afterwards). What it does not catch: a business that carries its owner's name, a person named in running text away from any contact detail and without a phrase such as "contact …" or "call …" (for example "you will report to …", or the doctors a clinic lists), a name inside a longer sentence, and contact details written in a form it does not know. What it costs: the line or sentence around a contact goes with it, generic company addresses such as careers@… are removed too, and a short line next to a contact line that looks like a name (a company name of two or three words, for instance) can go as well. On 250 ads checked against the original text, the 31% that were touched kept 95% of their text on average (worst case 77%), and none lost its whole description. An ad typed as one paragraph without spaces after its full stops used to lose its whole description to a single address (seen once in 600); it now loses that sentence only.
- A location in a pasted URL or typed without "All" is passed to SEEK as text and can be read as one suburb and its surroundings (a URL ending `/in-Auckland` was understood as "Auckland CBD Auckland", `/in-Canterbury` as "Christchurch Central Canterbury"). The summary then carries a warning and `resolvedLocation`; `complete: true` refers to the location as SEEK understood it.
- Speed in our runs: 1,000 to 3,000 jobs per minute without descriptions (less for keyword searches that are split into many parts), about 285 per minute with descriptions (on purpose, see "Reliability").
- Only-new mode remembers up to 100,000 jobs per search; a search with more is refused with a message asking you to split it.

### Monitoring: only new jobs

1. Turn on **Only new jobs** and save the input as a task.
2. Narrow the search to recent ads: `dateRange` 2 or 3 for keywords, or `daterange=2` in a pasted URL. Not 1: that means "today in Sydney" and misses most of the previous day on a morning run.
3. Set **Max jobs per search** at least as high as the number of jobs the search lists in that period (up to 500 costs nothing extra; you pay only for new jobs).
4. Schedule the task (hourly or daily).

The first run delivers what it finds, up to the maximum. Every later run delivers and charges only jobs it has not delivered before for the same search (a job that two searches of the same input both find is delivered once and remembered for both); a run with nothing new ends as Succeeded with `0 new jobs in 1 monitored query (no results charged)` and costs the start event only.

**Mind the maximum.** "New" means "not delivered before", not "listed since the last run". If the search holds more jobs than your maximum, the next run pages past the known jobs and delivers older ads the earlier runs did not reach, up to the maximum again, until the 500 jobs SEEK serves are used up. Three runs of `nurse` with a maximum of 40, minutes apart, each delivered 40 "new" jobs (review runs `xAMH3r1SHg4nej4Is`, `EMzWdJgzQCEpe4ypi`, `4gycphYX119MXiZ7S`). Steps 2 and 3 above avoid this.

Measured (runs `4LdGpb45wryzVGtS1`, then `RniW6etDC745b8aCF` a minute later, same input: `project manager` in Brisbane, `dateRange` 2, descriptions on): 319 new jobs delivered and charged, then 0 with 5 requests and no description request. An earlier pair 12 minutes apart delivered 43 and then the 3 jobs listed in between (review runs `86NduNmvsbRJCnmBq`, `qdsljShLV2DRBrndW`).

The memory is a key-value store named `seek-jobs-scraper-monitor` in your own account (one record per search and monitor name). Delete the store or change **Monitor name** to start over. Jobs cut off by your charge limit are not marked as seen: the next run delivers them. With **Include full description** on, descriptions are requested only for new jobs. To be notified, add an integration to the task (e-mail, Slack, webhook) on "run succeeded".

### FAQ

**Do I need a proxy?** Not in our tests: runs from Apify's servers went through without one, and a run with Apify Proxy switched on also worked (`LQWs0iGoA3vpYfG8f`, earlier build). Leave it off unless your runs start failing as blocked.

**Why did I get fewer jobs than SEEK shows?** SEEK's count includes sponsored ads twice, and without splitting SEEK serves only the first 500 results. Raise **Max jobs per search** above 500 to get the rest, and read `completeness` in the summary.

**Why does the status say "not fully covered"?** Either you asked for at most 500 jobs of a search that has more, or a split search failed its sample check, which is usual for keyword searches. See "Past SEEK's 500-result cap".

**Why did `dateRange` 1 return so little?** It means "listed today, Sydney time". Use 2 or 3.

**Why is there no recruiter name or phone number?** By design. See "Legal and responsible use".

**Why are descriptions slower than the search?** Each needs its own request and SEEK limits that service, so the actor asks for 2 at a time.

**Can I run it from code?** Yes: the API, the Apify clients for JavaScript and Python, Make, Zapier, n8n. The Output tab shows the URLs.

**The status says a query failed with a network error. What now?** What was delivered is kept and charged once. Resurrect the run, or run it again later; see "Reliability".

**What happens when SEEK changes?** Runs fail loudly instead of returning empty data; report the run id under Issues.

### Legal and responsible use

This actor collects job advertisements that SEEK shows publicly without a login: data about jobs and the organisations advertising them. It is designed not to return people's contact data: the advertiser's phone number offered by SEEK is never read, recruiter profile data in the search results is not emitted, and lines or sentences of ad text (description, teaser, bullet points, salary text, title) containing an e-mail address, a phone number or a named contact person are replaced by `[contact details removed]`. This filter is automatic and not perfect: a person's name can remain in ad text, as described under "Limits". If you find personal data in the output, do not use it, and tell us.

SEEK's terms of use prohibit automated collection, and its robots.txt disallows the endpoints this actor reads. Job descriptions are the advertisers' copyright. You are responsible for how you use the data: check SEEK's terms, copyright and database rights, and privacy law (the Australian Privacy Act, the New Zealand Privacy Act, GDPR where it applies) for your use, in particular before republishing ad text. Do not use the output to contact individuals. The actor makes a modest number of requests at a time; please do not run many large snapshots in parallel. If you are unsure, ask a lawyer before you build on it.

### Related actors

- [Company Hiring Tracker](https://apify.com/amitaviv/company-hiring-tracker): follow named employers on their own job boards (Greenhouse, Lever, Ashby, Workday and five more), with open jobs per company and the roles opened or closed since your last run.
- [Welcome to the Jungle Scraper](https://apify.com/amitaviv/welcome-to-the-jungle-scraper): job ads from welcometothejungle.com (mostly France, also other European countries and the US) with full descriptions, plus a mode that returns one hiring-signals record per company.
- [Europages Company Scraper](https://apify.com/amitaviv/europages-company-scraper): European B2B companies by industry keyword and country (address, phone, website, company email), when you are building a list of employers to approach.

### Changelog

- 2026-10-13, build 0.1.15: only-new mode no longer delivers a job again when two searches of one input both find it (before, the next run delivered and charged such jobs once more); names of contact persons standing next to removed contact lines ("Name: …", a name with a job title) are removed; ads typed without spaces after full stops keep their text; filter values in pasted URLs are checked; your own proxy URLs are used; a warning when SEEK reads a location as a single suburb; salary parser corrections; `estimatedMissing` is now a lower bound.
- 2026-10-13, 0.1: first version. Descriptions are requested 2 at a time with pauses on "too many requests"; contact filter extended to salary text, titles, masked and obfuscated addresses, local numbers and named contacts; sample check for split searches; `job-detail` charged only when a description is delivered.

### Support

Open an issue on the actor's **Issues** tab with the run id.

# Actor input Schema

## `queries` (type: `array`):

Job keywords (e.g. data analyst) or search URLs copied from seek.com.au / seek.co.nz (e.g. https://www.seek.com.au/data-analyst-jobs/in-All-Sydney-NSW?daterange=7). Each entry is one search. The filters below apply to keywords only; a URL carries its own filters. A URL with a part or a filter value the actor does not understand is skipped and listed in the run summary with the reason, not run as a wider search. Leave empty to get every job of the location or classification chosen below; for a whole country use the URL https://www.seek.com.au/jobs.

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

For keyword searches. A place as SEEK names it, e.g. All Sydney NSW, All Melbourne VIC, Queensland QLD, All Auckland. A name without 'All' or a postcode is read by SEEK as one suburb and nearby areas, which can be clearly fewer jobs (Wellington: 116 nurse jobs, All Wellington: 158); the run summary then carries a warning. Leave empty for the whole country.

## `country` (type: `string`):

For keyword searches: which SEEK site to search. AU = seek.com.au (Australia), NZ = seek.co.nz (New Zealand). Default AU. A pasted search URL uses the site in the URL.

## `classification` (type: `string`):

For keyword searches: only jobs of this SEEK classification (industry), e.g. Information & Communication Technology. Default: any classification. With no keywords, a classification alone returns every job in it (narrowed by Location if set).

## `workType` (type: `string`):

For keyword searches: only jobs of this work type: full time, part time, contract/temp or casual/vacation. Default: any work type.

## `workArrangement` (type: `string`):

For keyword searches: only on-site, hybrid or remote jobs, as the advertiser marked them. Default: any arrangement.

## `salaryMin` (type: `integer`):

For keyword searches: SEEK's own salary filter, annual amount in AUD (NZD for New Zealand), e.g. 100000. SEEK also matches ads whose pay is not shown but which the advertiser placed in this range.

## `salaryMax` (type: `integer`):

For keyword searches: upper end of SEEK's salary filter, annual amount in AUD (NZD for New Zealand), e.g. 150000; must be above the minimum. Leave empty for no upper limit.

## `dateRange` (type: `integer`):

For keyword searches: SEEK's date filter, counted in calendar days of Sydney time. 1 = listed today (since midnight in Sydney), 2 = today and yesterday, and so on. For a daily schedule use 2 or 3, not 1. Leave empty for any date.

## `maxItemsPerQuery` (type: `integer`):

Number of jobs: stop after this many jobs for each keyword or URL (default 100, up to 200,000). Up to 500 they are the newest, except that SEEK puts up to two featured ads at the top of each page of 100 (isFeatured: true). A search can deliver slightly fewer than the maximum when some of its jobs were already delivered by an earlier search in the same run. SEEK serves at most 500 results per search; when you ask for more, the actor also runs narrower searches (by classification, state, work type, salary band), removes duplicates and reports in the run summary how well they covered the search. Past the first 500 the jobs come classification by classification, so a maximum below the search's total is not a cut by date. You are charged only for delivered jobs.

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

Also fetch each job's full description (descriptionHtml, descriptionText), expiry date, verified-advertiser flag and company profile facts (industry, size, website, rating). One extra request per job, about 4 to 5 jobs per second, and one extra "job-detail" charge per job delivered with a description. Default off: those fields are null.

## `onlyNew` (type: `boolean`):

Return and charge only jobs that earlier runs of this actor in your account have not delivered for the same search. The first run delivers what it finds, up to the maximum. Later runs deliver jobs not delivered before, in SEEK's order (newest first, after up to two featured ads per page): while the search holds more jobs than the maximum, that includes older ads the earlier runs did not reach, so set a date filter (2 or 3 days) to get fresh ads only. The memory is a key-value store named seek-jobs-scraper-monitor in your account (up to 100,000 jobs per search); delete it to start over.

## `monitorKey` (type: `string`):

Only with "Only new jobs". Any short name, e.g. sydney-daily. Runs with the same search and the same monitor name share one memory. Give two schedules different names to keep their memories apart. Default: empty (one memory per search).

## `proxyConfiguration` (type: `object`):

Off by default: SEEK answered our test runs from Apify's servers without a proxy. Turn on Apify Proxy, or enter your own proxy URLs, only if your runs are being blocked.

## Actor input object example

```json
{
  "queries": [
    "data analyst"
  ],
  "location": "All Sydney NSW",
  "country": "AU",
  "classification": "",
  "workType": "",
  "workArrangement": "",
  "maxItemsPerQuery": 50,
  "includeDescription": false,
  "onlyNew": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All delivered job ads as JSON, one object per job: id, url, title, company, location, area, country, classification and subClassification (with SEEK ids), workTypes, workArrangement, salaryText as written plus salaryMin, salaryMax, salaryPeriod (hour, day, week, month or year) and salaryCurrency when the text is clearly an amount, listedAt, teaser, bulletPoints, isFeatured; with Include full description also descriptionText, descriptionHtml, expiresAt and company profile fields (otherwise null).

## `resultsCsv` (type: `string`):

All delivered records as a CSV file.

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

What was requested, delivered and charged, how each search was covered (completeness, with a sample check for split searches), warnings, which inputs failed or were ignored, and why the run stopped.

# 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 = {
    "queries": [
        "data analyst"
    ],
    "location": "All Sydney NSW",
    "maxItemsPerQuery": 50,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("amitaviv/seek-jobs-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 = {
    "queries": ["data analyst"],
    "location": "All Sydney NSW",
    "maxItemsPerQuery": 50,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("amitaviv/seek-jobs-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 '{
  "queries": [
    "data analyst"
  ],
  "location": "All Sydney NSW",
  "maxItemsPerQuery": 50,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call amitaviv/seek-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,amitaviv/seek-jobs-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/kILIaxZIVrXl8LBdT/builds/b5cmPvEA1VtkYdfSo/openapi.json
