# JORADP Scraper (joradp.dz) (`hamza325/joradp-scraper`) Actor

Extract Algeria's Journal Officiel: legal texts with nature, number, signature date, ministry, JO issue and page, plus the texts that repeal, amend, implement or approve each one. Search, whole archive or issue calendar, in the Arabic or the French edition.

- **URL**: https://apify.com/hamza325/joradp-scraper.md
- **Developed by:** [Hamza Abbad](https://apify.com/hamza325) (community)
- **Categories:** News, AI
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 0.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

## JORADP Scraper

Collect Algeria's **[Journal Officiel](https://www.joradp.dz)** (*Journal Officiel de la République Algérienne Démocratique et Populaire*, الجريدة الرسمية) — the gazette where every law, decree, order and ministerial decision is published. Search the legal archive, harvest the whole thing year by year, or list the issues themselves. Results land in a dataset ready to export as CSV, Excel or JSON.

### What you can scrape

- **Legal texts matching a search** — laws, ordonnances, decrees, arrêtés, decisions and more, each with its nature, number, signature date, issuing authority, the issue and page it was published at, and the official abstract.
- **How texts connect** — for every text, the ones that **repeal**, **amend**, **implement** or **approve** it, and the rectifications that correct it. These arrive as extra rows linked to the text they belong to, so a search returns the legal history, not just a list.
- **The whole archive** — every published text, year by year, from 1962 to today.
- **The issue calendar** — every issue of the Journal with its publication date and a link to its PDF.

Each run collects one edition: the **Arabic** edition (الجريدة الرسمية) or the **French** one. They are separate publications with their own text numbering and their own PDFs, so run the Actor once per edition when you need both.

### Why this Actor

- **Every row can be cited.** Each text gives its Journal issue, publication date, page and a link that opens the PDF at exactly that page.
- **The relations come with the texts.** Repeals, amendments, implementing texts and approvals are collected alongside the text they affect, linked by `parentTextId` and `relation` — the part that makes a legal corpus usable.
- **Either edition, cleanly.** One edition per run — Arabic or French — with a `language` field on every row, so two runs combine without ambiguity.
- **Analysis-ready.** Consistent field names, ISO dates next to the dates as printed, and a `reference` string you can drop straight into a citation.
- **Cheap and fast.** No PDF is downloaded: the Actor collects metadata and links, so a large run stays small.
- **No login required.** The Journal is public; no account or cookie is needed.

#### Where it goes beyond a list of laws

A search returns the texts that match. The value is in what travels with each one:

| Surface | What a run gives you | What it is for |
| --- | --- | --- |
| **Text rows** | nature, number, signature date, issuing authority, JO issue and page, the official abstract, and a PDF link that opens at that page | a citation you can defend |
| **Relations** | for every text, the ones that repeal, amend, implement or approve it, plus its rectifications — as linked rows carrying `parentTextId` and `relation` | legal history, not a bare list |
| **Issues** | every Journal issue with its own PDF | archive completeness |
| **Editions** | Arabic or French, one edition per run | a corpus in the language you work in |

### Quick start

1. Click **Try actor**.
2. Choose what to collect in **What to scrape** (`startMode`).
3. Add a keyword or another filter (search mode needs at least one).
4. Click **Start**.
5. Open the **Dataset** tab and pick the **Legal texts** or **Journal issues** view.

Texts about the government secretariat, Arabic edition:

```json
{ "startMode": "search", "language": "ar", "keywords": "أمانة عامة للحكومة" }
```

Every law published in 2024, French edition:

```json
{ "startMode": "bulk", "language": "fr", "yearFrom": 2024, "yearTo": 2024, "nature": "Loi" }
```

The 2024 issues, with their PDFs:

```json
{ "startMode": "issues", "yearFrom": 2024, "yearTo": 2024 }
```

### Tutorial: a real workflow

Say you need everything Algeria published in 2024 on public security, and you want to know what happened to each text afterwards.

#### 1. Search the Arabic edition for the sector

```json
{
  "startMode": "search",
  "language": "ar",
  "sector": "الأمن العمومي",
  "joDateFrom": "01/01/2024",
  "joDateTo": "31/12/2024"
}
```

Each matching text comes back with its nature, number, signature date, issuing authority, abstract and Journal page.

#### 2. Read the relations

Below each match you get rows with `depth` 1 or more. A row with `relation: "repealed_by"` is the text that repealed it, `"amended_by"` amended it, `"implementing"` implements it, `"approved_by"` approved it, and `"corrigendum"` corrects it. `parentTextId` points at the text the row hangs under, so the tree survives the export to a spreadsheet.

#### 3. Widen to the full year

Swap to bulk mode with the same sector to collect every text of 2024 in that sector, not just the ones that matched a keyword:

```json
{ "startMode": "bulk", "language": "ar", "yearFrom": 2024, "yearTo": 2024, "sector": "الأمن العمومي" }
```

#### 4. Collect the French edition

Set `language` to `fr` and run the same query again. The French rows carry the same fields with the French headings (`Décret exécutif n° 93-182 du 27 Juillet 1993`) and their own text IDs, and they land in their own dataset.

### Input

See the input tab for the full configuration options.

#### Main input

| Label (`key`) | Description | Default |
| --- | --- | --- |
| **What to scrape** (`startMode`) | `search` for texts matching filters, `bulk` for the archive year by year, `issues` for the issue calendar. | `search` |
| **Edition** (`language`) | `ar` for the Arabic edition (الجريدة الرسمية), `fr` for the French one. One edition per run. | `ar` |

#### Search filters

Used in search mode, and combined with each year in bulk mode.

| Label (`key`) | Description | Default |
| --- | --- | --- |
| **Keywords** (`keywords`) | A phrase from the Journal's keyword index, written as usual: `أمانة عامة للحكومة`. | — |
| **Sector** (`sector`) | Sector of activity, e.g. `الأمن العمومي`, `AFFAIRES ETRANGERES`. | — |
| **Issuing authority** (`ministry`) | The ministry or authority that issued the text. | — |
| **Nature of the text** (`nature`) | `قانون`, `مرسوم تنفيذي`, `أمر`, `Loi`, `Décret exécutif`, `Arrêté`… | — |
| **Issue number** (`joNumber`) | One issue of the Journal; needs a date range around it. | — |
| **Text number** (`textNumber`) | The text's own number, e.g. `97-74`. | — |
| **Published from / to** (`joDateFrom`, `joDateTo`) | Publication date range of the Journal, `dd/mm/yyyy`. | — |
| **Signed from / to** (`textDateFrom`, `textDateTo`) | Signature date range of the texts, `dd/mm/yyyy`. | — |

Filters are checked against the lists the Journal publishes, so a misspelled sector or authority is reported instead of quietly returning nothing.

#### Year window

Required in bulk and issues modes: those runs collect a range of years, so there is no default to fall back on. Search mode filters by date instead and ignores both fields.

| Label (`key`) | Description | Default |
| --- | --- | --- |
| **First year** (`yearFrom`) | Required in bulk and issues modes; ignored in search mode. The archive starts in 1962. | — |
| **Last year** (`yearTo`) | Required in bulk and issues modes; ignored in search mode. Use the same year as **First year** for a single year. | — |

#### Output & debugging

| Label (`key`) | Description | Default |
| --- | --- | --- |
| **Include related texts** (`includeRelations`) | Emit the texts that repeal, amend, implement, approve or rectify each match. Turn it off to keep only the texts that matched. | `true` |
| **Include PDF links** (`includeLinks`) | Add `pageUrl`, `issuePdfUrl` and `issueIndexUrl` to every row. No PDF file is downloaded. | `true` |

#### Limits & performance

| Label (`key`) | Description | Default |
| --- | --- | --- |
| **Maximum rows** (`maxItems`) | Maximum rows for the whole run. `0` = no cap. | `0` |
| **Maximum pages per query** (`maxPagesPerQuery`) | Stop each query after this many result pages. `0` = page to the end. | `0` |
| **Texts per page** (`pageSize`) | How many texts the Journal returns per page (1–99). Larger pages are slower per request. | `20` |
| **Parallel sessions** (`maxConcurrency`) | How many queries run at the same time. | `3` |
| **Delay between pages** (`requestDelayMs`) | Pause between two pages of the same query, in milliseconds. | `700` |
| **Retries per request** (`maxRequestRetries`) | Retries before a request is given up on. | `4` |

### Output

One dataset per run. Every row carries a `recordType` — `"legalText"` for a text, `"issue"` for an issue of the Journal. Switch between the **Legal texts** and **Journal issues** views in the **Dataset** tab, and download as JSON, HTML, CSV or Excel.

#### Modes at a glance

| Mode (`startMode`) | What you get | Key fields |
| --- | --- | --- |
| **Search** (`search`) | The texts matching your filters, with their relations | `textId`, `title`, `type`, `number`, `signatureDateIso`, `ministry`, `abstract`, `jo`, `pageUrl`, `relation`, `parentTextId` |
| **Bulk** (`bulk`) | The same rows for a range of years, the whole archive if you want it | Same fields, plus `query.year` |
| **Issues** (`issues`) | One row per issue of the Journal | `year`, `number`, `dateIso`, `issuePdfUrl` |

#### Legal text row (`recordType: "legalText"`)

| Column | What it holds |
| --- | --- |
| `language` | `ar` or `fr`: which edition the row comes from. |
| `textId` | The Journal's identifier for the text, e.g. `000154`. |
| `title` | The heading as published, e.g. `مرسوم رقم 82-05 ممضي في 02 يناير 1982`. |
| `type` / `number` | Nature of the text and its own number, split out of the heading. |
| `signatureDate` / `signatureDateIso` | Signature date as printed, and as `YYYY-MM-DD`. |
| `ministry` | The issuing authority. |
| `abstract` | The summary the Journal prints under the heading. |
| `flaggedAmended` | The Journal's own marker that the text carries amendments. |
| `jo` | The Journal reference: `year`, `issue`, `date`, `dateIso`, `page`. |
| `reference` | The same reference as one line, e.g. `JO 1982-1, page 27`. |
| `pageUrl` | Opens the PDF at the page of the text (or the single-page PDF for older issues). |
| `issuePdfUrl` / `issueIndexUrl` | The whole issue's PDF, or the page index for issues older than 1994. |
| `depth` | `0` for a text that matched the query, `1`+ for a related text. |
| `relation` / `relationLabel` | How a related text relates to its parent, in English keys and as published. |
| `parentTextId` | The text a related row hangs under. |
| `query` | Which run and filters produced the row. |
| `scrapedAt` | When the row was collected. |

#### Issue row (`recordType: "issue"`)

| Column | What it holds |
| --- | --- |
| `language` | `ar` or `fr`. |
| `year` / `number` | The issue's year and number. |
| `dateIso` | Publication date, `YYYY-MM-DD`. |
| `issuePdfUrl` | The issue's PDF. |
| `scrapedAt` | When the row was collected. |

#### Relation values

| `relation` | Arabic | French |
| --- | --- | --- |
| `repealed_by` | ملغى بـ | Abrogé par |
| `amended_by` | معدل بـ | Modifié par |
| `implementing` | نص تطبيقي | Texte(s) d'application |
| `approved_by` | موافق عليه بـ | Approuvé par |
| `corrigendum` | استدراك في | Rectificatif au |
| `other` | any other label the Journal introduces | — |

#### Example rows

```json
{
  "recordType": "legalText",
  "language": "ar",
  "textId": "000154",
  "title": "مرسوم رقم 82-05 ممضي في 02 يناير 1982",
  "type": "مرسوم",
  "number": "82-05",
  "signatureDate": "02 يناير 1982",
  "signatureDateIso": "1982-01-02",
  "ministry": "وزارة السكن والعمران",
  "abstract": "يتضمن تطبيق الفقرة الثالثة من المادة 3 من القانون رقم 81-01 ...",
  "flaggedAmended": true,
  "jo": { "year": 1982, "issue": 1, "date": "05 يناير 1982", "dateIso": "1982-01-05", "page": 27 },
  "reference": "الجريدة الرسمية 1982-1، الصفحة 27",
  "pageUrl": "https://www.joradp.dz/Jo6283/1982/001/Ap27.pdf",
  "issuePdfUrl": null,
  "issueIndexUrl": "https://www.joradp.dz/Jo6283/1982/001/A_Pag1.htm",
  "depth": 0,
  "relation": null,
  "parentTextId": null,
  "query": { "mode": "search", "query": "keywords=دولة", "year": null },
  "scrapedAt": "2026-11-01T20:00:00.000Z"
}
```

```json
{
  "recordType": "issue",
  "language": "ar",
  "year": 2024,
  "number": 1,
  "dateIso": "2024-01-06",
  "issuePdfUrl": "https://www.joradp.dz/FTP/jo-arabe/2024/A2024001.pdf",
  "scrapedAt": "2026-11-01T20:00:00.000Z"
}
```

### Notes and limits

- **Related texts can fall outside your date filter.** A 2024 text repealed by a 2025 ordinance is still listed under the 2024 match, with its own 2025 issue. Filter on `depth: 0` when you want only the texts that matched the query.
- **One edition per run.** Arabic and French texts are different publications: the same law has a different `textId`, and often a different page, in each. Run the Actor once per edition if you need both.
- **Keywords are looked up in the Journal's keyword index**, not searched as free text. A phrase that reads like a subject (`أمانة عامة للحكومة`) finds its texts; unrelated words strung together (`دولة محروقات`) find nothing, even though each word exists on its own.
- **Some dates have no ISO form.** When the Journal prints a date in the Hijri calendar, `signatureDateIso` or `jo.dateIso` is `null` instead of a converted date, and the printed date stays available in `signatureDate` / `jo.date`.
- **Bulk makes one query per year**, so a wide year window is a longer run. Narrow the sector or nature to cut it down, or raise `maxConcurrency`.
- **`pageSize` above the default costs more per request.** The Journal takes noticeably longer to render 99 texts than 20; the default keeps runs responsive.
- **Links only, no PDF files.** The Actor collects metadata and URLs; download the PDFs you need from the links.
- Fields that do not apply to a row type are `null` in that row.

### FAQ

**How do I find one specific text?**
Use search mode with `textNumber` (the text's own number, e.g. `97-74`) or `joNumber` together with a date range around that issue.

**Why do I get rows with `depth` above 0?**
Those are the texts the Journal links to a match — the ones that repeal, amend, implement, approve or rectify it. Turn off **Include related texts** to keep only the matches.

**How do I get both editions?**
Run the Actor twice, once with `language` set to `ar` and once to `fr`. Each run produces its own dataset.

**Can I read the text itself?**
The Journal publishes the official text as PDF. Each row's `pageUrl` opens that PDF at the right page, and `issuePdfUrl` gives the whole issue.

**How much data does a bulk run return?**
A recent year holds roughly 1,000–1,300 texts per edition before relations, so expect more rows than texts. Set `maxItems` or a narrower year window to bound a run.

**Does the Actor need a login?**
No. The Journal is public.

### 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/joradp-scraper/changelog.md

# Actor input Schema

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

Search needs at least one filter. Bulk walks every year of the archive. Issues collects the calendar: one row per issue with its publication date and PDF.

## `keywords` (type: `string`):

A phrase from the Journal's keyword index, written normally — words separated by spaces, e.g. `أمانة عامة للحكومة`. The Journal looks the phrase up in its own index, so a combination of words it does not list finds nothing: prefer phrases that read like a subject.

## `sector` (type: `string`):

Sector filter (43 Arabic / 45 French values, e.g. `الأمن العمومي`, `AFFAIRES ETRANGERES`). Matched case-insensitively against the list the site publishes at runtime; a partial value is accepted when it is unambiguous.

## `ministry` (type: `string`):

Ministry filter (364 Arabic / 365 French values). The site spells them abbreviated, e.g. `وزارة الداخلية` or `M.D/M.F/CH/BUDG.PRO`.

## `nature` (type: `string`):

Text nature (28 values in both portals): `قانون`, `مرسوم تنفيذي`, `أمر`, `Loi`, `Décret exécutif`, `Arrêté`…

## `joNumber` (type: `string`):

Issue number of the Journal Officiel. Needs a date range around it (`joDateFrom`/`joDateTo`), because the same number exists in every year.

## `textNumber` (type: `string`):

The text's own number, e.g. `77-74`. Looks up one specific text.

## `joDateFrom` (type: `string`):

Issue publication date, `dd/mm/yyyy`. Filters on the date the Journal was published.

## `joDateTo` (type: `string`):

Issue publication date, `dd/mm/yyyy`.

## `textDateFrom` (type: `string`):

Signature date of the text itself, `dd/mm/yyyy`. Different from the publication date: a text signed in December can be published the next month.

## `textDateTo` (type: `string`):

Signature date of the text itself, `dd/mm/yyyy`.

## `yearFrom` (type: `integer`):

Required in bulk and issues modes; ignored in search mode. The Journal's archive starts in 1962.

## `yearTo` (type: `integer`):

Required in bulk and issues modes; ignored in search mode. Use the same year as First year for a single year.

## `includeRelations` (type: `boolean`):

Emit the texts that repeal, amend, implement, approve or rectify each match, as extra rows flagged with `depth`, `relation` and `parentTextId`. Turning this off keeps only the texts that matched the query.

## `includeLinks` (type: `boolean`):

Add `pageUrl` (a deep link into the issue PDF, or the single-page PDF before 1994), `issuePdfUrl` and `issueIndexUrl`. No PDF file is downloaded: the run collects links only.

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

Maximum rows for the whole run. 0 = no cap.

## `maxPagesPerQuery` (type: `integer`):

Stop each query after this many result pages. 0 = page to the end of the result set.

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

The site's own default is 20. Larger pages cost far more per request: 99 texts take ~37 s and ~3 MB against ~2 s and ~200 KB at 20.

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

Queries run in parallel, each on its own session: the site keys a result set to a session, so one query is always paged by one session.

## `requestDelayMs` (type: `integer`):

Pause between two page requests of the same session.

## `maxRequestRetries` (type: `integer`):

How many times a failed request is retried before it is given up on. The site occasionally answers 500 or drops the session token.

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

Optional. The site does not block datacentre traffic, so a proxy is only needed if a run is throttled.

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

Which edition to collect. The two are separate publications with their own text numbering and their own PDFs, so a run covers one of them: run the Actor twice to have both.

## Actor input object example

```json
{
  "startMode": "search",
  "includeRelations": true,
  "includeLinks": true,
  "maxItems": 0,
  "maxPagesPerQuery": 0,
  "pageSize": 20,
  "maxConcurrency": 3,
  "requestDelayMs": 700,
  "maxRequestRetries": 4,
  "language": "ar"
}
```

# Actor output Schema

## `rows` (type: `string`):

Default dataset containing every row the run pushed. Filter on `recordType` to separate legal texts from journal issues, and on `depth = 0` to keep only the texts a query matched.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("hamza325/joradp-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 = {}

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

```

## MCP server setup

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