# Eluta.ca Job Scraper — Canadian Jobs, Salaries & Apply URLs (`corvuslab/eluta-scraper`) Actor

Scrape Canadian job listings from Eluta.ca by keyword, city and province. Get title, company, structured location, remote flag, parsed CAD salary (min/max/period), the direct employer apply URL and the full job description — with cheap incremental monitoring for scheduled daily feeds.

- **URL**: https://apify.com/corvuslab/eluta-scraper.md
- **Developed by:** [Corvuslab](https://apify.com/corvuslab) (community)
- **Categories:** Lead generation, Jobs, Automation
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.30 / 1,000 results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Eluta.ca Job Scraper — Canadian Jobs, Salaries & Apply URLs

### 🍁 What does the Eluta.ca Job Scraper do?

> **Turn any Eluta.ca search into clean, structured Canadian job data — title, company, location, salary and the direct employer apply link — in one run.**

Scrape job listings from **Eluta.ca**, the search engine behind Canada's Top 100 Employers, by keyword, city and province. Every job comes back as a structured record: **title, company, structured location (city / province), a first-class remote flag, parsed salary (min / max / currency / period), the direct employer apply URL and the full job description** — plus dozens more fields. Run it no-code in the visual editor or via the API, export to **JSON, CSV, Excel** or feed it straight to an **AI agent**, and schedule it with **incremental monitoring** so a daily feed only ever costs you the jobs that actually changed.

**Why this scraper**

- ⚡ **Fast & low-cost** — pulls about 10 jobs per page request, so large runs stay cheap.
- 🧾 **Rich, typed records** — 35+ structured fields per job, not raw HTML.
- ♻️ **Cheap to monitor** — incremental mode re-scrapes only what changed (see below).
- 🔔 **Notifications built in** — Telegram, Slack, Discord or any webhook.
- 🤖 **AI- & API-ready** — compact output, MCP-friendly, one-click integrations.

***

### ✨ Key features

- 🔎 **Search or URL scraping** — run a keyword search with optional city/province, or paste Eluta.ca search and job-page URLs.
- 🍁 **Built for Canada** — structured `city`, `province` and `countryCode`, plus a first-class `isRemote` boolean parsed straight from the listing.
- 💰 **Parsed salary** — the raw salary text *and* structured `salaryMin`, `salaryMax`, `salaryCurrency` (CAD) and `salaryPeriod` (year / hour), so you can sort and filter on pay.
- 🔗 **Direct employer apply URL** — resolves the real destination on the employer's own careers site, not just the Eluta redirect, so leads and applications land where they should.
- 🧠 **Full job descriptions** — the complete posting as plain **text**, **HTML** and **Markdown**, with an always-on short snippet for previews.
- 🎚️ **Powerful filters** — city, province/territory, include/exclude **keyword** filters, a posted **date range** (`fromDate` / `toDate`), max age in days *or* minutes, and a result cap — keyword filtering runs before any detail fetch, so it stays cheap.
- 📇 **Contact extraction** — optionally pull **emails, phone numbers and social-profile links** out of each job description.
- ♻️ **Incremental monitoring** — schedule it and get only what changed (NEW / UPDATED / EXPIRED); unchanged jobs are skipped before their detail page is ever fetched.
- 🔁 **Repost detection** — the same job re-posted under a new ID is flagged (`isRepost`), and can be dropped entirely with **Skip reposts**.
- 🔔 **Notifications** — Telegram, Slack, Discord or any webhook (n8n / Make / Zapier), with an optional **changes-only** filter.
- 🤖 **AI-ready** — compact and drop-empty output modes keep payloads small for LLMs and MCP.

***

### 📤 Example output

```json
{
  "id": "68416bf7ec7a681f6c0c59587993542e",
  "title": "Senior Software Developer, Backend",
  "url": "https://www.eluta.ca/spl/senior-software-developer-backend-68416bf7ec7a681f6c0c59587993542e",
  "company": "Autodesk Canada Co.",
  "employerElutaUrl": "https://www.eluta.ca/jobs-at-autodesk-canada",
  "location": "Toronto, ON",
  "city": "Toronto",
  "province": "ON",
  "countryCode": "CA",
  "isRemote": false,
  "isSponsored": false,
  "salaryRaw": "$107,000 - $157,300",
  "salaryMin": 107000.0,
  "salaryMax": 157300.0,
  "salaryCurrency": "CAD",
  "salaryPeriod": "year",
  "postedAtRaw": "19 hours ago",
  "postedAt": "2026-08-10T23:17:39.552811+00:00",
  "applyUrl": "https://autodesk.wd1.myworkdayjobs.com/en-US/Ext/job/Toronto-ON-CAN/Senior-Software-Developer--Backend_26WD100353",
  "sourceDomain": "autodesk.wd1.myworkdayjobs.com",
  "sourceCountry": "CA",
  "descriptionSnippet": "... software – from the greenest buildings and cleanest cars to the smartest factories and biggest hit movies. We help innovators turn their ideas into reality, transforming not only how things are made, but what can be made...",
  "resultRank": 4,
  "source": "eluta.ca",
  "searchKeyword": "software developer",
  "scrapedAt": "2026-08-11T18:17:39.552811+00:00",
  "detailFetched": true
}
```

Full runs also carry the complete description as `description` (text), `descriptionHtml` and `descriptionMarkdown`, plus `descriptionLength`.

### 📥 Example input

A few ready-to-run configurations — set these in the visual editor or pass them as JSON via the API:

```json
{ "query": "software developer", "location": "Toronto", "maxResults": 100 }
```

```json
{ "query": "registered nurse", "province": "BC", "maxAgeDays": 7, "includeDetails": true }
```

```json
{ "query": "data analyst", "location": "Ottawa", "incrementalMode": true, "telegramToken": "…", "telegramChatId": "@yourchannel" }
```

```json
{ "query": "developer", "location": "Toronto", "includeKeywords": "python, backend", "excludeKeywords": "senior, manager", "includeDetails": true, "includeContacts": true }
```

***

### 📚 What data can you extract?

- **Job & employer** — `title`, `company`, `employerElutaUrl`, `url`, `id`, `source`.
- **Location** — `location`, `city`, `province`, `countryCode`, `isRemote`.
- **Salary** — `salaryRaw`, `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`.
- **Posting details** — `employmentType`, `isSponsored`, `postedAtRaw`, `postedAt`, `resultRank`, `searchKeyword`.
- **Apply & source** — `applyUrl` (direct employer link), `sourceDomain`, `sourceCountry`.
- **Description** (with **Fetch full details**) — `descriptionSnippet`, `description`, `descriptionHtml`, `descriptionMarkdown`, `descriptionLength`.
- **Contacts** (with **Extract contacts**) — `emails`, `phones`, `socialProfiles` parsed from the description.
- **Change tracking** (with incremental mode) — `changeType`, `isRepost`, `repostOfId`, `repostDetectedAt`, `contentHash`, `detailFetched`, `scrapedAt`.

Every field is present in standard mode (missing values are `null`); **compact mode** returns the core fields only (title, company, location, salary, apply URL), for lean AI/MCP payloads.

***

### ⚙️ Input

Configure it in the visual editor — no code needed — or pass JSON via the API.

| Field | What it does |
|---|---|
| `query` | Keyword search, e.g. `registered nurse` (comma-separate for multiple searches). |
| `location` | City or region to search within, e.g. `Toronto`, `Vancouver`, `Calgary`. |
| `province` | Restrict to a Canadian province or territory (AB, BC, ON, QC …). |
| `startUrls` | Scrape specific Eluta.ca search or job-page URLs directly. |
| `maxResults` | Cap the number of jobs (0 = unlimited). |
| `maxAgeDays` / `maxAgeMinutes` | Keep only jobs first seen within the last N days or minutes. |
| `fromDate` / `toDate` | Keep only jobs posted within a date range. |
| `includeKeywords` / `excludeKeywords` | Keep or drop jobs by keyword (matched before any detail fetch). |
| `includeDetails` | Fetch each job's full description and direct apply URL. |
| `includeContacts` | Extract emails, phones and social links from the description. |
| `incrementalMode` | Emit only what changed since the last run. |

…and **33 inputs** in total — the table shows the essentials; the rest cover description format and trimming, compact / drop-empty output modes, repost skipping, notification channels (with a changes-only option), incremental tuning and advanced options, all in the visual editor.

***

### 💡 Use cases

- **Recruiting & sourcing** — build a live board of open Canadian roles with employer names and direct apply links.
- **Salary benchmarking** — aggregate `salaryMin`/`salaryMax` by role, city and province to price your offers.
- **Job-market & hiring research** — track which companies are hiring, where, and for what across Canada.
- **Job board & aggregator feeds** — enrich your own site or app with clean, structured Canadian listings.
- **Daily vacancy monitoring** — schedule with incremental mode + notifications for a live change feed.
- **AI agents & pipelines** — compact output plugs straight into LLM / MCP workflows.

***

### ♻️ Incremental monitoring — pay for changes, not repeats

Schedule the actor and turn on **incremental mode**: each run compares against the last and emits only **NEW / UPDATED / EXPIRED** jobs — unchanged listings are skipped *before* their detail page is fetched, so a daily watch costs a fraction of a full re-scrape.

| Daily churn | of 1,000 tracked | billable records | you save |
|---|---|---|---|
| 5 % | 1,000 | 50 | **95 %** |
| 15 % | 1,000 | 150 | **85 %** |
| 30 % | 1,000 | 300 | **70 %** |

The first run seeds the baseline and bills in full; every run after that bills only the delta.

***

### 🚀 How to run it

1. Open the actor and enter a **search keyword** (e.g. `software developer`) and, optionally, a **city** or **province** — or paste an Eluta.ca URL.
2. Set **Max results** and choose whether to **fetch full details** (description + direct apply URL).
3. (Optional) Turn on **incremental mode** and a **notification** channel, then **Schedule** it.
4. Click **Start**.
5. Download the data as **JSON, CSV or Excel**, or pull it from the **API**.

New to Apify? Create a free account — it comes with monthly credit, no credit card required.

***

### 🔌 Integrations & export

Export to **JSON, CSV, Excel** or an HTML table, or pull from the **REST API** and the **JavaScript / Python** clients. Runs on a **schedule**, connects to **Google Sheets, Slack, Make, Zapier and n8n**, and works as an **MCP tool** for AI agents — compact mode keeps token usage small.

***

### ❓ FAQ

**Do I need a proxy or login?** No — it runs out of the box. Proxy rotation is on by default for reliable multi-page runs and you can adjust it under Advanced.

**Which locations does it cover?** All of Canada — search by city, or restrict to any province or territory. Leave both empty to search the whole country.

**Do I get salaries?** Yes, when the employer publishes them — as raw text and as structured `salaryMin` / `salaryMax` / `salaryCurrency` / `salaryPeriod`. Jobs without a posted salary return `null`.

**Can I get the real employer apply link?** Yes — with **Fetch full details**, `applyUrl` resolves to the employer's own careers page, not just the Eluta redirect.

**Can I get only new jobs on a schedule?** Yes — turn on incremental mode and schedule it; each run emits only what changed and can notify your channel. Add **Notify only on changes** to keep those alerts to genuinely new/updated jobs.

**Can I filter the results?** Yes — narrow by city/province, a posted **date range** (`fromDate` / `toDate`) or **max age** (days or minutes), and keep or drop jobs with include/exclude **keyword** filters. Keyword filters run on the listing *before* any detail fetch, so they don't cost you extra.

**Can I get recruiter emails or phone numbers?** Turn on **Extract contacts** and the actor parses `emails`, `phones` and `socialProfiles` out of each fetched description. Most postings link to an applicant-tracking system rather than listing a contact, so these are often empty.

**What formats can I export?** JSON, CSV, Excel, HTML table, or via the API.

**Is it good for AI agents?** Yes — enable compact mode; the output is MCP-friendly.

**How many jobs can I get?** As many as the search returns — set `maxResults` (0 = unlimited).

**Is scraping this legal?** The actor collects only **publicly available** data. You are responsible for how you use it, including any personal data and GDPR-style obligations.

***

### ⚖️ Disclaimer

This actor accesses only publicly available data on Eluta.ca. You are responsible for how you use the extracted data — in particular any personal information — and for complying with the site's terms and applicable law (including the GDPR where it applies). Not affiliated with, endorsed by, or sponsored by Eluta.ca or Mediacorp Canada Inc.

***

**Keywords:** eluta scraper · eluta.ca scraper · eluta api · eluta job scraper · canada job scraper · canadian jobs scraper · canada jobs api · job listings canada · job postings scraper · salary data scraper · remote jobs canada · job market research · recruitment data · job board scraper · Toronto jobs scraper · export jobs to CSV · no-code job scraper · MCP tool for AI agents

# Actor input Schema

## `query` (type: `string`):

Job keywords, e.g. "software developer", "registered nurse" or "data analyst". Separate multiple searches with commas.

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

City or region to search within, e.g. "Toronto", "Vancouver" or "Calgary". Leave empty to search all of Canada.

## `province` (type: `string`):

Restrict the search to a Canadian province or territory (used when Location is empty).

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

Paste Eluta.ca search or job-page URLs to scrape directly instead of (or in addition to) the keyword search.

## `maxResults` (type: `integer`):

Maximum number of jobs to return. Set 0 for unlimited (bounded by how many the search has).

## `maxAgeDays` (type: `integer`):

Only keep jobs first seen within the last N days. Leave empty for no age limit.

## `maxAgeMinutes` (type: `integer`):

Finer recency limit — keep only jobs first seen within the last N minutes. Combined with Max age (days), the stricter of the two applies.

## `fromDate` (type: `string`):

Keep only jobs posted on or after this date (YYYY-MM-DD). Matched against the parsed posted date, which is approximate.

## `toDate` (type: `string`):

Keep only jobs posted on or before this date (YYYY-MM-DD, inclusive). Matched against the parsed posted date, which is approximate.

## `includeKeywords` (type: `string`):

Keep only jobs whose title, company, location or snippet contains at least one of these terms (case-insensitive, comma-separated). Applied before any detail fetch.

## `excludeKeywords` (type: `string`):

Drop jobs whose title, company, location or snippet contains any of these terms (case-insensitive, comma-separated). Applied before any detail fetch.

## `ignoreUrlFailures` (type: `boolean`):

Skip start URLs that cannot be interpreted instead of failing the whole run.

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

Fetch each job's full description and the direct employer apply URL. Turn off for the fastest, cheapest runs (listing fields only).

## `descriptionFormat` (type: `string`):

Which representation(s) of the job description to include when details are fetched.

## `descriptionMaxLength` (type: `integer`):

Trim each description to at most this many characters. Leave empty for the full text.

## `includeContacts` (type: `boolean`):

Parse each fetched description for emails, phone numbers and social-profile links (emails / phones / socialProfiles). Requires Fetch full details; most postings link to an ATS, so these are often empty.

## `compact` (type: `boolean`):

Emit only the core fields (title, company, location, salary, apply URL). Ideal for AI agents and MCP clients.

## `excludeEmptyFields` (type: `boolean`):

Remove null, empty-string and empty-array fields from each record.

## `incrementalMode` (type: `boolean`):

Track state between runs and tag every job with a changeType (NEW / UPDATED / UNCHANGED / EXPIRED).

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

Stable name for the tracked search. Leave empty to derive one automatically from your search settings.

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

Also emit jobs that have not changed since the previous run.

## `emitExpired` (type: `boolean`):

Emit records for jobs present last run but gone now.

## `skipReposts` (type: `boolean`):

Drop jobs detected as a repost of an already-seen posting (same title, new ID) instead of emitting them.

## `telegramToken` (type: `string`):

Bot token from @BotFather.

## `telegramChatId` (type: `string`):

Chat or channel ID, e.g. "-100123456789" or "@yourchannel".

## `slackWebhookUrl` (type: `string`):

Slack incoming-webhook URL.

## `discordWebhookUrl` (type: `string`):

Discord incoming-webhook URL.

## `webhookUrl` (type: `string`):

Any HTTPS endpoint. Receives a JSON POST with the matched jobs — works with n8n, Make and Zapier.

## `webhookHeaders` (type: `object`):

Extra headers for the webhook request, e.g. {"Authorization": "Bearer xyz"}.

## `notificationLimit` (type: `integer`):

How many jobs to include in each notification message.

## `notifyOnlyChanges` (type: `boolean`):

In incremental mode, only notify about jobs that actually changed (NEW / UPDATED), never unchanged listings emitted via Include unchanged jobs.

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

Eluta.ca throttles a single IP after many requests, so runs rotate through Apify datacenter proxies by default. Leave this on for reliable multi-page runs; switch it off only for tiny one-page runs.

## `maxRequestRetries` (type: `integer`):

How many times to retry a failed request before giving up on it.

## Actor input object example

```json
{
  "query": "software developer",
  "location": "Toronto",
  "province": "",
  "maxResults": 25,
  "includeKeywords": "python, backend",
  "excludeKeywords": "senior, manager",
  "ignoreUrlFailures": true,
  "includeDetails": true,
  "descriptionFormat": "all",
  "includeContacts": false,
  "compact": false,
  "excludeEmptyFields": false,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "skipReposts": false,
  "notificationLimit": 5,
  "notifyOnlyChanges": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxRequestRetries": 3
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `allItems` (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 = {
    "query": "software developer",
    "location": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("corvuslab/eluta-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 = {
    "query": "software developer",
    "location": "",
}

# Run the Actor and wait for it to finish
run = client.actor("corvuslab/eluta-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 '{
  "query": "software developer",
  "location": ""
}' |
apify call corvuslab/eluta-scraper --silent --output-dataset

```

## MCP server setup

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