Skip to main content
Thirdwatchthirdwatch
Jobs & recruitment

Scrape Seek Jobs for Australia & NZ Recruiting Data (2026)

Build an Australia/NZ recruiting pipeline from Seek with Thirdwatch. Get titles, companies, salary, location, classification, work type, and full descriptions.

Jun 20, 2026 · 6 min read · 1,305 words
See the scraper →

Thirdwatch's Seek Jobs Scraper returns Seek listings across Australia (seek.com.au) and New Zealand (seek.co.nz) — title, company, location, salary, classification, work type, work arrangement, teaser, bullet points, and optional full descriptions. Built for ANZ recruiter agencies, in-house TA teams, salary-research analysts, and aggregator builders who need Seek's catalog as a structured feed, no API key or login required.

Why scrape Seek for Australia and NZ recruiting

Seek is the dominant job board across Australia and New Zealand. According to the Australian Bureau of Statistics Labour Force release, Australia has roughly 14 million people in the labour force, and Seek carries the deepest local listing coverage of any single source for that market. For any ANZ-focused pipeline — recruiter agency, in-house talent-acquisition team, salary-research analyst — Seek is the primary source. The blocker for systematic access: Seek does not offer a public job-search API for third-party developers.

The job-to-be-done is structured. An ANZ recruiter agency wants a daily refresh of every senior software engineer role across Sydney, Melbourne, and Brisbane. A people-analytics team benchmarks compensation by classification and work arrangement across Australia and New Zealand. A staffing firm placing nurses across regional health systems tracks Healthcare & Medical listings filtered by work type. A job-board aggregator ingests Seek alongside LinkedIn and Indeed for the broadest possible ANZ coverage. All of these reduce to keyword + location + siteKey pulls returning structured rows ready for pandas, BigQuery, or a recruiter CRM.

How does this compare to the alternatives?

Three options for getting Seek data into a pipeline:

Approach Cost Reliability Setup time Maintenance
DIY Python scraper Free compute, weeks of dev Brittle when Seek changes 4–8 weeks You own the parser
Generic scraping API Subscription + per-call Generic, no Seek schema Days You build the field mapping
Thirdwatch Seek Jobs Scraper Transparent pay-per-result Production-tested AU + NZ 5 minutes Thirdwatch tracks Seek changes

A DIY build has to keep up with Seek's evolving search responses and rebuild field mapping on every change. A generic scraping API returns raw HTML you still have to parse into Seek's classification and salary structure. The Seek Jobs Scraper actor page returns clean, named fields out of the box — title, company, salary, classification, work type, and work arrangement — across both the AU and NZ sites.

How to scrape Seek for ANZ recruiting in 4 steps

Step 1: How do I authenticate against Apify?

Sign in at apify.com (free tier, no credit card), open Settings → Integrations, and copy your personal API token. Every example below assumes the token is in APIFY_TOKEN:

export APIFY_TOKEN="apify_api_xxxxxxxxxxxxxxxx"

Step 2: How do I pull jobs across roles and cities?

Pass search terms in keywords, set where to a location label, and choose siteKey for the country. Each keyword returns up to maxResults jobs.

import os, requests, pandas as pd

ACTOR = "thirdwatch~seek-jobs-scraper"
TOKEN = os.environ["APIFY_TOKEN"]

resp = requests.post(
    f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items",
    params={"token": TOKEN},
    json={
        "keywords": ["software engineer", "data analyst", "registered nurse"],
        "where": "Sydney NSW",
        "siteKey": "AU-Main",
        "scrapeDescription": False,
        "maxResults": 100,
    },
    timeout=900,
)
df = pd.DataFrame(resp.json())
print(f"{len(df)} Seek jobs across {df.company.nunique()} companies")

Three keywords × 100 results = up to 300 listings — small enough to run on demand at the actor's transparent pay-per-result pricing. To cover New Zealand, run the same input with "siteKey": "NZ-Main" and a NZ location like "Auckland".

Step 3: How do I filter by salary, classification, and work arrangement?

salary is present only when the advertiser publishes it, so filter to non-null rows before any band analysis. classification and work_arrangement are clean structured fields.

disclosed = df[df.salary.notna() & (df.salary != "")]
print(f"Salary disclosed on {len(disclosed)}/{len(df)} listings "
      f"({len(disclosed)/len(df):.0%})")

# Volume by Seek classification (e.g. Information & Communication Technology)
by_class = df.classification.value_counts()
print(by_class.head(10))

# Remote / hybrid demand from the structured work_arrangement field
remote = df[df.work_arrangement.isin(["Remote", "Hybrid"])]
print(f"{len(remote)} remote/hybrid roles "
      f"({len(remote)/len(df):.0%} of the batch)")

work_arrangement is Seek's own On-site / Hybrid / Remote flag — far more reliable than parsing "remote" out of titles. Because salary is frequently blank, always report disclosure rate alongside any median so the numbers are honest.

Step 4: How do I enrich descriptions and push to a CRM?

For keyword-matchable body content, enable scrapeDescription, then dedupe on the stable url and upsert into your ATS or CRM.

resp = requests.post(
    f"https://api.apify.com/v2/acts/{ACTOR}/run-sync-get-dataset-items",
    params={"token": TOKEN},
    json={
        "keywords": ["registered nurse"],
        "where": "Melbourne VIC",
        "siteKey": "AU-Main",
        "scrapeDescription": True,
        "maxResults": 100,
    },
    timeout=1800,
)
df = pd.DataFrame(resp.json()).drop_duplicates(subset=["url"])

import psycopg2.extras
with psycopg2.connect(...) as conn, conn.cursor() as cur:
    psycopg2.extras.execute_values(
        cur,
        """INSERT INTO seek_jobs
              (url, title, company, location, salary, work_type,
               work_arrangement, classification, listing_date, scraped_at)
           VALUES %s
           ON CONFLICT (url) DO UPDATE SET
             salary = EXCLUDED.salary,
             scraped_at = now()""",
        [(j["url"], j["title"], j["company"], j.get("location"),
          j.get("salary"), j.get("work_type"), j.get("work_arrangement"),
          j.get("classification"), j.get("listing_date"), "now()")
         for _, j in df.iterrows()],
    )
print(f"Upserted {len(df)} Seek jobs")

Schedule the actor on Apify's scheduler at daily cadence and the loop is self-maintaining — fresh Seek roles land in the recruiter's pipeline every morning.

Sample output

A single Seek record looks like this. Five rows of this shape weigh roughly 8 KB without descriptions, more once scrapeDescription is enabled.

{
  "id": "84231055",
  "title": "Senior Software Engineer",
  "company": "Atlassian",
  "location": "Sydney NSW",
  "locations": ["Sydney NSW", "Remote"],
  "salary": "$150,000 - $180,000 per year",
  "work_type": "Full time",
  "work_types": ["Full time"],
  "work_arrangement": "Hybrid",
  "classification": "Information & Communication Technology",
  "subclassification": "Engineering - Software",
  "teaser": "Join a platform team building developer tooling at scale.",
  "bullet_points": ["Flexible hybrid work", "Equity + bonus", "Modern stack"],
  "listing_date": "2026-06-18T03:12:00Z",
  "listing_date_display": "2d ago",
  "is_featured": false,
  "url": "https://www.seek.com.au/job/84231055",
  "keyword": "software engineer",
  "scraped_at": "2026-06-20T08:00:00Z"
}

url is the canonical natural key for upsert. salary is whatever Seek displays publicly and is frequently null when the advertiser withholds it. classification and subclassification follow Seek's own taxonomy — high-signal for cross-role analysis without keyword guessing. bullet_points is a clean array of the listing's highlighted selling points, and description (not shown above) is populated only when scrapeDescription is enabled.

Common pitfalls

Three things go wrong in production Seek pipelines. Salary opacity — many Seek advertisers do not publish pay, so salary is null on a large share of listings; pipelines that average without filtering null rows produce wildly wrong estimates, so always filter to disclosed-salary rows and report the disclosure rate. AU vs NZ mixingAU-Main and NZ-Main are separate sites; run them as separate batches with the matching where location, and dedupe across the combined dataset by url. Description cost-vs-depth — the teaser and bullet points are always returned, but full body text only arrives when scrapeDescription is enabled, which adds a per-job fetch; leave it off for high-volume sweeps and on only when you need matchable description content.

Thirdwatch's actor handles the request work and field mapping so you get named columns instead of raw HTML, and the schema stays stable across AU and NZ. Pair Seek with LinkedIn Jobs Scraper for international coverage and Indeed Scraper for broader volume in the same markets. A fourth subtle issue: featured listings (is_featured true) surface ahead of newer organic posts, so for true new-posting velocity sort by listing_date rather than result order. A fifth: the same role can appear under multiple location tags via the locations array when an employer hires across offices — dedupe by url before computing per-city volume so multi-location postings do not double-count.

Related use cases

Frequently asked questions

Why scrape Seek for Australia and New Zealand recruiting?

Seek is the dominant job board across Australia and New Zealand, carrying the deepest local listing coverage of any single source. For any ANZ-focused recruiter pipeline, talent-market dataset, or job-board aggregator, Seek is the primary source — far deeper on local roles than LinkedIn or Indeed AU.

Does the actor cover both Australia and New Zealand?

Yes. Set siteKey to AU-Main for seek.com.au or NZ-Main for seek.co.nz. The two share the same field schema, so you can run an AU batch and an NZ batch into one dataset and dedupe by job id or url. Use the where input to narrow within each country.

Does Seek always return salary?

No. Salary is only present when the advertiser publishes it; many listings leave it blank, so the salary field is frequently null. Filter to non-null rows before computing salary bands. The actor returns the salary label exactly as Seek displays it (for example $150,000 - $180,000 per year).

How do I get full job descriptions?

Enable scrapeDescription. By default the actor returns the search teaser plus bullet points, which is fast and cheap. With scrapeDescription set to true, it fetches the full description text for every listing — slower and richer, ideal when you need keyword-matchable body content.

How fresh is Seek data?

Each run pulls live from Seek at request time. The listing_date field is an ISO timestamp and listing_date_display shows a human-readable age like 3d ago. Most active ANZ recruiter pipelines run Seek daily; for high-velocity contract markets, multiple-times-daily polling is justified.

Related

Try it yourself

100 free credits, no credit card.

About 30 real searches. Add the MCP to Claude or Cursor in two minutes.