# Seek JobStreet Jobsdb Scraper — 8 Countries (`incognito_mode/seek-jobs-scraper`) Actor

Scrape jobs from Seek (AU, NZ), JobStreet (SG, MY, PH, ID) and Jobsdb (HK, TH) in one Actor. Structured salary, company and location, optional full description, monitoring mode for new or changed listings. No browser, no login.

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

## Pricing

from $1.50 / 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.

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 JobStreet Jobsdb Scraper — 8 Countries

Scrape job listings from **Seek** (Australia, New Zealand), **JobStreet**
(Singapore, Malaysia, Philippines, Indonesia) and **Jobsdb** (Hong Kong,
Thailand) in one Actor. Every row uses the same flat shape: title, company,
structured salary, location, work type, arrangement and the correct public job
URL for that market. Optionally pull the full HTML description. Turn on
monitoring mode to emit only new or changed listings on a schedule.

No browser, no login, no residential proxy. Results land in an Apify dataset
you can export as **JSON, CSV, Excel, XML or HTML**, or pull through the API.

**From $1.50 per 1,000 jobs.** Full descriptions are an opt-in add-on. See
[Pricing](#pricing).

### Why one Actor for eight sites

Seek, JobStreet and Jobsdb are the same platform under different brands. Other
Store Actors usually cover Seek AU/NZ **or** JobStreet **or** Jobsdb. This one
takes a list of markets and returns the same fields for each, with the right
public URL per brand — so a multi-country talent or lead pipeline is one run,
not three Actors and three schemas.

### What you get

A search for `software engineer` on Australia (trimmed):

| Field | Example |
| --- | --- |
| title | Graduate Software Engineer |
| companyName | SEEK |
| salary.label | Be paid $82,000 + super + annual performance bonus |
| salary.min / max / currency | 82000 / 82000 / AUD |
| salary.period / annualMin | yearly / 82000 |
| locations | Melbourne VIC (AU) |
| workTypes | Full time |
| workArrangement | Hybrid |
| listedAt | 2026-09-29T01:40:48Z |
| url | https://www.seek.com.au/job/94933963 |

With **Include full description** on, the same row also carries
`descriptionHtml`, `expiresAt`, `isLinkOut` and `isVerified`.

### Markets

| Code | Brand | Site |
| --- | --- | --- |
| `AU` | Seek | [seek.com.au](https://www.seek.com.au) |
| `NZ` | Seek | [seek.co.nz](https://www.seek.co.nz) |
| `SG` | JobStreet | [jobstreet.com.sg](https://www.jobstreet.com.sg) |
| `MY` | JobStreet | [my.jobstreet.com](https://my.jobstreet.com) |
| `PH` | JobStreet | [jobstreet.com.ph](https://www.jobstreet.com.ph) |
| `ID` | JobStreet | [jobstreet.co.id](https://www.jobstreet.co.id) |
| `HK` | Jobsdb | [hk.jobsdb.com](https://hk.jobsdb.com) |
| `TH` | Jobsdb | [th.jobsdb.com](https://th.jobsdb.com) |

### What makes this useful

- **One schema across eight markets.** Filter or join on `site` / `brand`
  without renaming columns per country.
- **Structured salary from free text.** Seek only publishes a label. The Actor
  parses `min`, `max`, `currency` and `period`, and when the period is known
  also fills `annualMin` / `annualMax` (monthly × 12, weekly × 52, and so on).
  Currency defaults to the market's (AUD, NZD, SGD, …) when the label has `$`
  but no code. Labels with no usable number leave the numeric fields null —
  the original label is always kept.
- **Monitoring mode for schedules.** First run emits every match as `NEW`.
  Later runs only emit jobs that are new or whose title / listed date changed
  (`UPDATED`). State lives in the run's default key-value store, keyed by your
  search (or by an explicit **State key** so several schedules stay isolated).
- **Cheap to run.** Plain HTTP, 512 MB default memory, no residential proxy.
  Listings are billed only when stored; a failed detail fetch leaves
  `descriptionHtml` null and is not charged for the detail event.
- **Ready-made Store examples.** Open one, press **Try for free**, change the
  keywords — see [Ready-made examples](#ready-made-examples).

### Limits you should know

Seek stops returning results after about **500 hits per query**, regardless of
page size. Narrow with keywords, location or **Listed within** rather than
raising max pages past 10. Broader keywords will not dump the whole board.

Detail fetches that fail leave `descriptionHtml` null and are not billed. The
run stops cleanly when it reaches your maximum charge.

### How to use it

**Today's software roles in Australia:**

```json
{
  "sites": ["AU"],
  "keywords": "software engineer",
  "dateRange": "1",
  "maxItems": 50
}
```

**Singapore and Malaysia, with full descriptions:**

```json
{
  "sites": ["SG", "MY"],
  "keywords": "data analyst",
  "includeDetails": true,
  "maxItems": 100
}
```

**Daily monitor for new nursing jobs in Auckland:**

```json
{
  "sites": ["NZ"],
  "keywords": "nurse",
  "where": "Auckland",
  "monitoringMode": true,
  "dateRange": "3"
}
```

#### Tips

- **Keywords are required.** Use the same string you would type into the
  Seek / JobStreet / Jobsdb search box.
- **`maxItems` is across every selected market.** Searching AU + NZ with
  `maxItems: 100` returns up to 100 rows in total, not 100 each.
- **For alerts, prefer monitoring mode + a short `dateRange`.** That keeps
  each scheduled run small and only stores what actually changed.
- **Set a `stateKey`** if two schedules share similar keywords but different
  locations, so their monitoring state does not collide.
- **Leave proxy configuration alone.** The Actor does not need a residential
  proxy for normal use.

### Input

| Field | Default | Notes |
| --- | --- | --- |
| `sites` | `["AU"]` | Any of `AU` `NZ` `SG` `MY` `PH` `ID` `HK` `TH` |
| `keywords` | — | **Required.** Job search keywords |
| `where` | — | City / region filter (e.g. `"Sydney"`, `"Auckland"`) |
| `dateRange` | — | `1` / `3` / `7` / `14` / `31` days |
| `sortBy` | `relevance` | or `listedDate` |
| `maxItems` | `100` | Hard cap across every selected market |
| `maxPages` | `10` | Per market, up to 50 jobs per page |
| `includeDetails` | `false` | Paid add-on: full HTML description |
| `monitoringMode` | `false` | Emit new / changed jobs only |
| `stateKey` | — | Optional name for monitoring state |
| `emitUnchanged` | `false` | Also emit unchanged rows in monitoring mode |

### Output

One row per job listing:

```jsonc
{
  "id": "94933963",
  "url": "https://www.seek.com.au/job/94933963",
  "site": "AU",
  "brand": "Seek",
  "title": "Graduate Software Engineer",
  "companyName": "SEEK",
  "advertiser": { "id": "…", "name": "SEEK", "isVerified": true },
  "employer": { "id": "…", "name": "SEEK", "companyUrl": "…" },
  "locations": [
    { "label": "Melbourne VIC", "countryCode": "AU", "areas": ["Melbourne VIC"] }
  ],
  "workTypes": ["Full time"],
  "workArrangement": "Hybrid",
  "classifications": [
    { "id": "…", "name": "Information & Communication Technology",
      "subId": "…", "subName": "Developers/Programmers" }
  ],
  "salary": {
    "label": "Be paid $82,000 + super + annual performance bonus",
    "min": 82000, "max": 82000, "currency": "AUD",
    "period": "yearly", "annualMin": 82000, "annualMax": 82000
  },
  "teaser": "Join our graduate program…",
  "bulletPoints": ["Competitive salary", "Hybrid work"],
  "listedAt": "2026-09-29T01:40:48Z",
  "isFeatured": false,

  // Present when includeDetails is true and the fetch succeeded:
  "descriptionHtml": "<div>…</div>",
  "expiresAt": "2026-10-29T01:40:48Z",
  "isExpired": false,
  "isLinkOut": false,
  "isVerified": true,

  // Present in monitoring mode:
  "changeType": "NEW"   // NEW | UPDATED | UNCHANGED
}
```

The dataset **Overview** view in Console shows market, title, company, salary,
work type, arrangement, listed date, URL and change type.

### Pricing

Pay per event — you pay only for what is stored.

| Event | When it is charged | Price |
| --- | --- | ---: |
| Actor start | once per run | $0.0001 |
| Job | every stored listing | $0.0015 |
| Job detail | listing has `descriptionHtml` | $0.001 |

Examples:

- 1,000 list rows ≈ **$1.50**
- 1,000 list rows with full descriptions ≈ **$2.50**
- A 50-job search without descriptions ≈ **$0.08** (including start)

Failed detail fetches and empty searches are not charged for job or detail
events. The run stops cleanly when it hits your maximum charge.

### Ready-made examples

Open one, press **Try for free**, and change the input to your own:

- [Seek Australia software jobs this week](https://apify.com/incognito_mode/seek-jobs-scraper/examples/seek-au-software-jobs)
- [JobStreet Singapore & Malaysia jobs](https://apify.com/incognito_mode/seek-jobs-scraper/examples/jobstreet-sg-my-jobs)
- [Seek AU jobs with full description](https://apify.com/incognito_mode/seek-jobs-scraper/examples/seek-jobs-with-description)

### FAQ

**Do I need an account on Seek / JobStreet / Jobsdb?**\
No. The Actor only collects publicly listed jobs. No login and no API key.

**Can I search several countries in one run?**\
Yes. Pass multiple codes in `sites`, for example `["SG", "MY", "PH"]`. Rows
share one schema; use `site` or `brand` to filter afterward.

**Why did I get fewer than `maxItems` rows?**\
Seek caps each query at about 500 results, and some keywords simply have fewer
listings in the date window you chose. Narrow or broaden keywords, or widen
`dateRange`.

**Why is `salary.min` null when there is a salary label?**\
Some labels are qualitative (“Competitive”, “Attractive package”) or mix
benefits without a clear number. The original `salary.label` is always kept;
numeric fields stay null when nothing reliable can be parsed.

**How does monitoring mode decide something changed?**\
A job is `UPDATED` when its title or listed date differs from the previous run
for the same search state. Unchanged jobs are skipped unless you turn on
**Also emit unchanged jobs**.

**Is residential proxy required?**\
No. Normal runs use plain HTTP on low memory. Leave proxy settings at the
default unless you have a specific reason to override them.

**How should I use the data?**\
You are responsible for complying with Seek / JobStreet / Jobsdb terms and
applicable laws (including privacy and recruitment rules) for your use case.

# Changelog

This Actor's version history is a separate document: https://apify.com/incognito_mode/seek-jobs-scraper/changelog.md

# Actor input Schema

## `sites` (type: `array`):

Which Seek-platform markets to search. All eight share one API; each code picks the right site key and the public job URL. Defaults to Australia.

## `keywords` (type: `string`):

Job search keywords, the same string you would type into Seek / JobStreet / Jobsdb.

## `where` (type: `string`):

Optional city, region or country filter (e.g. "Sydney", "Auckland", "Singapore").

## `dateRange` (type: `string`):

Only jobs listed in the last N days.

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

Relevance (Seek's default) or newest first.

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

Hard cap on how many jobs are stored. Applied across every selected market. Seek itself stops returning results after about 500 hits per query, so a broader keyword will not dump the whole board.

## `maxPages` (type: `integer`):

Each page holds up to 50 jobs. Seek refuses requests past roughly page 27 (about 500 results), so the useful range is 1–10 for most keywords.

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

Fetch each job's full HTML description, expiry date and link-out flag via GraphQL. Billed as a separate pay-per-event charge on top of the listing.

## `monitoringMode` (type: `boolean`):

Remember the jobs seen for this search and only emit NEW or UPDATED ones on later runs. Ideal for a daily schedule. State is stored in the run's default key-value store.

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

Optional name for the monitoring state. Leave empty to derive one from the search inputs so different keyword/location combinations stay isolated.

## `emitUnchanged` (type: `boolean`):

When monitoring mode is on, also emit jobs that have not changed since the last run (changeType = UNCHANGED).

## Actor input object example

```json
{
  "sites": [
    "AU"
  ],
  "keywords": "software engineer",
  "sortBy": "relevance",
  "maxItems": 50,
  "maxPages": 10,
  "includeDetails": false,
  "monitoringMode": false,
  "emitUnchanged": false
}
```

# Actor output Schema

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

No description

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

No description

# 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 = {
    "sites": [
        "AU"
    ],
    "keywords": "software engineer",
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("incognito_mode/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 = {
    "sites": ["AU"],
    "keywords": "software engineer",
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("incognito_mode/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 '{
  "sites": [
    "AU"
  ],
  "keywords": "software engineer",
  "maxItems": 50
}' |
apify call incognito_mode/seek-jobs-scraper --silent --output-dataset

```

## MCP server setup

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