# Michael Page Scraper — Multi-Country White-Collar Jobs (`studio-amba/michael-page-scraper`) Actor

Scrape white-collar, finance and legal job listings from Michael Page (PageGroup) across France, UK, Ireland, Germany, Austria, Switzerland, Spain, Italy, Portugal and Poland. No login or cookies required.

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

## Michael Page Scraper — Multi-Country White-Collar Jobs

Extract white-collar, finance and legal job listings from [Michael Page](https://www.michaelpage.fr) (part of PageGroup), one of the world's largest specialist recruitment agencies, as clean structured JSON — across ten European markets from a single actor. Pick a market, search by keyword, and get job titles, locations, salary, contract type, posting dates and the full job description.

No login. No cookies. No browser automation. Michael Page's country sites all run the same server-rendered platform, so this actor reads the same HTML the site itself serves.

### What this actor does

Michael Page runs a single Drupal-based platform across its country sites, with one job-search listing per market and a job-detail page that carries clean `schema.org JobPosting` structured data (title, full description, salary, employment type, location). This actor walks the listing pages for your chosen market and search, then fetches the structured detail page for each job, returning one row per listing with:

- Job title
- Location (city, region, country)
- Contract type (local label, e.g. `CDI`, `Interim`, `Permanent`) and structured employment type
- Salary range and currency, when disclosed
- Posting date
- The full job description (not just a snippet)
- Michael Page's own job-detail URL

### Supported markets

| Input value | Market |
|---|---|
| `FR` | France — default |
| `UK` | United Kingdom |
| `IE` | Ireland |
| `DE` | Germany |
| `AT` | Austria |
| `CH` | Switzerland |
| `ES` | Spain |
| `IT` | Italy |
| `PT` | Portugal |
| `PL` | Poland |

Sweden (`michaelpage.se`) was investigated and left out: the platform is identical, but live inventory at build time was only a handful of open jobs total — not enough to be a useful market on its own. All ten markets above were verified live against real, current listings before shipping.

### Why use it

- **Pan-European white-collar recruitment research** — track what roles Michael Page is placing across ten markets from one tool instead of ten.
- **Finance, legal and management job market benchmarking** — Michael Page specializes in office/finance/legal roles rather than blue-collar staffing, so listings skew toward accounting, banking, HR, legal, supply chain and management positions.
- **Salary benchmarking** — a large share of Michael Page listings disclose a salary range and currency, useful for comparing pay bands across countries.
- **Full job descriptions**, not truncated snippets — every result includes the complete requirements/responsibilities text from the job's own structured data.

### How to scrape Michael Page data

1. Open the actor and pick a **Market** (e.g. `FR` for France).
2. Set a **Search Query** in the local language (e.g. `comptable` for France, `accountant` for UK/Ireland/Switzerland, `buchhalter` for Germany/Austria, `contable` for Spain, `contabile` for Italy, `contabilidade` for Portugal, `księgowy` for Poland). Leave it empty to use a sensible default for the selected market.
3. Optionally set a **Location filter** (e.g. `Lyon`, `Manchester`, `München`) to narrow results to a city or region — this filter is applied server-side by Michael Page and works reliably.
4. Set **Max Results** — each result costs one extra request to fetch its full job description and salary, so keep this proportional to how much detail you need.
5. Leave the proxy on the default automatic setting and click **Start**.

The actor pages through Michael Page's job-search listing (20-30 results per page) until it reaches your Max Results or runs out of listings, fetches the full structured detail page for each selected job, and writes everything to the dataset.

A note on search: Michael Page's `search` parameter is relevance-based, not a strict filter. On markets with a healthy inventory, an unmatched keyword can still return the market's closest-relevance listings rather than zero results — the `location` filter, by contrast, does filter strictly.

### Input

| Field | Type | Description |
|-------|------|-------------|
| `market` | string | Market to search. One of `FR`, `UK`, `IE`, `DE`, `AT`, `CH`, `ES`, `IT`, `PT`, `PL`. Default: `FR`. |
| `searchQuery` | string | Keyword in the local language. Empty = market-specific default. |
| `location` | string | Optional city/region filter, applied server-side by Michael Page. |
| `maxResults` | integer | Maximum number of listings to return (max 500). Default: `50`. |
| `proxyConfiguration` | object | Apify proxy settings. Default: automatic (no group). |

#### Example input

```json
{
    "market": "FR",
    "searchQuery": "comptable",
    "location": "",
    "maxResults": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
}
```

### Output

Each dataset item looks like this:

```json
{
    "jobTitle": "Comptable Fournisseurs (F/H)",
    "company": "Michael Page",
    "locationRaw": "Le Pontet",
    "locationParsed": { "city": "Le Pontet", "region": "France", "country": "France" },
    "market": "France",
    "language": "fr",
    "contractType": "Interim",
    "employmentType": "TEMPORARY",
    "salaryRaw": "€26.000 - €28.000 par an",
    "salaryMin": 26000,
    "salaryMax": 28000,
    "salaryCurrency": "EUR",
    "postedAt": "2026-06-24",
    "jobDescription": "Poste challengeant, Grand Groupe. Notre client, un grand groupe recherche un Comptable Fournisseur...",
    "summarySnippet": "Notre client recherche un·e Comptable Fournisseurs...",
    "url": "https://www.michaelpage.fr/job-detail/comptable-fournisseurs-fh/ref/jn-062026-7038168",
    "source": "michaelpage.fr",
    "scrapedAt": "2026-08-08T19:43:49.272Z",
    "jobId": "jn-062026-7038168"
}
```

#### Output fields

| Field | Type | Description |
|-------|------|-------------|
| `jobTitle` | string | Job title, as posted |
| `company` | string | Always `Michael Page` |
| `locationRaw` | string | Location as shown on the listing card |
| `locationParsed` | object | `{ city, region, country }`, from structured data |
| `market` | string | Country market, e.g. `France` |
| `language` | string | ISO 639-1 language code of the posting |
| `contractType` | string | Local contract type label, e.g. `CDI`, `Interim`, `Permanent` |
| `employmentType` | string | Structured employment type, e.g. `FULL_TIME`, `TEMPORARY` |
| `salaryRaw` | string | Raw salary text, when disclosed |
| `salaryMin` / `salaryMax` | integer | Parsed annual salary range, when disclosed |
| `salaryCurrency` | string | ISO currency code, when disclosed |
| `postedAt` | string | Posting date (YYYY-MM-DD) |
| `jobDescription` | string | Full plain-text job description |
| `summarySnippet` | string | Short summary shown on the listing card |
| `url` | string | Michael Page's own job-detail page URL |
| `source` | string | Source domain, e.g. `michaelpage.fr` |
| `scrapedAt` | string | ISO 8601 timestamp of collection |
| `jobId` | string | Michael Page's job reference code |

### Cost estimate

Each run makes one listing-page request per 20-30 results plus one detail-page request per result (to fetch the full description and salary). Both are lightweight HTML requests, no browser — a 50-result run is around 52 requests total plus proxy traffic. Usage is only charged after a run finishes with status SUCCEEDED — a failed or aborted run doesn't bill for partial results.

### Limitations

- **Sweden is not covered.** `michaelpage.se` runs the identical platform but had only a handful of live listings at build time — not enough inventory to be a useful standalone market.
- **Search is relevance-based, not a strict filter.** An unmatched or misspelled keyword can still return a full page of Michael Page's "closest relevance" listings for that market rather than zero results. Use the `location` filter for strict filtering.
- **Salary is not always disclosed.** Some markets (notably Germany) publish salary far less often than others (notably France) — `salaryMin`/`salaryMax`/`salaryCurrency` are `null` when not shown.
- **Switzerland defaults to the English-language site.** `michaelpage.ch` also serves German (`/de`) and French (`/fr`) subpaths; this actor uses the default English domain for a single consistent Swiss endpoint.

### Related scrapers

Building a European staffing dataset? These sibling actors follow the same clean-structured-data approach:

- **Adecco Scraper** — global staffing agency, multi-country
- **Hays Scraper** — white-collar recruitment, UK-based
- **Randstad Belgium Scraper** — world's largest staffing agency, Belgian market
- **VDAB Scraper** — Flemish public employment service
- **StepStone Scraper** — Belgian commercial job board

### Disclaimer

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

# Actor input Schema

## `market` (type: `string`):

Michael Page country site to search.

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

Keyword to search for, in the local language (e.g. 'comptable' for France, 'accountant' for UK/Ireland/Switzerland, 'buchhalter' for Germany/Austria, 'contable' for Spain, 'contabile' for Italy, 'contabilidade' for Portugal, 'księgowy' for Poland). Leave empty to use a sensible default for the selected market. Note: Michael Page's search is relevance-based, not a strict filter — an unmatched keyword can still return the market's closest-relevance listings rather than zero results.

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

Optional city or region to filter results by (e.g. 'Lyon', 'Manchester', 'München'). This filter is applied server-side by Michael Page and works reliably, unlike the search keyword.

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

Maximum number of job listings to return. Each result triggers one extra request to fetch the full job description and salary, so larger values take proportionally longer.

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

Proxy settings. Cloudflare is present on Michael Page's sites but passive at normal request rates — Apify's automatic proxy works fine; only switch to a residential group if you hit blocks at high volume.

## Actor input object example

```json
{
  "market": "FR",
  "searchQuery": "comptable",
  "maxResults": 10,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# 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": 10,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studio-amba/michael-page-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": 10,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

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

```

## MCP server setup

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