# El Mouchir Scraper (elmouchir.dz) (`hamza325/elmouchir-scraper`) Actor

Scrape elmouchir.dz, the Algerian Chamber of Commerce company directory: 79,000+ firms with address, wilaya, sector, phone and e-mail. Search by keyword, browse a sector or a wilaya, open a company page or resolve a name.

- **URL**: https://apify.com/hamza325/elmouchir-scraper.md
- **Developed by:** [Hamza Abbad](https://apify.com/hamza325) (community)
- **Categories:** Lead generation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 75.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.47 / 1,000 results

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

## El Mouchir Scraper

**[El Mouchir](https://elmouchir.dz)** is the online directory of the Algerian Chamber of Commerce and Industry:
tens of thousands of Algerian companies with their address, their activity and their
phone numbers and e-mail addresses.

This Actor reads that directory and puts the companies in your dataset. Fill in a
keyword, a sector or a wilaya and you get back the matching companies — with their
contact details, ready to use.

### What you can scrape

- **Companies by keyword** — every company whose name or address matches what you
  are looking for.
- **Companies of a sector** — the whole "Transport", "Café & Restaurant",
  "Construction" … branch of the directory.
- **Companies of a wilaya** — everything the directory files under Oran, Blida,
  Sétif …
- **One company in full** — name, address, precise wilaya, logo, phone numbers and
  e-mail addresses.
- **The tree of sectors and activities** — the directory's own menu, with the
  number of companies in every node.
- **A name lookup** — resolve a company or a wilaya name to the directory's own id
  before you scrape it.

### Why this Actor

- The contact details come out **readable**: phone numbers and e-mail addresses,
  not pictures.
- The filters compose: a keyword **inside** a sector **inside** a wilaya is one run.
- The directory throttles hard. The Actor works with it instead of against it, so
  a run finishes instead of dying half way.
- You always know what you got: the run tells you how many companies the directory
  said there were, and how many rows came out.

#### Where it goes beyond a company list

A keyword search returns matching companies. The directory has more structure than that, and the Actor reads all of it:

| Surface | What a run gives you | What it is for |
| --- | --- | --- |
| **One company in full** | name, address, precise wilaya, logo, phone numbers and e-mail addresses as readable text | outreach without a second lookup |
| **A sector branch** | every company the directory files under that sector | size a market before you contact it |
| **A wilaya** | everything filed under Oran, Blida, Sétif … | local prospecting |
| **The sector and activity tree** | the directory's own menu, with the number of companies under every node | scope a segment, or find the sector URL to paste |
| **Composable filters** | a keyword inside a sector inside a wilaya, in one run | narrow to exactly the segment you sell to |

### Quick start

The simplest run — every company that matches a keyword:

```json
{ "searchQuery": "voyage" }
```

The keyword is the thing to fill in. A run that also wants the contact details
(the default) and a hundred rows (the default) is done.

### Tutorial: a real workflow

Say you want to reach the travel agencies in Oran, with their phone numbers.

#### 1. Find the sector you want

The directory groups its companies into sectors and activities. Run the Actor in
the **sector catalog** mode to get the whole menu:

```json
{ "startMode": "categories" }
```

The dataset holds one row per node: the sectors and the activities inside them,
with the number of companies each one holds. Look for "Agences de voyages et de
tourisme" and note its sector — for the example below that is
`https://elmouchir.dz/secteur/5563/transport-tourisme-transit-hotellerie`.

#### 2. List the sector

Paste the sector page into **Sector or activity URL**:

```json
{ "categoryUrl": "https://elmouchir.dz/secteur/5563/transport-tourisme-transit-hotellerie" }
```

You get every company of that sector, with their names, addresses and contact
details. The run log tells you how many the directory said there were.

> A sector page has an address, so you can also open it in your browser and paste
> the address bar into **Page URL** — the Actor works out that it is a sector.

#### 3. Narrow it to a wilaya

Add the wilaya. Its name is enough — the Actor looks it up:

```json
{
  "categoryUrl": "https://elmouchir.dz/secteur/5563/transport-tourisme-transit-hotellerie",
  "wilaya": "Oran"
}
```

Now you have the travel agencies of that sector **in Oran**. You can also give the
wilaya id instead of its name (`"31"`) if you know it — it is the number in the
wilaya's own address, `https://elmouchir.dz/wilaya/31/oran`.

#### 4. Narrow it to a keyword

One more field, and the three compose: the travel agencies of that sector in Oran
whose name mentions "voyage".

```json
{
  "categoryUrl": "https://elmouchir.dz/secteur/5563/transport-tourisme-transit-hotellerie",
  "wilaya": "Oran",
  "searchQuery": "voyage"
}
```

The keyword is what to type in the directory's own search box. It never becomes
part of a link, so it always goes into the **Search keyword** field — there is no
address to copy for a search.

#### 5. Get every company's full record

The listing rows carry the directory's summary of a company. Open every company's
own page to get its precise wilaya, its full address and its complete contact
list:

```json
{
  "categoryUrl": "https://elmouchir.dz/secteur/5563/transport-tourisme-transit-hotellerie",
  "wilaya": "Oran",
  "includeDetails": true
}
```

That is one extra request per company, so it roughly doubles the run's time.

#### 6. Scrape a hand-picked list of companies

If you already know the companies — from a previous run, a spreadsheet, a
customer list — put their pages into **Company URLs**:

```json
{
  "startUrls": [
    "https://elmouchir.dz/entreprise/94249/tadjra-voyage",
    "https://elmouchir.dz/entreprise/94262/el-massa-voyages"
  ]
}
```

Each company comes back as one row, with everything the directory knows about it.

#### 7. Look a name up before you scrape it

Not sure how the directory spells a company? Ask it:

```json
{ "startMode": "suggest", "searchQuery": "tadjra" }
```

The dataset holds the matches with the id the directory uses for them — the number
in the company's own address. Switch **Lookup scope** to `wilaya` to do the same
for a wilaya.

### Input

**Page URL** (`pageUrl`) is the shortcut most runs use; every other field has a default. The Console form shows the main fields first, then the task selector, the limits and the debugging options.

#### Main input

| Label (`key`) | Description | Default |
| --- | --- | --- |
| **Page URL (shortcut)** (`pageUrl`) | A shortcut: paste a sector, company or wilaya page, or the sector catalog, and the Actor works out what to do with it. | — |
| **Search keyword** (`searchQuery`) | The keyword the companies should match. Empty means the whole directory. | — |
| **Sector or activity URL** (`categoryUrl`) | The sector or activity to list, as its page URL. | — |
| **Wilaya** (`wilaya`) | A wilaya, by name (`Oran`) or by id (`31`). | — |
| **Company URLs** (`startUrls`) | The pages of specific companies to scrape. | `[]` |

The filters combine: keyword + sector + wilaya is one listing.

#### Task (advanced)

| Label (`key`) | Description | Default |
| --- | --- | --- |
| **What to scrape** (`startMode`) | Which of the fields above the run is about. `auto` (the default) uses the most specific one you filled in. | `auto` |
| **Lookup scope** (`suggestScope`) | Whether a name lookup is about a company or a wilaya. | `company` |

#### Limits & performance

| Label (`key`) | Description | Default |
| --- | --- | --- |
| **Maximum rows** (`maxItems`) | Max rows in the dataset. `0` = no cap. The main cost lever. | `200` |
| **Resolve phone and e-mail** (`includeContacts`) | Read the phone numbers and e-mail addresses. | `true` |
| **Open each company's page** (`includeDetails`) | Also open every company's own page. | `false` |
| **Companies per listing page** (`pageSize`) | Companies asked for per listing page. The directory only honours 5, 10, 20, 50 or 100; other values return 10 per page. `100` gives up to 300 companies per run (three pages). | `100` |
| **Maximum listing pages** (`maxPages`) | Listing pages per filter. The site lets an anonymous visitor read three. | `3` |
| **Maximum catalog pages** (`maxCatalogPages`) | Pages of the sector catalog to walk. | `25` |
| **Parallel connections** (`maxSessions`) | How many listings the Actor works on at the same time. | `10` |
| **Stagger between connections (seconds)** (`sessionStartDelaySecs`) | Seconds between the first requests of two connections. | `2` |
| **Retries per request** (`maxAttempts`) | Retries per request. | `7` |

#### Output & debugging

| Label (`key`) | Description | Default |
| --- | --- | --- |
| **Skip repeated companies** (`dedupe`) | Put a company in the dataset only once. | `true` |
| **Debug: include the raw page data** (`debugIncludeRaw`) | Attach the unprocessed page data to every row. | `false` |

`debugIncludeRaw` puts the page data the row was built from next to the row itself. Turn it on to check a run, off to keep the dataset small.

### Output

One dataset, three kinds of row, told apart by the `__type` field. Company rows
have none.

#### Company row

| Column | What it holds |
| --- | --- |
| `id` | The company's number in the directory. |
| `url` | The company's page. |
| `name` | The company's name. |
| `address` | Its street address, as the directory prints it. |
| `wilaya` / `wilayaId` | The wilaya the company belongs to. Filled in when the run is about a wilaya, or when `includeDetails` is on. |
| `categoryName` / `categoryUrl` / `categoryId` | The sector or activity the company was listed under. |
| `logoUrl` | The company's logo. |
| `badge` | The label the directory puts on the listing, when it has one. |
| `phones` | The phone numbers. Empty when `includeContacts` is off. |
| `emails` | The e-mail addresses. Empty when `includeContacts` is off. |
| `sourceUrl` | The listing the company was found on. |
| `scrapedAt` | When the row was written. |

#### Sector catalog row

`__type` is `elmouchir_category`. Holds the node's `id`, `name`, `url`, whether it
is a `sector` or an `activity`, the `parentId`/`parentName` of the sector it
belongs to, its `imageUrl` and the `companyCount` the directory gives for it.

#### Name lookup row

`__type` is `elmouchir_suggest`. Holds the `id` and the `label` of the match, and
whether the lookup was about a `company` or a `wilaya`.

### Notes and limits

- The directory serves **three listing pages per filter** to an anonymous
  visitor. With `pageSize` at 100, a run therefore returns at most **300
  companies per filter**, and it tells you in the log when the directory stopped
  it. If you set a value it does not honour, it silently serves 10 per page, so
  the same run returns at most 30.
- A keyword matches the directory's own search: a part of a word is enough
  ("voyage" finds "El Kawther Voyages").
- A wilaya lists the companies the **directory** files under it. A company whose
  address is in another wilaya will not show up there, even though the run says
  the wilaya. Turn on `includeDetails` when you want the precise wilaya of every
  company instead.
- Phone numbers and e-mail addresses come out as the directory holds them:
  a number may be written in several formats, and an address the directory does
  not publish stays empty.
- The directory is slow to serve and slows down under load. A run of a few
  hundred companies with contacts takes minutes, not seconds.
- **Activities cannot be browsed.** The catalog lists activities with a company
  count, but the directory's listing endpoint answers 0 companies for every
  activity id — it filters by sector only. An activity URL therefore stops the
  run with an explanation that names the sector to browse instead (the run's
  sector, keyword and wilaya modes are unaffected). Each of these activities
  was checked against the catalog: the catalog counts thousands of companies in
  them, and the listing answers none.

### FAQ

**How do I find the sector I want?**
Run the sector-catalog mode once: `{ "startMode": "categories" }`. It gives you
every sector and activity with the number of companies in it.

**How do I find a company's page?**
Open the company in the directory and copy the address bar. The company's number
is the last part of it.

**Why is there no search URL to paste?**
The directory's search box does not change the address, so there is no link to
copy. The keyword goes into **Search keyword**.

**The run says it hit the page limit. Now what?**
The directory stopped serving after three listing pages. Narrow the run — a
keyword, a wilaya or a sector — and run it again for the rest.

**Can I scrape several sectors in one run?**
Yes, but each sector has its own page budget, so a run over many sectors takes
longer. Keep `maxItems` in mind: it counts every row the run produces.

**How do I get the most rows for the money?**
Turn `includeDetails` off and keep `pageSize` at 100. The directory serves at most
three listing pages per filter, so one filter can return up to 300 companies. If
you set another value, the directory may quietly serve only 10 per page.

### Contact

Bug reports, feature requests, or a custom scraper for a site not yet covered? Open an issue on the Actor's Apify Console page or message the maintainer directly there.

# Changelog

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

# Actor input Schema

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

The keyword the companies should match. The keyword can be combined with a sector and a wilaya. Leave it empty to list the whole directory.

## `categoryUrl` (type: `string`):

The page of the sector to list, e.g. https://elmouchir.dz/secteur/5563/transport-tourisme-transit-hotellerie. Copy it from the address bar of the sector page on the site. Run the Actor in the sector-catalog mode first if you do not know which sector you want. Activity pages (/activite/<id>) are not supported: the directory answers 0 companies for every activity id, so the run stops and names the sector to browse instead.

## `wilaya` (type: `string`):

A wilaya, by name ("Oran") or by id ("31"). It narrows the result to the companies the directory files under that wilaya. The id is the number in the wilaya's own address, e.g. https://elmouchir.dz/wilaya/31/oran.

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

The pages of specific companies to scrape, e.g. https://elmouchir.dz/entreprise/94249/tadjra-voyage. One row per company, with its full record.

## `pageUrl` (type: `string`):

A shortcut: paste a page of the site and the Actor works out what to do from it. Accepted are a sector or activity page, a company page, a wilaya page and the sector catalog. A search has no address of its own — use the **Search keyword** field for that.

## `startMode` (type: `string`):

What the run is about. Leave it on **auto** unless a field does not say it clearly enough. **auto** uses the most specific of the fields you filled in. **search** lists the companies of a keyword, **category** those of a sector, **wilaya** those of a wilaya, **detail** the companies whose URLs you gave, **categories** the tree of sectors and activities, **suggest** looks a name up in the directory's own search.

## `suggestScope` (type: `string`):

Only for the **suggest** mode: look a company name up, or a wilaya name.

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

Max rows to put in the dataset. 0 means no cap. This is the main cost lever: every row costs a contact lookup, so keep it in the hundreds on the platform. It also caps the work: once the cap is reached no further listing page is fetched and, with **Open each company's page** on, no further company page is opened.

## `includeContacts` (type: `boolean`):

Read the phone numbers and e-mail addresses of the companies. Leave it on unless you only want the names and addresses — this is where most of the run's time goes.

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

Also open every company's own page, which gives its precise wilaya and its complete contact list. One extra request per company, so it roughly doubles the run's time.

## `pageSize` (type: `integer`):

How many companies the Actor asks for per listing page. The directory only honours 5, 10, 20, 50 or 100; any other value returns 10 companies per page. 100 is recommended: with the guest limit of three pages per filter, it yields up to 300 companies per run. You can try another value, but expect 10 per page.

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

Max listing pages per filter. The site lets an anonymous visitor read three, so anything above that is capped.

## `maxCatalogPages` (type: `integer`):

Max pages of the sector catalog to walk in the **categories** mode.

## `maxSessions` (type: `integer`):

How many listings the Actor works on at the same time. The site throttles every connection on its own, so a higher number means a faster run — and more load on the site.

## `sessionStartDelaySecs` (type: `integer`):

Seconds between the first requests of two consecutive connections. It keeps the run from hitting the site all at once.

## `maxAttempts` (type: `integer`):

How often a request is retried before it is given up on. A request the site put on hold is retried after the wait the site asked for.

## `dedupe` (type: `boolean`):

Put a company in the dataset only once, even when it shows up on several pages.

## `debugIncludeRaw` (type: `boolean`):

Attach the unprocessed page data to every row. Useful while checking a run, noisy afterwards.

## Actor input object example

```json
{
  "searchQuery": "voyage",
  "wilaya": "Oran",
  "startUrls": [],
  "startMode": "auto",
  "suggestScope": "company",
  "maxItems": 200,
  "includeContacts": true,
  "includeDetails": false,
  "pageSize": 100,
  "maxPages": 3,
  "maxCatalogPages": 25,
  "maxSessions": 10,
  "sessionStartDelaySecs": 2,
  "maxAttempts": 7,
  "dedupe": true,
  "debugIncludeRaw": false
}
```

# Actor output Schema

## `companies` (type: `string`):

The default dataset with every row the Actor pushed during the run. Switch the view in the Output tab: the company rows for a search/sector/company run, the catalog nodes for the sector catalog, the lookup results for a name lookup.

# 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": "voyage",
    "wilaya": "Oran"
};

// Run the Actor and wait for it to finish
const run = await client.actor("hamza325/elmouchir-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": "voyage",
    "wilaya": "Oran",
}

# Run the Actor and wait for it to finish
run = client.actor("hamza325/elmouchir-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": "voyage",
  "wilaya": "Oran"
}' |
apify call hamza325/elmouchir-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hamza325/elmouchir-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/jnL7jWpalQjWhHvuh/builds/Oi695BZmybkZb5CQh/openapi.json
