# Seek Jobs Scraper (Australia & New Zealand) (`hexagone-data/seek-scraper`) Actor

Scrape Seek Australia and New Zealand job search results and job ads: title, advertiser, location, classification, work type, salary text, dates; optional full ad, apply type, questions and company profile. Searches over Seek's 550-result limit are split. Pay per result.

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

## Pricing

from $0.42 / 1,000 jobs

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)

Scrape job offers from **Seek Australia** (seek.com.au, served today as au.seek.com) and **Seek New Zealand** (seek.co.nz,
served as nz.seek.com). Paste the search results URL your browser shows, filters included, or a job URL, and get one record
per job: title, advertiser, location, classification and sub-classification, work type, work arrangement, salary text as shown,
listing date, card tags and selling points. Turn on `includeDetails` to also get the full ad, its status and expiry, the apply
type, the employer questions, contacts found in the ad and the company profile, at no extra charge.

What makes it different:

- **Every job beyond Seek's 550-result limit.** Seek serves at most 550 results per search (page 18 comes back empty, with a
  total of 0). Bigger searches are split by work type, classification and sub-classification, sorted by date, and the run
  compares the distinct jobs you got with the total Seek announced. Any gap is reported with both numbers, never hidden.
- **One visitor per run.** Seek shows different totals and orders to different visitors (measured: 1,098 vs 1,160 jobs for
  the same search). The run keeps one visitor id from start to end, also after a platform migration.
- **Expired jobs are marked**, never presented as open; a job id that does not exist gets a clear warning.
- **No browser, no residential proxy**: Seek's own search and job details APIs, through the Apify datacenter proxy.

### Try it with 20 jobs

1. Keep the prefilled start URL (`https://www.seek.com.au/nurse-jobs/in-Sydney-NSW-2000`) and **Maximum number of jobs = 20**.
2. Click **Start**. You pay for 20 jobs: $0.017 (details included, start fee included).
3. Open **Jobs overview** for the table, **Run summary** for what was collected and why anything is missing.

### What to paste

| You paste | Example | What you get |
|---|---|---|
| A keyword search | `https://www.seek.com.au/nurse-jobs/in-Sydney-NSW-2000` | every job of the search, in the site's order |
| A classification search | `https://www.seek.com.au/jobs-in-information-communication-technology/in-Melbourne-VIC-3000/full-time?daterange=7&salaryrange=100000-&salarytype=annual` | filters kept exactly as in the URL |
| A remote / hybrid search | `https://www.seek.com.au/data-analyst-jobs/remote` | `workarrangement` filter |
| A New Zealand search | `https://www.seek.co.nz/developer-jobs/in-All-Auckland` | NZ jobs, `url` on nz.seek.com |
| A job | `https://www.seek.com.au/job/95568355` | that job with its details (`detailsSource: jobPage`) |

Supported URL parts: `/<keywords>-jobs`, `/jobs`, `/jobs-in-<classification>` (the 30 Seek classifications),
`/<keywords>-jobs-in-<classification>`, `/in-<location>`, `/full-time`, `/part-time`, `/contract-temp`, `/casual-vacation`,
`/remote`, `/hybrid`, and every query parameter of the site (`daterange`, `salaryrange`, `salarytype`, `sortmode`,
`classification`, `subclassification`, `workarrangement`...). `page=` is ignored: the run starts at page 1. A URL the actor does
not understand is **rejected with the reason**; it is never replaced by a broader search.

No URL? Fill **Keyword**, **Location**, **Country** (AU or NZ) and the optional filters. URLs win over the keyword fields.

**Where: same resolution as the website.** A free location is searched exactly as Seek's website searches it, for the keyword
fields and for pasted `/in-<location>` URLs alike: a postcode or a state code (`3000`, `Sydney NSW`) is kept as typed; a plain
place name is looked up once in Seek's own location suggestions and becomes the region the site uses when there is one
(`Wellington` -> `All Wellington`, `Perth` -> `All Perth WA`), otherwise it is kept as typed (`Newcastle`, `Bondi`). Measured on
16 places on 2026-10-29: same location and same totals as the website 16/16 (sending the raw text gave a wrong total for 7 of
them, down to -20%). A place Seek does not know (`Hunter Region`) is never replaced by another one: the site shows no job for it,
so the URL ends `no_results` with a `LOCATION_UNKNOWN` warning. The run summary shows the location searched (`whereSearched`).

### Output

One record per job and run (`jobId` is the key: a job found by two of your URLs is delivered once). Unknown values are `null`,
never `""`, `0` or `false`. Real record, **abridged** (39 of the 73 fields shown, ad text shortened; search `nurse-jobs/in-Sydney-NSW-2000`, read on
2026-10-29). Every field is listed in the dataset schema:

```json
{
  "jobId": "95568355",
  "url": "https://au.seek.com/job/95568355",
  "title": "Telehealth Nurse (RN or EN)",
  "advertiserName": "Reorg Solutions",
  "advertiserId": "63161095",
  "companyName": "Reorg Solutions",
  "companyId": null,
  "companyLogo": null,
  "location": "Sydney NSW",
  "countryCode": "AU",
  "classification": "Healthcare & Medical",
  "classificationId": "1211",
  "subClassification": "Nursing - General Medical & Surgical",
  "subClassificationId": "6342",
  "workTypes": ["Casual/Vacation"],
  "workArrangements": ["On-site"],
  "salaryLabel": "$45 per hour",
  "teaser": "Seeking a Registered Nurse for a remote telehealth casual role at $45/hr, with flexible hours.",
  "postedAt": "2026-10-29T02:19:03Z",
  "postedAtLabel": "17h ago",
  "tags": [],
  "site": "AU",
  "sourceUrl": "https://www.seek.com.au/nurse-jobs/in-Sydney-NSW-2000",
  "page": 1,
  "position": 1,
  "searchPart": null,
  "detailsSource": "jobPage",
  "descriptionText": "Telehealth Registered Nurse (RN) - Working With Veterans\nCasual Position | $45 Per Hour | Work From Home\n...",
  "status": "Active",
  "isExpired": false,
  "expiresAt": "2026-11-28T12:59:59Z",
  "isExternalApply": false,
  "questions": ["Which of the following statements best describes your right to work in Australia?", "..."],
  "jobPageClassifications": ["Nursing - General Medical & Surgical (Healthcare & Medical)"],
  "suburb": "Sydney",
  "state": "New South Wales",
  "postcode": "2000",
  "area": "CBD, Inner West & Eastern Suburbs",
  "companySearchUrl": "https://au.seek.com/Reorg-Solutions-jobs/at-this-company"
}
```

Good to know:

- `advertiserName` is the name the card shows (`GWM`); `companyName` is Seek's normalised company name (`Great Wall Motors`),
  not shown on the card, null when Seek has none.
- `salaryLabel` is the salary text exactly as shown (`$80,000 – $110,000 per year`, `Excellent hourly rates!`). Seek has no
  structured salary: no amount is invented.
- `classification` and `subClassification` are the first classification (the one the card shows); `classifications` lists all.
- `searchPart` is null for jobs from the site's own pages and names the split part (`worktype=242&classification=1211`) for jobs
  read beyond the 550 limit; their `page`/`position` are those of the part (50 per page).
- With `includeDetails` off, every detail field is null (or `[]`) and `detailsSource` is `listing`.

### Run summary

Each run writes a readable **Run summary** (and the same as JSON in `OUTPUT`): per start URL, the status, jobs delivered vs jobs
announced by Seek, and the reason for anything missing.

| Status | Meaning |
|---|---|
| `done` | distinct jobs seen + jobs Seek repeated **inside one page** >= jobs announced, and no page failed, came back empty, only repeated earlier jobs or shrank the total |
| `partial_collection` | fewer rows than announced: both numbers and the cause (failed page, empty page before the end, split part too big...); warning `PARTIAL_COLLECTION` |
| `stopped_at_limit` | your `maxItems` or maximum charge was reached |
| `no_results` | Seek announced 0 jobs: the search is empty, the URL is wrong or the site is down (`NO_RESULTS` warning) |
| `no_new_jobs` | every job of this URL was already delivered by an earlier URL |
| `job_not_available` | a pasted job id does not exist on Seek (Seek answers "no job" without error); no job charged |
| `location_not_resolved` | Seek's location suggestions could not be read and nothing was found; never reported as an empty search |
| `blocked_by_site`, `network_error`, `http_error` | Seek refused or did not answer even after new IPs — never reported as an empty search |

Seek's total counts listing rows, and a page of its listing can show the same job twice (measured twice, always inside one
page: 41 rows, 39 distinct jobs). Only those repeats inside one page of the site's own listing are counted: the summary says
"41 listed by Seek, 39 distinct (2 repeated by Seek)" and gives `siteDuplicates`, and the run can be `done`. A job seen again on a
later page (the listing moved while it was read) or inside a split part is never counted as a repeat: any gap stays visible
as `partial_collection`. If more than 10% of the rows are repeats, `SITE_DUPLICATES` warns that this is unusual.
Codes starting with `ANO_` (`ANO_ACCES`, `ANO_CHAMP_VIDE`, `ANO_ERREUR_INTERNE`...) come from the shared run checks: access
verdicts, a field suddenly empty, an internal error.

A run where no URL delivers anything **fails** with the exact cause. Warnings (`PARTIAL_COLLECTION`, `UNEXPECTED_DATA`, `JOBS_SKIPPED`, `JOB_EXPIRED`,
`DETAILS_NOT_READ`, `DETAILS_STOPPED`, `LOCATION_INTERPRETED`, `DELIVERED_OUTSIDE_LOCATION`...) are listed under **Checks**; "Checks passed" is shown only when
there is none.

### Pricing

Pay per event: **$0.60 per 1,000 jobs** ($0.0006 per job delivered), **details included**: the same price with or without
`includeDetails`, and for a pasted job URL. Plus a **$0.005 start fee per run** (charged by Apify once per GB of memory; the
default is 1 GB).
Apify subscribers pay less automatically, on every event: 10% off on the Bronze plan, 20% on Silver, 30% on Gold and above.

| Jobs | Price (with or without details, start fee included) |
|---|---|
| 20 | $0.017 |
| 1,000 | $0.605 |

Only jobs written to your dataset are paid. Your maximum charge per run is respected: the run stops cleanly before the job
that would exceed it, and no job details are read for a job that could not be paid.

### Known limits

- **Keyword searches are semantic on Seek**: beyond 550 jobs, the split parts may not add up to the announced total (measured
  1,150 of 1,160 for nurse jobs in Sydney). The run then ends `partial_collection` with both numbers. Searches without keyword
  split exactly (measured 1,003 of 1,003 for Hobart).
- A split part that still holds more than 550 jobs with the same work type, classification and sub-classification can only be
  read to 550; the gap is reported. Sub-classifications are taken from the jobs read in their classification.
- If Seek's location suggestions cannot be read (new IPs, then a last try after a pause), the location is searched as typed:
  the URL ends `partial_collection` (or `location_not_resolved` if nothing was found), never `done`, with `LOCATION_UNRESOLVED`;
  a resumed run tries the suggestions again. If it then finds Seek's region and some jobs delivered before (searched as
  typed) are not in that region's search, they stay in your dataset and are counted apart, with a
  `DELIVERED_OUTSIDE_LOCATION` warning (`deliveredOutsideLocation` in the summary). This warning is only given when the
  region's search was read in full; otherwise the summary only says those jobs could not be checked against it.
- `postedAtLabel` ("17h ago") is relative to the reading time. Featured ads, multiple locations and multiple work types per
  job were never seen on 2026-10-29: delivered as Seek sends them.
- Not available without a Seek account: number of applicants. Not provided: estimated salary, parsed salary amounts.
- Several work types or arrangements in the keyword fields are sent comma-separated (not measured on the site).
- Default run timeout: 4 hours (about 0.6 s per job with details on Apify: some 20,000 jobs); raise it for larger searches.

### Support

Questions, a URL that does not work, a field you need: open an issue on the actor's **Issues** tab with the run link.

This actor is not affiliated with, endorsed or sponsored by SEEK Limited. "Seek" is a trademark of its owner, used here only to
describe the website the data comes from. Only publicly visible job ads are collected; use the data in line with Seek's terms
and the privacy laws that apply to you (contacts found in ads are personal data).

# Actor input Schema

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

Seek search results URLs exactly as your browser shows them, filters included (e.g. https://www.seek.com.au/nurse-jobs/in-Sydney-NSW-2000, https://www.seek.com.au/jobs-in-information-communication-technology/in-Melbourne-VIC-3000/full-time?daterange=7, https://www.seek.co.nz/developer-jobs/in-All-Auckland), or job URLs (https://www.seek.com.au/job/<id>). When set, keyword and its filters are ignored.

## `keyword` (type: `string`):

Job search keywords, e.g. nurse, data analyst.

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

Where, as the site writes it: 'Sydney NSW 2000', 'All Melbourne VIC', 'All Auckland'.

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

AU = seek.com.au, NZ = seek.co.nz.

## `workTypes` (type: `array`):

Any of: full-time, part-time, contract-temp, casual-vacation.

## `workArrangements` (type: `array`):

Any of: on-site, hybrid, remote.

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

Classification id or slug as in the site URLs, e.g. 6281 or information-communication-technology.

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

1, 3, 7, 14 or 31 like the site; empty = any time.

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

Only jobs paying at least this (site filter salaryrange=<min>-).

## `salaryType` (type: `string`):

Period of the minimum salary.

## `sortBy` (type: `string`):

relevance (site default) or date (newest first).

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

Stops after this many unique jobs across all start URLs. 0 = no limit. You pay per delivered job.

## `includeDetails` (type: `boolean`):

Off by default. When on, each delivered job's details are read once (Seek's own job details API): full ad (HTML and text), status and expiry, apply type, employer questions, contacts found in the ad, video and branding, company profile (industry, size, website, rating, reviews, perks), suburb, state and postcode. Details are included in the price per job: no extra charge ($0.60 per 1,000 jobs with or without details).

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

Apify datacenter proxy by default: enough for Seek's search and job details APIs (measured 2026-10-29). Do not ask for country AU on the datacenter group (no such IP: HTTP 407). A refused or timed-out request is retried with a new IP, up to 4 times.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.seek.com.au/nurse-jobs/in-Sydney-NSW-2000"
    }
  ],
  "country": "AU",
  "salaryType": "annual",
  "sortBy": "relevance",
  "maxItems": 20,
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `jobs` (type: `string`):

All job records, one per job offer, with every field.

## `jobsOverview` (type: `string`):

Title, advertiser, location, work type, salary, classification, date, link.

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

Readable per-URL summary: status, jobs delivered vs announced, reason for anything missing.

## `summaryJson` (type: `string`):

The same summary as JSON (OUTPUT record).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        {
            "url": "https://www.seek.com.au/nurse-jobs/in-Sydney-NSW-2000"
        }
    ],
    "maxItems": 20,
    "includeDetails": false,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("hexagone-data/seek-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [{ "url": "https://www.seek.com.au/nurse-jobs/in-Sydney-NSW-2000" }],
    "maxItems": 20,
    "includeDetails": False,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("hexagone-data/seek-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://www.seek.com.au/nurse-jobs/in-Sydney-NSW-2000"
    }
  ],
  "maxItems": 20,
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call hexagone-data/seek-scraper --silent --output-dataset

```

## MCP server setup

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