# Randstad Scraper: Job Listings in 6 European Countries (`studio-amba/randstad-scraper`) Actor

Scrape job listings from Randstad in Belgium, the Netherlands, Germany, France, Italy and Poland, the world's largest staffing agency. Search by keyword in the local language. No login or cookies required.

- **URL**: https://apify.com/studio-amba/randstad-scraper.md
- **Developed by:** [Studio Amba](https://apify.com/studio-amba) (community)
- **Categories:** Jobs
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 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/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

## Randstad Scraper — Belgium, Netherlands, Germany, France, Italy & Poland

Extract job listings from Randstad in [Belgium](https://www.randstad.be), the [Netherlands](https://www.randstad.nl), [Germany](https://www.randstad.de), [France](https://www.randstad.fr), [Italy](https://www.randstad.it) and [Poland](https://www.randstad.pl), the world's largest staffing agency, as clean structured JSON. Pick a country, search by keyword, and get job titles, locations, employment type, posting dates and description excerpts in one run.

No login. No cookies. No browser automation. The actor reads the same data each country's site publishes server-side — Belgium's embedded search-page job data, and for every other country the job sitemap plus each listing's structured job-posting data — so it is fast and stable.

### What this actor does

Given a country and a search keyword, this actor collects the matching job listings from that Randstad site and returns one row per job with:

- Job title
- Hiring company (Randstad, plus the end client when disclosed — Belgium only)
- Location (city, province)
- Language of the posting
- Employment type (contract type and working hours)
- Posting date
- A 300–400 character description excerpt
- The job's unique ID and direct URL

Randstad Belgium publishes listings in Dutch, French and English — you can search with keywords in any of the three languages and choose which language version of the site to crawl. The other countries publish in their local language only.

### Why use it

- **Staffing market research** — track what roles Randstad is placing, and where, across two of its largest European markets.
- **Recruitment and sourcing** — build a live feed of temp and permanent positions for a role or region.
- **Competitive intelligence** — compare Randstad's open roles against VDAB/Le Forem/Actiris (Belgium) or UWV WERKbedrijf (Netherlands) public listings.
- **Aggregators and job boards** — feed Randstad staffing-agency listings into your own product.

### How to scrape Randstad job data

1. Open the actor and pick a **Country** — `Belgium`, `Netherlands`, `Germany`, `France`, `Italy` or `Poland`.
2. Set a **Search Query**. For Belgium this hits the site's own search engine (e.g. `warehouse`, `magazijnier`, `comptable`, `logistiek`). For every other country this actor matches your keyword against each live job's URL slug — job-title words in the local language work best (`logistiek` NL, `lager` DE, `technicien` FR, `magazziniere` IT, `pracownik` PL).
3. Optionally set a **Location filter** such as `Antwerp`, `Brussels`, `Liège` or `Utrecht` to narrow results to that city or province.
4. For Belgium, pick a **Language** (`en`, `nl` or `fr`) — this selects which language version of the site to crawl. Ignored for every other country.
5. Set **Max Results** (up to 30 per run — see Limitations).
6. Leave the proxy on its default residential setting and click **Start**.

For Belgium, the actor fetches the search page for your query and language, extracts the job data embedded in the page, applies your location filter if set, and writes everything to the dataset. For the other countries, it fetches the site's public job sitemap (every currently live vacancy), keeps the ones whose URL matches your keyword, then fetches each matching job's own page for its full structured data. Either way you can export the result as JSON, CSV or Excel.

### Input

| Field | Type | Description |
|-------|------|-------------|
| `country` | string | `be`, `nl`, `de`, `fr`, `it` or `pl`. Default: `be`. |
| `searchQuery` | string | Keyword to search for. Belgium: English, Dutch or French all work against the site's own search. Other countries: matched against each job's URL slug — keywords in the local language work best. Default: `warehouse`. |
| `location` | string | Optional city or province filter, applied client-side (e.g. `Antwerp`, `Liège`, `Utrecht`). |
| `language` | string | Belgium only: site language to crawl (`en`, `nl` or `fr`). Default: `en`. |
| `maxResults` | integer | Maximum number of listings to return (max 30 — see Limitations). Default: `30`. |
| `proxyConfiguration` | object | Apify proxy settings. Default: Belgian residential. |

#### Example input (Belgium)

```json
{
    "country": "be",
    "searchQuery": "warehouse",
    "location": "Antwerp",
    "language": "en",
    "maxResults": 30,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "BE"
    }
}
```

#### Example input (Netherlands)

```json
{
    "country": "nl",
    "searchQuery": "logistiek",
    "location": "Utrecht",
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "NL"
    }
}
```

### Output

Each dataset item looks like this:

```json
{
    "title": "Sewer",
    "company": "Randstad",
    "locationRaw": "Ronse, East Flanders",
    "locationParsed": { "city": "Ronse", "country": "Belgium" },
    "language": "en",
    "postedAt": "2026-08-04T13:56:13.636546466Z",
    "descriptionSnippet": "Are you looking for a stable job with daytime hours at a top company in Ronse? As a sewer, you will manufacture industrial insulation jackets using a sewing machine and enjoy a wage of €14.4939, meal vouchers, and an option for a permanent contract...",
    "url": "https://www.randstad.be/en/candidates/jobs/sewer_ronse_5c298002-967d-47ca-b556-313982d6f0ed/",
    "employmentType": "Permanent, Full-Time",
    "source": "randstad.be",
    "scrapedAt": "2026-08-04T17:16:12.303Z",
    "jobId": "5c298002-967d-47ca-b556-313982d6f0ed",
    "category": "Goods And Materials Production"
}
```

A Netherlands result has the same shape, sourced from that job's own page:

```json
{
    "title": "Logistiek medewerker fulltime",
    "company": "Randstad",
    "locationRaw": "Utrecht, Utrecht",
    "locationParsed": { "city": "Utrecht", "country": "Netherlands" },
    "language": "nl",
    "postedAt": "2026-09-26T00:00:00.000Z",
    "descriptionSnippet": "Ben jij op zoek naar een dynamische baan in de logistiek? Als logistiek medewerker ga jij aan de slag met het in- en uitladen van vrachtwagens en het verwerken van bestellingen...",
    "url": "https://www.randstad.nl/vacatures/753739/logistiek-medewerker-fulltime",
    "employmentType": "TEMPORARY, PART_TIME, 8 - 16 uur per week",
    "source": "randstad.nl",
    "scrapedAt": "2026-09-27T19:40:00.000Z",
    "jobId": "753739"
}
```

#### Output fields

| Field | Type | Description |
|-------|------|-------------|
| `title` | string | Job title |
| `company` | string | Hiring company as posted — Randstad itself unless a client is disclosed (Belgium only) |
| `clientCompany` | string | Randstad's end client, when disclosed on the listing (Belgium only) |
| `locationRaw` | string | Location as shown on the listing |
| `locationParsed` | object | `{ city, country }` |
| `language` | string | ISO 639-1 language code of the posting (`en`, `nl` or `fr`) |
| `postedAt` | string | ISO 8601 posting timestamp |
| `descriptionSnippet` | string | 300–400 character plain-text excerpt of the description |
| `url` | string | Full job listing URL |
| `employmentType` | string | Contract type and working hours |
| `salaryRaw` | string | Raw salary data, when disclosed (rare) |
| `source` | string | Source site identifier (`randstad.be` or `randstad.nl`) |
| `scrapedAt` | string | ISO 8601 timestamp of collection |
| `jobId` | string | Unique Randstad job identifier |
| `category` | string | Randstad's internal job classification category (Belgium only — not published on Netherlands listings) |

### Cost estimate

Belgium runs make a single lightweight HTTP request — no browser, no per-item detail fetches. Runs for the other countries make a few requests for the job sitemaps plus one request per matched job (up to `maxResults`), since the full job data only lives on each job's own page. Either way there's no browser involved. Runs typically finish in well under a minute; cost is dominated by proxy traffic plus the per-result charge. Usage is only charged after a run finishes with status SUCCEEDED — a failed or aborted run doesn't bill for partial results.

### Limitations

- **Six countries.** Belgium, Netherlands, Germany, France, Italy and Poland. Randstad Spain disallows all crawling in its robots.txt and is not included.
- **Up to 30 results per query.** Belgium's search page renders a fixed batch of results with no working server-side pagination parameter (page/offset/pageNumber query params were all tested live and had no effect). The sitemap countries are capped the same way for consistency. Run multiple searches with different keywords for broader coverage.
- **Search in the sitemap countries matches URL slugs, not a real search index.** This actor matches your keyword against each live job's URL slug (drawn from the site's own job sitemap), which is a good proxy for job-title keywords but won't find matches based on description text alone.
- **Location filtering is client-side.** Neither country's search page exposes a working location query parameter, so the `location` input filters the fetched results by substring match rather than querying the site directly.
- **Salary is rarely disclosed.** Most listings on both sites don't publish pay rates; `salaryRaw` is empty for the large majority of jobs.
- **Client company and category are Belgium-only fields.** Randstad Netherlands' job-posting data doesn't include an end-client name or an internal category classification the way Belgium's does.

### Related scrapers

Building a European staffing and public-employment dataset? These sibling actors follow the same clean-JSON approach:

- **VDAB Scraper** — Flemish public employment service
- **Le Forem Scraper** — Walloon public employment service
- **Actiris Scraper** — Brussels public employment service
- **StepStone Scraper** — Belgian commercial job board
- **Jobat Scraper** — Belgian job board

### Disclaimer

This actor collects publicly available data from Randstad Belgium and Randstad Netherlands for legitimate research and aggregation use. Respect Randstad's terms of service and applicable data protection law when using the output.

# Actor input Schema

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

Which Randstad country site to scrape. Belgium uses the site's own search. The other five countries match the keyword against the URL of every live job in the site's job sitemap — see the searchQuery description below.

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

Keyword to search for (e.g. 'warehouse', 'magazijnier', 'comptable', 'logistiek'). For Belgium this queries the site's own search engine (English, Dutch or French all work). For the other countries this matches the keyword against each live job's URL slug — best with a job-title word in the local language, for example 'logistiek' (NL), 'lager' (DE), 'technicien' (FR), 'magazziniere' (IT) or 'pracownik' (PL).

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

Optional city or province to filter results by (e.g. 'Antwerp', 'Brussels', 'Liège', 'Utrecht'). Applied client-side after fetching — neither country's search page exposes a working server-side location parameter.

## `language` (type: `string`):

Site language to crawl. Belgium-only — Randstad Belgium publishes jobs in Dutch, French and English depending on region. Ignored for every other country (those postings are in the local language).

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

Maximum number of job listings to return. Capped at 30 per run — Belgium's search page renders a fixed batch with no working pagination parameter, and every other country fetches one detail page per matched job so a high value means more requests. Run multiple searches with different keywords for broader coverage.

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

Proxy settings. A residential proxy in the matching country is recommended for reliability — the actor defaults to one automatically based on the Country field if you leave this untouched.

## Actor input object example

```json
{
  "country": "be",
  "searchQuery": "warehouse",
  "language": "en",
  "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": "warehouse",
    "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/randstad-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": "warehouse",
    "maxResults": 5,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "BE",
    },
}

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

```

## MCP server setup

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