# Jobs.lu Scraper — Luxembourg Job Listings & Employer Data (`studio-amba/jobs-lu-scraper`) Actor

Scrape job listings, employers, locations, salaries, and posting dates from Jobs.lu, one of Luxembourg's main commercial job boards. No login, no browser needed.

- **URL**: https://apify.com/studio-amba/jobs-lu-scraper.md
- **Developed by:** [Studio Amba](https://apify.com/studio-amba) (community)
- **Categories:** Jobs
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 result 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/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

## Jobs.lu Scraper

Extract **job listings from [Jobs.lu](https://en.jobs.lu/)**, one of Luxembourg's two main commercial job boards. Luxembourg's labour market is unusually international — the country runs on cross-border commuters from France, Belgium, and Germany, and jobs.lu carries postings for all of it, in French, German, and English on the same site.

This Actor pulls **job titles, employers, locations, employment types, salaries (when disclosed), posting dates, and full descriptions** by reading the site's search results and job detail pages directly. No login, no cookies, no browser automation — jobs.lu is server-rendered HTML with no anti-bot protection, so runs are fast and stable.

### What does Jobs.lu Scraper do?

Give it a keyword (French works best — the board is mostly French-language, e.g. `comptable`, `commercial`, `informaticien`) and, optionally, a location. The Actor searches jobs.lu, paginates through the results, visits each job's detail page for the full description and metadata, and returns clean, structured records you can download as JSON, CSV, Excel, or HTML — or pull through the Apify API into your own systems.

Because it runs on the Apify platform, you also get scheduling, monitoring, proxy rotation, and integrations (webhooks, Make, Zapier, Google Sheets) out of the box.

### Why use Jobs.lu Scraper?

- **A genuinely international job market.** Luxembourg has one of the highest shares of cross-border and foreign workers in Europe — jobs.lu reflects that with postings in French, German, and English, and a real minority of roles based just across the border in France, Belgium, or Germany.
- **Labour-market research.** Track hiring demand by sector, employer, or location in a small, dense, high-income economy.
- **Recruitment and sourcing.** Build lead lists of Luxembourg employers actively hiring for a role.
- **Salary benchmarking.** Salaries are disclosed on a meaningful share of postings — useful for compensation research in a market where pay tends to run higher than neighbouring countries.
- **Job aggregators.** Feed a niche job site, newsletter, or Luxembourg-focused product with fresh, structured listings.
- **No anti-bot headaches.** The site is plain server-rendered HTML with no CAPTCHA or bot-detection layer, so runs are reliable.

### How to scrape Jobs.lu data

1. Open the Actor and go to the **Input** tab.
2. Enter a **Search Query** (e.g., `comptable`, `commercial`, `chauffeur`, `informaticien`). French terms return the most results — the board is mostly French-language.
3. Optionally set a **Location** (e.g., `Luxembourg`, `Esch-sur-Alzette`, `Wiltz`) to narrow results to that area.
4. Set **Max Results** to control how many listings you want.
5. Click **Start**. When the run finishes, open the **Output** tab and download your data as JSON, CSV, Excel, or HTML.

That's it. You can also schedule the Actor to run daily and be notified of new listings.

### Input

| Field | Type | Description |
| --- | --- | --- |
| `searchQuery` | string | Keyword to search for. Defaults to `comptable`. French terms return the most results. |
| `location` | string | City or area (e.g., `Luxembourg`, `Esch-sur-Alzette`). Leave empty to search all locations. |
| `maxResults` | integer | Maximum number of listings to return (default 100). |
| `proxyConfiguration` | object | Proxy settings. Jobs.lu has no anti-bot protection, so Apify datacenter proxies work well. |

#### Example input

```json
{
  "searchQuery": "comptable",
  "location": "Luxembourg",
  "maxResults": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

### Output

Each job is returned as one dataset record. You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

#### Example output item

```json
{
  "title": "Comptable expérimenté (m/f/d) - Experienced Accountant",
  "company": "GSL Fiduciaire",
  "locationRaw": "Esch-sur-Alzette (LU)",
  "locationParsed": {
    "city": "Esch-sur-Alzette",
    "country": "Luxembourg"
  },
  "language": "fr",
  "postedAt": "2026-08-04T00:00:00.000Z",
  "descriptionSnippet": "Nous recrutons: Comptable expérimenté(e) (m/f/d) Nous recherchons un(e) professionnel(le) de la comptabilité prêt(e) à gérer un portefeuille de clients variés avec rigueur et expertise. Si vous appréciez l'autonomie, le contact client et les défis, cette opportunité est faite pour vous !…",
  "url": "https://en.jobs.lu/ApplyForJob.aspx?Id=286045",
  "employmentType": "Permanent, Full Time",
  "salaryRaw": null,
  "source": "jobs.lu",
  "scrapedAt": "2026-08-04T17:24:21.900Z",
  "jobId": "286045"
}
```

#### Data fields

| Field | Description |
| --- | --- |
| `title` | Job posting title (from the full, untruncated detail page) |
| `company` | Hiring company or organisation |
| `locationRaw` | Location exactly as shown on jobs.lu |
| `locationParsed.city` | Best-effort parsed city name |
| `locationParsed.country` | Parsed country — Luxembourg by default, or France/Belgium/Germany for cross-border postings |
| `language` | ISO 639-1 language code detected from the posting text (`fr`, `de`, or `en`) |
| `postedAt` | ISO 8601 date the posting was last updated, or `null` |
| `descriptionSnippet` | Plain-text description excerpt (300–400 characters, HTML stripped) |
| `url` | Absolute URL to the job posting |
| `employmentType` | Contract type and hours as the source expresses them (e.g. `Permanent, Full Time`) |
| `salaryRaw` | Salary text when disclosed, otherwise `null` — most postings don't disclose salary |
| `source` | Always `jobs.lu` |
| `scrapedAt` | ISO 8601 collection timestamp |
| `jobId` | jobs.lu internal job reference id, used for de-duplication |

### How much does it cost to scrape Jobs.lu?

The Actor reads plain server-rendered HTML pages (one listing request per 40 results, plus one detail-page request per job), so runs are cheap and predictable. A run of 100 results makes roughly 100 detail-page requests plus a handful of listing pages, and typically finishes in well under a minute. Pricing is pay per result on the Apify Store — check the pricing box on the Actor page for the current cost estimate per result.

**Note on usage cost:** a run's usage cost only settles once the run's status reaches `SUCCEEDED` — reading the dataset mid-run, or right after the actor finishes but before the run status updates, will undercount cost. Wait for `SUCCEEDED` before relying on the reported cost.

### Limitations

- The board is mostly French-language; German and English postings exist but are a minority. Search with French keywords for the widest coverage.
- Salary is disclosed on only a fraction of postings — treat `salaryRaw: null` as "not disclosed," not as "no salary."
- `language` and `locationParsed.country` are detected/parsed from the page text, not exposed as structured fields by the source, so edge cases (rare non-EU location names, heavily bilingual titles) can occasionally be misclassified.
- Employment type reflects whatever combination of "Contract Type" and "Hours" the recruiter selected — Luxembourg commercial job boards skew heavily toward permanent, full-time roles.

### Tips and advanced options

- **Combine query + location.** Both are matched as free text against jobs.lu's own search, so `searchQuery: "comptable"` + `location: "Esch-sur-Alzette"` narrows results the same way typing both into the site's search box would.
- **Cross-border roles.** Search without a location to catch postings based just across the border in France, Belgium, or Germany, then filter on `locationParsed.country` after the run.
- **Fresh listings only.** Sort is newest-first by default — schedule the Actor daily and de-duplicate downstream by `jobId` to build a rolling feed of new postings.
- **Deduplication.** Results are de-duplicated by job id within a run.

### Related Scrapers

Jobs.lu sits at the crossroads of three national labour markets. Pair it with our public employment service scrapers for the neighbouring countries:

- [France Travail Scraper](https://apify.com/studio-amba/francetravail-scraper) — France's public employment service, covering the Lorraine region across the border.
- [VDAB Scraper](https://apify.com/studio-amba/vdab-scraper) — Flanders' (Belgium) public employment service.
- [Arbeitsagentur Scraper](https://apify.com/studio-amba/arbeitsagentur-scraper) — Germany's Federal Employment Agency, covering Rheinland-Pfalz and Saarland across the border.

### FAQ, disclaimers, and support

**Is scraping Jobs.lu legal?** This Actor reads publicly available job listing pages that jobs.lu serves to any visitor without a login. Use the data responsibly and in line with applicable terms and data-protection law. You are responsible for how you use the extracted data.

**Do I need an account or API key?** No. The Actor handles access for you. No login or cookies are required.

**Why is `salaryRaw` null on most jobs?** Most Luxembourg employers don't disclose salary in the posting itself. The Actor only fills `salaryRaw` when jobs.lu shows a specific figure or range.

**Something's not working.** Open an issue on the Actor's **Issues** tab with your input and the run link. Custom fields or a tailored version can also be arranged.

# Actor input Schema

## `searchQuery` (type: `string`):

Keyword to search for. The board is mostly French-language. Examples: 'comptable' (accountant), 'commercial' (sales), 'informaticien' (IT), 'chauffeur' (driver).

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

Filter by city or area (e.g., 'Luxembourg', 'Esch-sur-Alzette', 'Wiltz'). Leave empty to search all locations. Matched as part of the search text alongside the keyword.

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

Maximum number of job listings to return.

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

Proxy settings. Jobs.lu has no anti-bot protection, but its detail pages occasionally time out through Apify's shared datacenter pool — Belgium-pinned residential proxies proved fastest and most reliable during testing.

## Actor input object example

```json
{
  "searchQuery": "comptable",
  "maxResults": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "BE"
  }
}
```

# Actor output Schema

## `results` (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 = {
    "searchQuery": "comptable",
    "maxResults": 5,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "BE"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studio-amba/jobs-lu-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 = {
    "searchQuery": "comptable",
    "maxResults": 5,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "BE",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("studio-amba/jobs-lu-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 '{
  "searchQuery": "comptable",
  "maxResults": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "BE"
  }
}' |
apify call studio-amba/jobs-lu-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,studio-amba/jobs-lu-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/jTaOJzWWXTyTxZbvX/builds/Vgzz6BfwYslddhH03/openapi.json
