# Arabic PDF to Text Extractor: Arabic OCR, Scanned & Digital (`saif-tools/arabic-pdf-text-extractor`) Actor

Arabic PDF to text, including scans and PDFs whose Arabic copies out garbled. Reads the text layer first and auto-OCRs scanned pages and common broken Arabic layers (Arabic + English); set OCR mode to Always for any it misses. One JSON row per page. $0.0008 per text page, $0.008 per OCR page.

- **URL**: https://apify.com/saif-tools/arabic-pdf-text-extractor.md
- **Developed by:** [saif alnuaimi](https://apify.com/saif-tools) (community)
- **Categories:** AI, Developer tools, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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?

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

**Pricing in one line:** in the default `auto` mode, pages that need OCR cost **$0.008 each, against $0.0008 for a text page (10×)**. To keep costs predictable, cap OCR with `maxOcrPages` (default 50 per run; 0 = never OCR), or use `ocrMode: "never"` (text layer only), and set the run's max-charge limit; the run stops cleanly when it is reached.

**Arabic PDF to text, for scanned and digital PDFs, with OCR to fix garbled Arabic text layers.** Extract Arabic text from PDF files (Arabic, English or mixed) as one JSON row per page. Digital pages are read from the text layer. Scanned pages, and pages whose Arabic text layer shows known breakage signs, get Arabic + English OCR automatically. If a text layer still looks wrong, set OCR mode to **Always** to force OCR. You pay per page; failed files cost nothing.

- Detects common **broken Arabic text layers**: lam-alef ligatures exported as Latin symbols like `ƾ`/`ǂ`, private-use or replacement characters, and letters reversed inside words. It re-reads those pages with OCR. For anything it misses, `ocrMode: "always"` forces OCR on every page
- Arabic OCR for scanned pages (Tesseract `ara+eng`)
- Normalises Arabic: presentation forms become standard letters; tatweel (ـ) and bidi marks are removed
- Page-level output with page numbers, for RAG citations, search indexes and AI agents
- $0.0008 per text page, $0.008 per OCR page, $0.002 per document. Blank pages are free and flagged isBlank.

### Before and after: a broken Arabic text layer

This is page 1 of our own test PDF. It looks normal on screen, but its text layer is broken. Here is what the text layer actually contains, as any text-layer-only extraction returns it (read with PyMuPDF):

```text
ﺩﺍﺋﺮﺓ ﺍƾ	ﺭﺍﺿﻲ ﻭﺍƾ	ﻣǂبﻙ ﻓﻲ ﺩﺑﻲ ﺗﻨﺸﺮ ﺑﻴﺎﻧﺎﺕ ﺍǄﻤﻌﺎﻣǂبﺕ ﺍǄﻌﻘﺎﺭﻳﺔ ﺑﺸﻜﻞ ﻳﻮﻣﻲ. ﺍǄﻘﻴﻤﺔ
ﺍƾﺟﻤﺎǄﻴﺔ Ǆǁﺼﻔﻘﺔ
٦٥٤٣٢١
 ﺩﺭﻫﻢ ﺇﻣﺎﺭﺍﺗﻲ.
```

The lam-alef ligatures (لا / لأ) came out as Latin symbols (`ƾ`, `ǂ`, `Ǆ`), the letters are presentation-form glyphs, and the number is reversed. Search, copy-paste and LLMs can't use this text.

This Actor flagged the page as broken, OCR'd it, and returned:

```json
{
  "fileName": "arabic_digital.pdf",
  "pageNumber": 1,
  "pageCount": 2,
  "text": "دائرة الأراضي والأملاك في دبي تنشر بيانات المعاملات العقارية بشكل يومي. القيمة\nالإجمالية للصفقة 1١51 درهم إماراتي.\n\nDubai Land Department publishes real estate transaction data daily.\nTotal value 123,456 AED.",
  "method": "ocr",
  "charCount": 208,
  "wordCount": 30,
  "arabicCharRatio": 0.564,
  "primaryScript": "mixed",
  "ocrConfidence": 92.8,
  "error": null
}
```

The Arabic words and the English lines are now correct. **The Arabic-Indic number is still wrong**: the page says ١٢٣٤٥٦, and OCR read "1١51". See [Limitations](#limitations): check amounts and IDs against the original.

### How it works

1. It reads the **text layer** first, which is fast and cheap.
2. It **checks every page for known signs of a broken Arabic layer**: Latin or symbol glyphs inside Arabic words, private-use or replacement characters, and letters reversed inside words. It also checks for scanned or image-only pages.
3. It runs **Arabic + English OCR** only on those pages (`ocrMode: "auto"`). To OCR every page regardless, use `ocrMode: "always"`.
4. It **normalises Arabic** (NFKC; tatweel and bidi marks removed). It can also convert Arabic-Indic digits (٠١٢٣) to 0-9.

Clean pages skip OCR, which is 10× cheaper per page. A clean English PDF from our tests (the IRS W-8BEN form):

```json
{
  "fileName": "fw8ben.pdf",
  "pageNumber": 1,
  "method": "text-layer",
  "charCount": 4858,
  "primaryScript": "latin",
  "ocrConfidence": null
}
```

### Quick start

1. Click **Start**.
2. Paste one or more **direct links to public PDF files** into *PDF URLs*.
3. Leave *OCR mode* on **Auto**. If some pages still come out garbled, re-run with **Always**.
4. Optional: set **Max cost per run**. The Actor stops before going over it.
5. Run, then open the **Pages** table or export JSON, CSV or Excel.

```json
{
  "pdfUrls": ["https://example.com/arabic-sample.pdf"]
}
```

For an Arabic archive where you want one row per document and Western digits:

```json
{
  "pdfUrls": ["https://example.com/contract-ar.pdf"],
  "ocrMode": "auto",
  "outputMode": "document",
  "convertArabicDigits": true,
  "maxPagesPerDocument": 30
}
```

#### Output fields

| Field | Meaning |
|---|---|
| `method` | `text-layer`, `ocr`, `mixed` (document mode), `empty` or `error` |
| `ocrConfidence` | Mean Tesseract word confidence, 0–100 (null if no OCR) |
| `arabicCharRatio` / `primaryScript` | Share of Arabic letters; `arabic`, `latin`, `mixed` or `none` |
| `error` | Why a file failed (HTTP 404, not a PDF, password-protected, too large) |
| `isBlank` | `true` when the page has no text at all, even after OCR (not charged); `false` on normal rows |

A run summary (documents, page counts, OCR pages, blank pages, charged events) is saved in the key-value store as `OUTPUT`.

### Pricing

Pay per event. No separate platform-usage charge.

| Event | Price (USD) | When |
|---|---|---|
| Actor start | $0.0005 per GB of memory | Once per run (default 2 GB = $0.001) |
| Document processed | $0.002 | A PDF downloaded and opened successfully |
| Text page | $0.0008 | A page read from the text layer |
| OCR page | $0.008 | A page read with OCR |

**Examples** (excluding the start event): a 20-page digital PDF ≈ $0.018; a 20-page scan ≈ $0.162.

- Failed downloads and error rows are **free**.
- Blank pages are free and flagged isBlank. A page counts as blank only if it has no non-whitespace character after the text layer and after OCR; a page with only a page number is charged as a normal page. The document fee still applies, even to a PDF where every page is blank.
- Pages beyond `maxPagesPerDocument` are skipped and **not charged**.
- With "Max cost per run" set, the budget is checked before each page and each OCR, and the run stops cleanly. In our test, a $0.015 limit stopped after 1 document + 1 OCR page, and that page was delivered.
- OCR cost guard: `maxOcrPages` (default 50) caps OCR'd pages per run. Once reached, further pages that need OCR are returned from the text layer with `ocrSkipped: true` and charged as text pages ($0.0008), or free if they have no text layer at all. The run summary (`OUTPUT`) reports `ocrSkippedByCap`, and the run's status message says when the cap was hit. Scanned pages skipped this way come back empty, so raise `maxOcrPages` for large scans.
- Cheapest option: `ocrMode: "never"` (text layer only), or `maxOcrPages: 0`.

### Use cases

- Arabic documents from the UAE, Saudi Arabia and the GCC: contracts, tenancy documents, government circulars, invoices, annual reports
- Arabic RAG and chatbot knowledge bases with page-level citations
- Bulk digitising of scanned Arabic and English archives
- A "read this Arabic PDF" tool for AI agents via MCP

### Input options

| Field | Default | What it does |
|---|---|---|
| `pdfUrls` | (required) | Direct links to public PDFs (http/https) |
| `ocrMode` | `auto` | `auto` = OCR only pages that need it; `always`; `never` (cheapest) |
| `maxOcrPages` | 50 | Most pages OCR'd per run ($0.008 each); 0 = never OCR. Pages over the cap come back from the text layer with `ocrSkipped: true` |
| `ocrLanguages` | `ara+eng` | Tesseract languages (installed: `ara`, `eng`) |
| `ocrDpi` | 250 | OCR render resolution, 100–400 |
| `minTextCharsPerPage` | 25 | In auto mode, pages with less real text are OCR'd |
| `maxPagesPerDocument` | 100 | Extra pages are skipped and not charged (hard cap 2,000) |
| `maxFileSizeMb` | 50 | Larger files are rejected (max 200) |
| `outputMode` | `page` | `page` = one row per page; `document` = one row per PDF |
| `normalizeArabic` | true | NFKC, remove tatweel and bidi marks, tidy whitespace |
| `convertArabicDigits` | false | ٠١٢٣ / ۰۱۲۳ → 0123 |

### Use with AI agents (MCP)

```
https://mcp.apify.com/?tools=saif-tools/arabic-pdf-text-extractor
```

Example prompt: *"Read this Arabic tenancy contract PDF and summarise the rent, dates and parties."* For agents, use `outputMode: "document"` and a low `maxPagesPerDocument` to keep responses small. The Actor also works from Make, n8n and Zapier through Apify's own integrations: https://docs.apify.com/platform/integrations

### FAQ

#### Why does text copied from my Arabic PDF look like garbage?

Many Arabic PDFs map letters or ligatures to the wrong characters, or store them in the wrong order, so the text layer doesn't match what you see on screen. In `auto` mode the Actor detects pages with ligature, private-use or mixed-script artifacts and letters reversed inside words, and OCRs them instead (see the before/after above). Other kinds of damage, such as reversed word order or old legacy Arabic fonts, may not be detected. For those, set `ocrMode: "always"` to force OCR.

#### The output still looks garbled. What now?

Re-run the file with `ocrMode: "always"`. Every page is then read with OCR ($0.008 per page) instead of the text layer, up to `maxOcrPages` (default 50; raise it for longer files). Check `method` in the output: `text-layer` means the page was not OCR'd.

#### Does it work on scanned Arabic PDFs?

Yes. Pages without a usable text layer are OCR'd with the Arabic and English models. On our scanned Arabic test PDF, mean OCR confidence was 92.5 and 87.1 on its two pages.

#### Will it OCR pages that don't need it?

Not in `auto` mode. A clean digital PDF (the IRS W-8BEN form, 4,858 characters) was read from the text layer with no OCR.

#### How accurate are numbers?

Latin digits (0-9) are read well. **Arabic-Indic digits (١٢٣) in scanned or broken PDFs are not reliable**: in our test, ١٢٣٤٥٦ came back as "1١51". Always check amounts, dates and ID numbers against the original.

#### What if a URL is wrong?

You get one row with `method: "error"` and a reason such as `HTTP 404` or `not a PDF (missing %PDF header)`. Error rows are free.

#### Can it read password-protected or private files?

No. Public URLs only; the Actor doesn't log in anywhere. Password-protected PDFs return an error row.

#### Does it keep tables and columns?

No. Tables and multi-column layouts come out as plain text.

#### How long does OCR take?

In our local test, a 10-page scan took 28.8 s. The 1-page W-8BEN form, read from its text layer, took 0.09 s.

### Limitations

- **Arabic-Indic numerals in scanned or broken PDFs are OCR'd unreliably** (in the example above, ١٢٣٤٥٦ became "1١51"). Double-check amounts and IDs against the original.
- **Some broken text layers are not auto-detected.** PDFs with reversed word order, or with old legacy Arabic fonts, may pass the checks and come back garbled as `text-layer`. If a page's text still looks wrong, re-run with `ocrMode: "always"` to force OCR on every page.
- A very short Arabic title (2 to 7 words) may OCR slightly worse than a longer text.
- A short broken Arabic line on a mostly English page stays on the text layer. For such files, use `ocrMode: "always"`.
- Arabic-Indic numbers stored in reverse digit order in the text layer are not always caught. Check amounts and IDs against the original.
- Table structure and multi-column layouts are not reconstructed.
- Handwriting is not supported.
- Public URLs only. No login.

### بالعربية

**تحويل PDF عربي إلى نص**، بما في ذلك الملفات الممسوحة ضوئياً والملفات التي تظهر حروفها العربية مشوّهة عند النسخ.

- يقرأ طبقة النص أولاً، ويكتشف الصفحات الممسوحة ضوئياً وأنواعاً شائعة من طبقات النص العربية التالفة (رموز لاتينية داخل الكلمات العربية، أو حروف معكوسة داخل الكلمة)، ثم يطبّق OCR عربي وإنجليزي على هذه الصفحات فقط.
- بعض الملفات التالفة لا تُكتشف تلقائياً، مثل الملفات ذات ترتيب الكلمات المعكوس أو الخطوط العربية القديمة. إذا بقي النص مشوّهاً، اختر وضع OCR "always" لتطبيق OCR على كل الصفحات.
- يوحّد أشكال الحروف العربية ويزيل التطويل (ـ) وعلامات الاتجاه.
- النتيجة صف JSON لكل صفحة مع رقم الصفحة، جاهز لأنظمة RAG ومحركات البحث ووكلاء الذكاء الاصطناعي.
- مناسب للعقود والتعاميم الحكومية والتقارير السنوية في الإمارات والسعودية ودول الخليج.
- الأسعار: 0.0008 دولار لكل صفحة نصية، و0.008 دولار لكل صفحة OCR، و0.002 دولار لكل مستند. الملفات التي تفشل لا تُحتسب.
- **تنبيه:** الأرقام العربية (١٢٣) في الملفات الممسوحة أو التالفة لا تُقرأ بدقة. في اختبارنا قُرئ الرقم ١٢٣٤٥٦ بشكل خاطئ، لذا راجع المبالغ والتواريخ وأرقام الهوية دائماً.
- يعمل مع الروابط العامة فقط، ولا يحتفظ ببنية الجداول.

### Related tools from saif-tools

- [Sitemap URL Extractor](https://apify.com/saif-tools/sitemap-url-extractor): list every URL a website publishes in its sitemaps. Filter with `includePatterns: ["\\.pdf$"]` to collect PDF links for this Actor (only PDFs listed in the sitemap are found).
- [Broken Link Checker](https://apify.com/saif-tools/broken-link-checker): find 404s, redirect chains and dead images on your site.

# Actor input Schema

## `pdfUrls` (type: `array`):

Direct links to PDF files (http/https). Public files only; the Actor does not log in anywhere.

## `ocrMode` (type: `string`):

auto = OCR only pages with no usable text layer (scans, images, broken fonts); these cost $0.008 per page vs $0.0008 for a text page, up to maxOcrPages. always = OCR every page (also capped by maxOcrPages). never = text layer only (cheapest).

## `maxOcrPages` (type: `integer`):

At most this many pages are OCR'd in the whole run ($0.008 each). Once reached, further pages that would need OCR are returned from the text layer with ocrSkipped = true and charged as text pages ($0.0008), or free if they have no text layer. 0 = never OCR. Applies to auto and always modes.

## `ocrLanguages` (type: `string`):

Tesseract language codes joined with +. Installed: ara, eng.

## `ocrDpi` (type: `integer`):

Higher = better on small print but slower. 200-300 is typical.

## `minTextCharsPerPage` (type: `integer`):

In auto mode, pages whose text layer has fewer meaningful characters than this are OCR'd.

## `maxPagesPerDocument` (type: `integer`):

Pages beyond this are skipped (and not charged). Hard cap 2000.

## `maxFileSizeMb` (type: `integer`):

Larger files are rejected before download completes.

## `outputMode` (type: `string`):

page = one row per page (best for RAG / citations). document = one row per PDF with all pages joined.

## `normalizeArabic` (type: `boolean`):

Convert Arabic presentation-form glyphs to standard letters (NFKC), remove tatweel (ـ), collapse whitespace.

## `convertArabicDigits` (type: `boolean`):

Turn ٠١٢٣٤٥٦٧٨٩ and ۰۱۲۳۴۵۶۷۸۹ into ASCII digits (useful for amounts, dates, IDs).

## Actor input object example

```json
{
  "pdfUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "ocrMode": "auto",
  "maxOcrPages": 50,
  "ocrLanguages": "ara+eng",
  "ocrDpi": 250,
  "minTextCharsPerPage": 25,
  "maxPagesPerDocument": 100,
  "maxFileSizeMb": 50,
  "outputMode": "page",
  "normalizeArabic": true,
  "convertArabicDigits": false
}
```

# Actor output Schema

## `results` (type: `string`):

One row per PDF page with normalised text and the extraction method (text layer or OCR).

## `summary` (type: `string`):

Run summary: documents processed, page counts and OCR usage.

# 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 = {
    "pdfUrls": [
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("saif-tools/arabic-pdf-text-extractor").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 = { "pdfUrls": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"] }

# Run the Actor and wait for it to finish
run = client.actor("saif-tools/arabic-pdf-text-extractor").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 '{
  "pdfUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}' |
apify call saif-tools/arabic-pdf-text-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,saif-tools/arabic-pdf-text-extractor"
        }
    }
}
```

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/PQLKXqsBtsGmYzRmh/builds/7FDSF6bloBXS445DD/openapi.json
