# Morocco Public Jobs & Concours Scraper (`scraper_guru/emploi-public-morocco-scraper`) Actor

Scrape Emploi-Public Morocco concours with jobs, deadlines, grades, vacancies, specialties, administrations, application methods, and official PDFs.

- **URL**: https://apify.com/scraper\_guru/emploi-public-morocco-scraper.md
- **Developed by:** [LIAICHI MUSTAPHA](https://apify.com/scraper_guru) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 public concours 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/actors/running/actors-in-store.md#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

## Morocco Public Jobs & Concours Scraper

Scrape structured public-sector recruitment competitions from **Emploi-Public.ma**, Morocco's official public employment portal. Collect administrations, grades, vacancies, specialties, deadlines, exam dates, application methods, concours codes, and official document links.

### Features

- Extracts current Moroccan public-sector concours and recruitment announcements
- Enriches each result from its official detail page
- Captures application deadlines, exam dates, publication dates, and vacancy counts
- Collects grades, pay scales, specialties, recruitment types, and concours codes
- Identifies announcements, written-exam invitations, oral invitations, results, cancellations, and postponements
- Includes links to opening decrees, invitations, results, and other official documents
- Supports keyword, stage, administration type, region, and custom URL inputs
- Handles pagination, duplicate concours IDs, retries, rate limits, and optional proxies
- Produces a structured dataset view and a `STATS` run summary

### Use Cases

- **Public job monitoring:** track newly published Moroccan government competitions
- **Candidate alerts:** identify approaching application deadlines and exam dates
- **Recruitment analytics:** compare public-sector demand by administration, grade, or specialty
- **Document collection:** gather official concours notices and result-document URLs
- **Labor-market research:** analyze public vacancies and hiring activity over time
- **Automation:** schedule recurring runs and send new concours to email, Slack, Sheets, or a database

### Input

| Field | Type | Default | Description |
|---|---|---:|---|
| `startUrls` | array | empty | Optional list, filtered-search, or individual concours URLs |
| `keyword` | string | empty | Search keyword used when `startUrls` is empty |
| `procedure` | string | `all` | Announcement stage filter |
| `administrationType` | string | `all` | State, local-authority, or public-establishment filter |
| `region` | string | `0` | Moroccan region identifier; `0` means all regions |
| `maxItems` | integer | `100` | Maximum unique concours records |
| `maxPages` | integer | `20` | Maximum search-result pages |
| `includeDetails` | boolean | `true` | Visit detail pages for complete fields |
| `includeDocuments` | boolean | `true` | Include official document URLs |
| `maxConcurrency` | integer | `8` | Parallel source requests |
| `maxRequestsPerMinute` | integer | `240` | Request-rate ceiling |
| `proxyConfiguration` | object | direct | Optional Apify Proxy settings |

```json
{
  "keyword": "ingénieur",
  "procedure": "avis",
  "administrationType": "service_etat",
  "region": "5",
  "maxItems": 100,
  "maxPages": 20,
  "includeDetails": true,
  "includeDocuments": true
}
```

To reproduce a search configured in the source website, paste its filtered results URL into `startUrls`.

### Output

Each dataset item represents one concours announcement or publication stage:

```json
{
  "concoursId": "7babf89f-94cb-4f16-8e62-84adf1859961",
  "title": "Avis de concours de recrutement de Administrateur 2ème grade - Echelle 11",
  "positionTitle": "Administrateur 2ème grade - echelle 11",
  "status": "announcement",
  "administration": "Ministère de la Jeunesse, de la Culture et de la Communication",
  "vacancies": 1,
  "applicationDeadline": "2026-08-28",
  "applicationDeadlineTime": "16:30",
  "examDate": "2026-09-13",
  "publicationDate": "2026-08-13",
  "specialties": ["Audit et Contrôle de Gestion"],
  "grade": "Administrateur 2ème grade - echelle 11",
  "scale": 11,
  "recruitmentType": "Recrutement régulier",
  "depositType": "sur emploi-public",
  "concoursCode": "C43263/26",
  "onlineApplicationAvailable": true,
  "documents": [
    {
      "label": "Arrêté d'ouverture du concours",
      "url": "https://www.emploi-public.ma/fr/concours/download/arrete/..."
    }
  ],
  "detailUrl": "https://www.emploi-public.ma/fr/concours/details/...",
  "source": "EMPLOI_PUBLIC_MA",
  "scrapedAt": "2026-08-22T16:00:00.000Z"
}
```

Missing source fields are returned as `null` or empty arrays rather than guessed. The `STATS` record reports detected results, pages processed, records saved, duplicates, failures, and run duration.

### How to Use

1. Open the Actor in Apify Console.
2. Leave the filters empty to collect the latest public concours, or select a keyword, stage, administration type, and region.
3. Keep detail and document enrichment enabled for complete records.
4. Start with 10 to 100 items before increasing the limits.
5. Run the Actor and inspect the **Output** table.
6. Export the dataset as JSON, CSV, Excel, or access it through the API.

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("scraper_guru/emploi-public-morocco-scraper").call(run_input={
    "procedure": "avis",
    "maxItems": 100,
    "includeDetails": True,
    "includeDocuments": True,
})

for concours in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(concours["title"], concours["applicationDeadline"])
```

### Pricing

The intended pricing is **$0.005 per saved concours result**, or **$5 per 1,000 results**, plus the small Actor-start charge displayed by Apify. Failed requests and duplicate concours do not produce result charges.

### Data Notes

- The Actor collects public information visible on Emploi-Public.ma; it does not submit applications or bypass candidate authentication.
- `onlineApplicationAvailable` indicates that the source advertises online submission. Authentication may still be required on the official portal.
- Document URLs point to official source downloads. The Actor does not parse the contents of PDF files.
- Dates are normalized to `YYYY-MM-DD`, while the original deadline text remains available in `applicationDeadlineRaw`.

### FAQ

**Is this limited to active announcements?**\
No. Use `procedure` to collect announcements, written-exam invitations, oral invitations, or results.

**Can I collect one specific concours?**\
Yes. Add its `/fr/concours/details/...` URL to `startUrls`.

**Can I monitor only one ministry or profession?**\
Use a keyword or paste a filtered Emploi-Public search URL into `startUrls`.

**Are official PDFs downloaded into the dataset?**\
No. The dataset contains direct official document URLs, keeping each result lightweight.

**Why can some dates or specialties be empty?**\
The portal does not publish every field for every announcement stage. Unpublished values remain null.

**Can this Actor be scheduled?**\
Yes. Create an Apify Task with your filters and run it daily or weekly to monitor new publications.

This Actor is not affiliated with the Moroccan government. Use the data lawfully and respect the source website's terms and capacity.

# Actor input Schema

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

Optional emploi-public.ma concours list, filtered search, or individual detail URLs. When supplied, these URLs override the search filters below.

## `keyword` (type: `string`):

Search concours titles and related fields, for example engineer, administrator, or ministry name.

## `procedure` (type: `string`):

Limit results to initial announcements, written-exam invitations, oral invitations, or results.

## `administrationType` (type: `string`):

Filter by government administration family.

## `region` (type: `string`):

Filter concours by Moroccan region.

## `maxItems` (type: `integer`):

Maximum number of unique concours records to save.

## `maxPages` (type: `integer`):

Safety limit for search-result pagination. Each page currently contains about nine concours.

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

Visit every concours page for specialties, grade, publication date, code, organizer, and application details.

## `includeDocuments` (type: `boolean`):

Collect links to opening decrees, invitations, results, and other official concours documents from detail pages.

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

Maximum simultaneous requests to emploi-public.ma.

## `maxRequestsPerMinute` (type: `integer`):

Request-rate ceiling used to limit load on the source website.

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

Optional Apify Proxy configuration. Direct requests are used by default because the public pages are server-rendered.

## Actor input object example

```json
{
  "startUrls": [],
  "keyword": "",
  "procedure": "all",
  "administrationType": "all",
  "region": "0",
  "maxItems": 10,
  "maxPages": 2,
  "includeDetails": true,
  "includeDocuments": true,
  "maxConcurrency": 5,
  "maxRequestsPerMinute": 180,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `concours` (type: `string`):

Concours collected from emploi-public.ma.

## `runStats` (type: `string`):

Detected, reserved, saved, duplicate, page, and failure counts.

# 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 = {
    "maxItems": 10,
    "maxPages": 2,
    "includeDetails": true,
    "includeDocuments": true,
    "maxConcurrency": 5,
    "maxRequestsPerMinute": 180
};

// Run the Actor and wait for it to finish
const run = await client.actor("scraper_guru/emploi-public-morocco-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 = {
    "maxItems": 10,
    "maxPages": 2,
    "includeDetails": True,
    "includeDocuments": True,
    "maxConcurrency": 5,
    "maxRequestsPerMinute": 180,
}

# Run the Actor and wait for it to finish
run = client.actor("scraper_guru/emploi-public-morocco-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 '{
  "maxItems": 10,
  "maxPages": 2,
  "includeDetails": true,
  "includeDocuments": true,
  "maxConcurrency": 5,
  "maxRequestsPerMinute": 180
}' |
apify call scraper_guru/emploi-public-morocco-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scraper_guru/emploi-public-morocco-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/s4ogvbJBhQBP5XiKJ/builds/2m1cTovQ4yjOCv4gY/openapi.json
