# Welcome to the Jungle Jobs Scraper — Europe Jobs & Salaries (`yasaslive/job-board-scraper`) Actor

Scrape job listings from Welcome to the Jungle (France, Spain, Czechia, Slovakia, UK) by keyword, location, contract and remote policy. Get salaries, full descriptions, benefits, apply links and company data as clean JSON.

- **URL**: https://apify.com/yasaslive/job-board-scraper.md
- **Developed by:** [Eonix Pvt Ltd](https://apify.com/yasaslive) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.68 / 1,000 job scrapeds

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

## Welcome to the Jungle Jobs Scraper — Europe Job Listings with Salaries

Get every job listing from [Welcome to the Jungle](https://www.welcometothejungle.com) as a clean spreadsheet or JSON feed: title, company, city, remote policy, contract type, **salary**, full description, benefits and the direct apply link. Search by keyword, city, contract type and remote policy across the French, English, Spanish, Czech and Slovak editions, with no login and no browser needed.

### What you get

- **Job details:** title, contract type (CDI, internship, freelance…), contract length, experience and education level, publication date.
- **Salary:** minimum, maximum, currency and period (yearly, monthly, daily), whenever the employer publishes it.
- **Location:** city, country, full address, GPS coordinates, remote policy (full, partial, occasional, none) and every office the job is open in.
- **Full description** (plain text and HTML), candidate profile, key missions and benefits (e.g. "2-3 days remote", "Meal vouchers").
- **Direct apply link** to the employer's own applicant tracking system (Lever, Greenhouse, Workday…).
- **Company data:** name, sector, number of employees, founding year, website, logo and the company's own description.
- **Tags:** job family, tools (Python, Dataiku, Terraform…) and skills.

### Use cases

- **Lead generation for recruiters and staffing agencies:** find companies that are hiring right now, with their size, sector and website, and pitch them before your competitors do.
- **Job aggregators and niche job boards:** feed your site with fresh European tech, startup and corporate jobs, de-duplicated and with apply links.
- **Salary and market research:** compare published salaries by role, city, contract type or experience level; track remote-work adoption across sectors.
- **Job-alert bots:** run it every morning with "Posted within 1 day" and push new jobs to Slack, Telegram, Discord or email (recipe below).
- **AI agents and RAG:** clean, structured job descriptions ready to embed in a vector database, or call the scraper as a tool through MCP.
- **Monitoring competitors:** watch which teams a company is growing, and where.

### Sample output

The first 3 items from a run with the **default input** ("data scientist" in Paris), trimmed to the most useful fields. Every item has ~35 fields; see the full list in the Output tab.

```json
[
  {
    "jobId": "8afd3e0d-c0b8-46ba-a804-49be349798df",
    "url": "https://www.welcometothejungle.com/fr/companies/ekimetrics/jobs/stage-2027-business-data-scientist-marketing-effectivness_paris",
    "title": "Stage 2027 Business Data Scientist - Marketing effectivness",
    "company": {
      "name": "Ekimetrics",
      "sector": "IT / Digital",
      "size": 500,
      "website": "https://www.ekimetrics.com/?language=fr"
    },
    "location": { "city": "Paris", "country": "France", "remote": "partial" },
    "contractType": "internship",
    "salary": { "min": null, "max": null, "currency": null, "period": null },
    "experienceLevel": null,
    "educationLevel": null,
    "publishedAt": "2026-09-25T00:01:43.000Z",
    "descriptionText": "Ekimetrics est un leader mondial de l'efficacité marketing et commerciale et des solutions d'IA à l'échelle. Depuis 2006, nous aidons les en…",
    "benefits": ["Entre 3-4 jours de télétravail", "Team building", "Afterworks, Déjeuners d'équipe, etc."],
    "applyUrl": "https://jobs.lever.co/ekimetrics/8f0d6bb4-903a-4deb-8689-2f72e8b3e171/apply?lever-source%5B%5D=WTTJ",
    "languages": ["fr"],
    "tags": ["Scientifique des données", "Données/Business Intelligence", "Kong", "Azure"]
  },
  {
    "jobId": "001141c5-7d2b-4d51-a289-29ac2e476799",
    "url": "https://www.welcometothejungle.com/fr/companies/aqemia-1/jobs/senior-data-engineer_london_t5m5dsi2",
    "title": "Senior Data Engineer",
    "company": { "name": "Aqemia", "sector": "Logiciels", "size": 71, "website": "https://www.aqemia.com/" },
    "location": { "city": "London", "country": "United Kingdom", "remote": null },
    "contractType": "full_time",
    "salary": { "min": null, "max": null, "currency": null, "period": null },
    "experienceLevel": "5_to_7_years",
    "educationLevel": null,
    "publishedAt": "2026-09-27T00:05:56.000Z",
    "descriptionText": "• As our Senior Data Engineer, you’ll own AQEMIA’s data platform end to end — from ingestion through the pipeline to the trusted, model-read…",
    "benefits": [],
    "applyUrl": "https://jobs.lever.co/aqemia.com/86818f9e-f167-4adb-b689-e5fc82e7b9fd",
    "languages": ["en"],
    "tags": ["Ingénieur de données", "Données/Business Intelligence", "Terraform", "BigQuery"]
  },
  {
    "jobId": "b87aaafb-4f2b-4851-95db-ff6b2d93d99f",
    "url": "https://www.welcometothejungle.com/fr/companies/artelys/jobs/lead-data-scientist-energie-et-prevision-f-h_paris_ARTEL_NwRPbo3",
    "title": "Lead Data Scientist – Energie et Prévision F/H",
    "company": {
      "name": "Artelys",
      "sector": "Ingénieries Spécialisées",
      "size": 135,
      "website": "https://www.artelys.com/fr"
    },
    "location": { "city": "Paris", "country": "France", "remote": null },
    "contractType": "full_time",
    "salary": { "min": null, "max": null, "currency": null, "period": null },
    "experienceLevel": null,
    "educationLevel": null,
    "publishedAt": "2026-09-25T05:30:02.000Z",
    "descriptionText": "Au sein des équipes Optimisation & Data, vous aurez la charge de conduire des travaux d’étude ou d’implémentation opérationnelle de modèles…",
    "benefits": [],
    "applyUrl": "https://taleez.com/apply/lead-data-scientist-energie-et-prevision-f-h-paris-artelys-cdi/applying?utm_source=wttj",
    "languages": ["fr"],
    "tags": ["Scientifique des données", "Données/Business Intelligence", "Python", "Gestion des entretiens"]
  }
]
```

When the employer publishes a salary, it looks like `"salary": { "min": 43550, "max": 53300, "currency": "EUR", "period": "yearly" }`. A missing value is always `null`, never an empty string, so your spreadsheet formulas and filters keep working.

### Pricing

You pay only for what you get. There is no monthly fee.

| What                      | Price  | When                                                                  |
| ------------------------- | ------ | --------------------------------------------------------------------- |
| Run start                 | $0.01  | Once per run                                                          |
| Job scraped               | $0.002 | Per job delivered to your dataset                                     |
| Full description (add-on) | $0.001 | Per job delivered with its full description (optional, on by default) |

**Worked example:** 1,000 jobs with full descriptions cost $0.01 + 1,000 × $0.002 + 1,000 × $0.001 = **$3.01**. Without descriptions it's **$2.01**. A daily alert that finds about 30 new jobs costs around **$0.10 per day**.

**You stay in control of spending.** Set **Maximum cost per run** when you start the actor. When the budget runs out, the scraper stops cleanly and keeps everything scraped so far. If only the base price still fits, the last job is delivered without its description, so you're never charged for the add-on on a job that doesn't include it. The run's `STATS` record shows `"budgetReached": true` when this happens.

### Input

| Field                                   | What it does                                                                                                                                     | Default          |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- |
| Search keywords                         | Job titles, skills or companies. One search per line; results are combined and de-duplicated.                                                    | `data scientist` |
| Location                                | City, region or country as written on the site (`Paris`, `Île-de-France`, `Spain`, `FR`, `Lyon, France`). Empty = everywhere.                    | `Paris`          |
| Site edition (language)                 | `fr`, `en`, `es`, `cz`, `sk`. Sets the language of benefits, country names and URLs (it does not filter by country).                             | `fr`             |
| Contract types                          | Any of: permanent/full-time, part-time, fixed-term, internship, apprenticeship, freelance, VIE, graduate program, volunteer, other. Empty = all. | all              |
| Remote work                             | `any`, `fulltime` (fully remote), `partial` (includes occasional remote), `none`.                                                                | `any`            |
| Maximum jobs                            | Stop after this many jobs across all keywords.                                                                                                   | `100`            |
| Include full job descriptions           | Adds the full description (HTML + text). Charged as the add-on above.                                                                            | on               |
| Include company website and description | Adds the company website and its own description.                                                                                                | on               |
| Posted within (days)                    | Only jobs published in the last N days (`1` for daily alerts). `0` = any time.                                                                   | `0`              |
| Start URLs                              | Optional Welcome to the Jungle job pages or job-search pages to scrape as well.                                                                  | none             |
| Proxy configuration                     | Residential proxies by default (fewest blocks). Falls back to the standard Apify Proxy automatically if residential isn't available.             | Residential      |
| Max concurrency / retries               | Advanced tuning.                                                                                                                                 | `5` / `5`        |

The default input returns results straight away, with zero edits.

### How to use it

#### Apify Console (no code)

Click **Start**, wait about 30 seconds, then download results as **Excel, CSV, JSON or HTML** from the **Output** tab.

#### API (cURL)

Replace `USERNAME` with the publisher's Apify username shown in the actor's URL, and `YOUR_TOKEN` with your [Apify API token](https://console.apify.com/settings/integrations).

```bash
curl -X POST "https://api.apify.com/v2/acts/USERNAME~welcome-to-the-jungle-jobs-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchQueries":["product manager"],"location":"Lyon","remote":"partial","maxJobs":50}'
```

#### Python

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("USERNAME/welcome-to-the-jungle-jobs-scraper").call(run_input={
    "searchQueries": ["data engineer"],
    "location": "Barcelona",
    "country": "es",
    "contractTypes": ["full_time"],
    "maxJobs": 200,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], "|", job["company"]["name"], "|", job["salary"])
```

#### Node.js

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

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('USERNAME/welcome-to-the-jungle-jobs-scraper').call({
  searchQueries: ['frontend developer', 'React'],
  location: 'Paris',
  postedWithinDays: 7,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(`${items.length} jobs`, items[0]);
```

#### Make (daily job alert → Slack or email)

1. **Schedule:** a Make scenario that runs every day at 8:00.
2. **Apify → Run an Actor:** pick this actor, turn **Run synchronously** on, and use this input:
   `{"searchQueries":["data scientist"],"location":"Paris","postedWithinDays":1,"maxJobs":100}`
3. **Apify → Get Dataset Items:** use the `defaultDatasetId` from step 2.
4. **Iterator** over the items, then optionally a **Filter** (e.g. `salary.min` is greater than 50000).
5. **Slack → Create a Message** (or Gmail → Send an Email): `{{title}} at {{company.name}} ({{location.city}}): {{url}}`.

Because `postedWithinDays = 1`, each morning you get only the jobs published in the last 24 hours, usually for a few cents.

#### n8n (same recipe)

1. **Schedule Trigger:** every day at 08:00.
2. **Apify node → Run an Actor and get dataset:** actor `USERNAME/welcome-to-the-jungle-jobs-scraper`, with the same JSON input as above.
3. **IF / Filter node:** e.g. `{{ $json.location.remote }}` equals `fulltime`.
4. **Slack / Telegram / Send Email node:** message `{{ $json.title }} — {{ $json.company.name }} — {{ $json.url }}`.

No Apify node available? Use an **HTTP Request** node with `POST` to the `run-sync-get-dataset-items` URL from the cURL example.

#### MCP (Claude, Cursor and other AI agents)

Add the Apify MCP server with this actor enabled, and your AI assistant can search jobs on demand ("find remote data engineering jobs in Spain posted this week"):

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com/?actors=USERNAME/welcome-to-the-jungle-jobs-scraper",
      "headers": { "Authorization": "Bearer YOUR_TOKEN" }
    }
  }
}
```

### Why not LinkedIn?

LinkedIn has more jobs, but scraping it is expensive and fragile:

- **Cost:** LinkedIn scrapers need residential proxies and often logged-in accounts, so they typically cost several times more per job.
- **Blocking and bans:** LinkedIn aggressively rate-limits and blocks automated traffic, and accounts used for scraping get restricted, so runs fail unpredictably.
- **Data quality:** many LinkedIn jobs have no salary, no remote policy and a truncated description behind a login.

Welcome to the Jungle is the go-to board for European startups, scale-ups and employer-branded corporates. Its listings are structured, so salary, remote policy, contract type, education level, benefits and company size come as real fields instead of free text. This scraper uses the same public search the website uses, over plain HTTP, which makes it fast (100 jobs in about 30 seconds), cheap and reliable. Many teams run both: LinkedIn for breadth, Welcome to the Jungle for richer, cheaper European data.

### FAQ

**Why does a job have `"salary": { "min": null, … }`?**
The employer didn't publish a salary. Roughly 15–30% of listings include one, depending on country and role.

**Does "Site edition" limit results to that country?**
No. It only changes the language of translated fields (benefits, country names) and of the job URLs. Use **Location** to restrict by country, e.g. `Spain` or `ES`.

**My location returns nothing.**
The log suggests close matches (e.g. "Did you mean: Paris?"). Use the city, region or country name as it appears on the site, or a 2-letter country code.

**How many jobs can I get?**
Up to 100,000 per run. Beyond 1,000 results for a single keyword, the scraper automatically splits the search by publication date to get past the site's 1,000-results-per-search limit.

**Is the data real-time?**
Yes. Each run queries the live site.

**Some jobs have `detailsScraped: false`.**
The job's detail page couldn't be fetched after several retries (usually temporary blocking). The job is still delivered with everything available from search results, but without the description, apply link or company website. You're not charged the description add-on for these jobs. The actor uses residential proxies by default to keep this rare. If you changed the proxy to another group, switch back to `RESIDENTIAL`.

**What does `experienceLevel` / `educationLevel` mean?**
They are the site's codes, e.g. `less_than_6_months`, `5_to_7_years`; `bac_3` = bachelor's degree, `bac_5` = master's degree, `phd`.

### Limitations

- Only publicly listed jobs are collected. Expired or unpublished jobs disappear from results.
- Salary, experience and education are included only when the employer provides them.
- Descriptions are in the language the employer wrote them in (the site does not translate them).
- If the site changes its structure, the scraper may need an update. Issues are usually fixed within a few days. Please report them in the Issues tab.

### Legal and responsible use

This actor collects **publicly available job listings and company information** only. It does not log in, does not collect candidate data, and does not collect data behind a login. Job listings can occasionally contain a recruiter's name or contact details. If you store or process them, you are responsible for doing so lawfully under the GDPR and other applicable laws (legitimate interest, data minimisation, retention limits). You are also responsible for making sure your use complies with Welcome to the Jungle's terms of use and the laws of your jurisdiction. If you're unsure whether your use case is legitimate, consult a lawyer. Please scrape at a reasonable pace and don't republish content in a way that infringes the rights of employers or the site.

# Changelog

This Actor's version history is a separate document: https://apify.com/yasaslive/job-board-scraper/changelog.md

# Actor input Schema

## `searchQueries` (type: `array`):

Job titles, skills or companies to search for — one search per line (e.g. "data scientist", "product manager", "React"). Results from all keywords are combined and de-duplicated.

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

City, region or country as written on the site, e.g. "Paris", "Lyon", "Île-de-France", "Barcelona", "Spain" or "FR". Leave empty to search everywhere.

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

Which Welcome to the Jungle edition to use. It sets the language of benefit names, country names and job URLs. It does NOT limit results to a country — use Location for that.

## `contractTypes` (type: `array`):

Only return these contract types. Leave empty for all.

## `remote` (type: `string`):

Filter by remote-work policy. "Partial" includes jobs with occasional remote days.

## `maxJobs` (type: `integer`):

Stop after this many jobs (across all keywords). You are only charged for jobs you receive.

## `scrapeDescriptions` (type: `boolean`):

Adds the full description (HTML and plain text) to every job. Charged as a small add-on per job (see Pricing).

## `scrapeCompany` (type: `boolean`):

Adds the company website and the company's own description. Company name, sector, size and logo are always included.

## `postedWithinDays` (type: `integer`):

Only jobs published in the last N days, e.g. 1 for a daily alert or 7 for a weekly one. 0 = any time.

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

Welcome to the Jungle job pages (…/companies/<company>/jobs/<job>) or job search pages (…/jobs?query=…) to scrape in addition to the keywords above. Clear the keywords to scrape only these URLs.

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

Residential proxies are used by default because the site's bot protection blocks datacenter IPs much more often. If your account can't use residential proxies, the actor falls back to the default Apify Proxy automatically.

## `maxConcurrency` (type: `integer`):

How many job pages are fetched in parallel. Higher is faster but more likely to be rate-limited.

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

How many times a failed or blocked request is retried (with a fresh session and exponential backoff) before giving up.

## Actor input object example

```json
{
  "searchQueries": [
    "data scientist"
  ],
  "location": "Paris",
  "country": "fr",
  "contractTypes": [],
  "remote": "any",
  "maxJobs": 100,
  "scrapeDescriptions": true,
  "scrapeCompany": true,
  "postedWithinDays": 0,
  "startUrls": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxConcurrency": 5,
  "maxRequestRetries": 5
}
```

# Actor output Schema

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

All scraped jobs (JSON, CSV, Excel via the dataset API).

## `jobsFull` (type: `string`):

No description

## `stats` (type: `string`):

Request/error counters by category and whether the budget limit was reached.

# 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 = {
    "searchQueries": [
        "data scientist"
    ],
    "location": "Paris",
    "country": "fr",
    "contractTypes": [],
    "remote": "any",
    "maxJobs": 100,
    "scrapeDescriptions": true,
    "scrapeCompany": true,
    "postedWithinDays": 0,
    "startUrls": [],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "maxConcurrency": 5,
    "maxRequestRetries": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("yasaslive/job-board-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 = {
    "searchQueries": ["data scientist"],
    "location": "Paris",
    "country": "fr",
    "contractTypes": [],
    "remote": "any",
    "maxJobs": 100,
    "scrapeDescriptions": True,
    "scrapeCompany": True,
    "postedWithinDays": 0,
    "startUrls": [],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "maxConcurrency": 5,
    "maxRequestRetries": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("yasaslive/job-board-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 '{
  "searchQueries": [
    "data scientist"
  ],
  "location": "Paris",
  "country": "fr",
  "contractTypes": [],
  "remote": "any",
  "maxJobs": 100,
  "scrapeDescriptions": true,
  "scrapeCompany": true,
  "postedWithinDays": 0,
  "startUrls": [],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxConcurrency": 5,
  "maxRequestRetries": 5
}' |
apify call yasaslive/job-board-scraper --silent --output-dataset

```

## MCP server setup

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