# Haraj Scraper (`crawlerbros/haraj-scraper`) Actor

Scrape Haraj (haraj.com.sa) - Saudi Arabia's largest classifieds marketplace. Search by keyword, browse by category/city, look up listings by ID, pull a seller's listings, or fetch trending keywords. Get price (SAR), city, description, images, posted date.

- **URL**: https://apify.com/crawlerbros/haraj-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Automation, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## Haraj Scraper

Scrape **Haraj** (haraj.com.sa) — Saudi Arabia's largest online classifieds marketplace, covering cars, real estate, electronics, furniture, jobs, services, and everything in between. Search by keyword, browse by category or city, look up specific listings by ID/URL, or pull every active listing from a seller. HTTP-only via Haraj's own public GraphQL API. No auth, no cookies, no proxy.

### What this actor does

- **Five modes:** `search` (keyword), `browse` (category / city / latest feed), `byId` (lookup by listing ID or URL), `byAuthor` (all listings from a seller), `trending` (site-wide trending search keywords — not listings)
- **20 categories and 21 cities/regions** (Saudi Arabia, Kuwait, UAE, Bahrain) — filter by one or several at once, plus free-text `subTag`, `carBodyType`, and `neighborhood` for finer brand/model, car body style, and town-level narrowing
- **Rich filters:** price range (SAR), only-with-image, only-with-video, exclude a category, hide dealer/store showroom listings (mode=search), sort order
- **Category-specific enrichment:** car specs (model year, mileage, fuel, transmission, condition), real estate REGA registration numbers, job details, general spec attributes, and an optional `similarListings[]` lookup (mode=byId)
- **Empty fields are omitted**

### Output per listing

- `id` — Haraj numeric listing ID
- `title`, `description`
- `priceSAR` — price in Saudi Riyal (omitted when the seller didn't state a price)
- `category` — the listing's most specific category tag
- `categories[]` — full list of category/brand/model tags Haraj assigned to the listing
- `city`, `geoCity`, `geoNeighborhood`, `geoHash` — region, city/town, neighborhood, and a geohash for the listing's approximate coordinates
- `images[]`, `thumbnailUrl`
- `url` — canonical listing URL
- `authorUsername`, `authorId` — seller / store name and Haraj's numeric account ID (useful for disambiguating sellers who share a display name)
- `postedDate`, `updatedDate` — ISO 8601 UTC (updatedDate only present when different from postedDate — e.g. the seller renewed/bumped the ad)
- `hasImage`, `hasVideo`, `isPromoted`, `commentCount`
- `upvotes`, `downvotes` — Haraj's community "helpful / not helpful" feedback counts on the listing (0 is a real "no votes yet" value, not a missing one)
- `car` — for vehicle listings: `listingType`, `is4WheelDrive`, `modelYear`, `mileageKm`, `fuelType`, `transmission`, `condition`, `vehicleType`, `bank`
- `realEstate` — for property listings: `regaAdvertiserRegistrationNumber`, `regaAuthorizationNumber`
- `job` — for job listings (when reachable via search/byId/byAuthor): `offerType`, `experienceLevel`, `contractType`, `qualification`, `commercialRegisterNumber`
- `specs` — key/value attributes Haraj extracted for the item (e.g. laptop brand, RAM size, screen size)
- `similarListings[]` — only when `mode=byId` and `includeSimilarListings=true`: related listings (`id`, `title`, `url`, `priceSAR`, `thumbnailUrl`, `city`)
- `recordType` — `listing` / `carListing` / `realEstateListing` / `jobListing`, `scrapedAt`

### Output for `mode=trending`

Each record is a trending search keyword, not a listing:

- `keyword` — the trending search term
- `score` — Haraj's relevance/popularity score for the keyword over the requested window (0-1 range observed)
- `rangeInDays` — the window that was requested
- `recordType` — `trendingKeyword`, `scrapedAt`

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `search` | `search` / `browse` / `byId` / `byAuthor` / `trending` |
| `searchQuery` | string | – | Free-text keyword (mode=search, required for this mode) |
| `category` | string | – | One of 20 top-level categories (mode=search, browse) |
| `subTag` | string | – | A specific brand/model tag, narrower than `category` (mode=search, browse). Takes priority over `category` for upstream narrowing when both are set. |
| `carBodyType` | string | – | One of 12 car body styles (mode=search, browse). Takes priority over `subTag`/`category` for upstream narrowing when both are set. |
| `city` | string | – | One of 21 cities/regions (mode=search, browse) |
| `cities` | array | `[]` | Multiple cities/regions at once, OR-combined with `city` if both set (mode=search, browse) |
| `neighborhood` | string | – | A specific town/district, narrower than `city`/`cities` — ANDed with them (mode=search, browse, byAuthor) |
| `excludeCategory` | string | – | Drop results from this category |
| `postIds` | array | – | Listing IDs or full URLs (mode=byId, required for this mode) |
| `authorUsername` | string | – | Exact seller/store username (mode=byAuthor, required for this mode) |
| `includeSimilarListings` | bool | `false` | Attach `similarListings[]` per record (mode=byId only) |
| `trendingRangeDays` | int | `1` | Trending window in days, 1–14 (mode=trending) |
| `priceMin` | int | – | Drop listings priced below this (SAR) |
| `priceMax` | int | – | Drop listings priced above this (SAR) |
| `onlyWithImage` | bool | `false` | Only listings with photos |
| `onlyWithVideo` | bool | `false` | Only listings with video |
| `hideShowRooms` | bool | `false` | Exclude dealer/store-seller listings (mode=search only) |
| `sortBy` | string | `recentlyActive` | `recentlyActive` (bumped/renewed first) / `newestPosted` (by original post date) |
| `maxItems` | int | `30` | Hard cap (1–1000) |

#### Example: search with price range

```json
{
  "mode": "search",
  "searchQuery": "ايفون",
  "priceMin": 1000,
  "priceMax": 3000,
  "maxItems": 50
}
```

#### Example: browse cars for sale in Jeddah

```json
{
  "mode": "browse",
  "category": "حراج السيارات",
  "city": "جده",
  "onlyWithImage": true,
  "maxItems": 50
}
```

#### Example: lookup specific listings by ID or URL

```json
{
  "mode": "byId",
  "postIds": ["186610241", "https://haraj.com.sa/11186610633/ايفون_17_برو/"]
}
```

#### Example: every active listing from a seller

```json
{
  "mode": "byAuthor",
  "authorUsername": "معرض رائد علي القحطاني",
  "maxItems": 100
}
```

### Use cases

- **Price intelligence** — track asking prices for cars, phones, or real estate across Saudi cities
- **Lead generation** — monitor new listings in a specific category or city as they're posted
- **Market research** — analyze categories, brands, and specs trending on Saudi Arabia's largest classifieds site
- **Seller monitoring** — track everything a specific store/seller has listed
- **Real estate analytics** — pull REGA-licensed property listings with registration details
- **Automotive analytics** — collect model year, mileage, fuel type, and condition across car listings

### FAQ

**What is Haraj?** Haraj (حراج) is Saudi Arabia's largest classifieds marketplace, used for buying and selling cars, real estate, electronics, and general goods across Saudi Arabia, Kuwait, UAE, and Bahrain. This actor is an independent, third-party tool using Haraj's own public API — it is not affiliated with or endorsed by Haraj.

**Why is `priceSAR` missing on some listings?** Haraj lets sellers post without stating a price (common for "message me" listings). The actor omits the field entirely rather than showing a fake `0`.

**What's the difference between `search` and `browse`?** `search` matches a free-text keyword against listing titles/descriptions (optionally narrowed by category/city/price). `browse` returns listings for a category and/or city (or the site-wide latest feed if neither is set) without requiring a keyword.

**Is Jobs a supported category?** Haraj's Jobs section (`وظائف`) is powered by a separate service that isn't reachable through the same public search/browse API used for the rest of the site, so it isn't included in the `category` dropdown. Individual job posts that also surface through keyword search or a specific seller's listings are still returned (with a populated `job` object) when found that way.

**Will `maxItems` always be fully delivered?** Not always, and this is expected. Haraj's own pagination only exposes a finite window of unique listings for a given `search`/`browse` request before it starts repeating already-seen listings. The unfiltered site-wide "latest" feed (`browse` with no category/city) has the smallest window, typically a few hundred listings; a specific keyword search or a category/city-scoped browse typically goes deeper. When a run hits this plateau it stops rather than looping forever, and reports the actual delivered count vs. the requested `maxItems` in the run's status message.

**What's the difference between `city` and `cities`?** `city` restricts results to exactly one city/region. `cities` (mode=search, browse) restricts to ANY of several cities/regions at once — useful for e.g. "show me listings in the three largest cities" in a single run instead of three separate runs. If both are set, a record needs to match only one of the two to be included (OR), not both.

**What's `neighborhood` and how is it different from `city`?** `city`/`cities` match Haraj's 21 top-level regions (e.g. "الرياض" Riyadh, the whole metro area). `neighborhood` matches Haraj's finer per-listing town/district tag (the `geoCity` output field — e.g. "الدرعية" Diriyah, a specific district within Riyadh). It's free-text (not a dropdown) because that value set runs into the hundreds and isn't stable enough to curate — enter the value exactly as it appears in a listing's own `geoCity` field. When combined with `city`/`cities`, a listing must satisfy both (AND).

**What's `carBodyType` and how does it relate to `category`/`subTag`?** `carBodyType` is a curated 12-value dropdown for a car's body style (sedan, SUV, hatchback, pickup, etc.). When more than one of `carBodyType`/`subTag`/`category` is set, `carBodyType` takes priority for upstream narrowing — the others still apply afterward as extra checks (e.g. `carBodyType="سيدان"` + `subTag="تويوتا"` correctly returns only Toyota sedans). It's most useful set alone or combined with `city`/`category`, since most body types already imply a brand-agnostic style search.

**What's `subTag` and how is it different from `category`?** `category` is one of Haraj's 20 top-level sections (e.g. "حراج السيارات" Cars). `subTag` is any finer node in Haraj's own tag tree attached to a listing — a specific brand ("تويوتا" Toyota) or model ("كامري" Camry). It's free-text because that tree runs into the thousands and isn't a stable curated set. When both `category` and `subTag` are set, `subTag` takes priority for upstream narrowing — `category` still applies afterward as an extra check (harmless when the two are consistent, e.g. Cars + Toyota; returns 0 results if they contradict, e.g. Real Estate + Toyota).

**What does `mode=trending` return?** Not listings — it's Haraj's own site-wide trending-search-keywords feed (what people are searching for over the last `trendingRangeDays` days), useful for spotting a hot keyword before running a `search`. Haraj's endpoint only has data for 1-14 day windows; values outside that range are clamped.

# Actor input Schema

## `mode` (type: `string`):

What to fetch.

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

Free-text keyword search, in Arabic or English (mode=search). Required for this mode.

## `category` (type: `string`):

Restrict results to a Haraj top-level category (mode=search, browse). Leave as "(any category)" for no category filter.

## `city` (type: `string`):

Restrict results to a Saudi city/region (or Kuwait, UAE, Bahrain) (mode=search, browse). Leave as "(any city)" for no city filter.

## `subTag` (type: `string`):

Narrower than `category` — a specific brand or model tag from Haraj's own hierarchical tag tree, e.g. `تويوتا` (Toyota), `كامري` (Camry), `ايفون` (iPhone) (mode=search, browse). Confirmed via live probing that Haraj's finer tags filter correctly server-side, but the full tag tree runs into the thousands (every brand/model/variant across every category) so it isn't a stable curated dropdown — enter the exact tag text, e.g. copied from a listing's own `categories[]` output field. When set, this takes priority over `category` for narrowing (only one tag value can be sent upstream at a time) — `category`/`excludeCategory` still apply as an extra check afterwards.

## `carBodyType` (type: `string`):

Restrict results to a specific car body style (mode=search, browse). Haraj's own frontend uses this exact 12-value vocabulary as a hidden filter axis, distinct from `subTag`'s brand/model tags — confirmed via live probing that each value reliably narrows to the correct body style server-side. Since only one tag value can be sent upstream at a time, this takes priority over `subTag`/`category` for that slot when set (both still apply as extra client-side checks afterwards). Leave as "(any body type)" for no filter.

## `cities` (type: `array`):

Restrict results to any of several Saudi cities/regions (or Kuwait, UAE, Bahrain) in one request — a listing matching ANY selected city passes (mode=search, browse). Combines with `city` above (both are OR'd together) if both are set. Leave empty for no multi-city filter.

## `neighborhood` (type: `string`):

Restrict results to an exact match of Haraj's finer town/neighborhood-level location tag (the `geoCity` output field) — e.g. `الدرعية` (Diriyah) or `الخبر` (Al Khobar). More granular than `city`/`cities` (which match the broader region/city) and combines with them as an AND — a listing must match both when both are set. Free-text because Haraj's neighborhood/town list runs into the hundreds and isn't a stable curated set — enter the exact value from a listing's own `geoCity` output field to filter by it precisely (mode=search, browse, byAuthor).

## `excludeCategory` (type: `string`):

Drop results from this category, even if they match other filters (mode=search, browse).

## `postIds` (type: `array`):

Haraj numeric listing IDs or full listing URLs, e.g. `186610241` or `https://haraj.com.sa/11186610241/...`. Required for this mode.

## `authorUsername` (type: `string`):

Exact Haraj seller username, e.g. `معرض رائد علي القحطاني`. Required for this mode.

## `includeSimilarListings` (type: `boolean`):

For each resolved listing, attach a `similarListings[]` array of related Haraj listings (id, title, url, priceSAR, thumbnailUrl, city) using Haraj's own "related items" lookup. Adds one extra request per listing ID, so only available for mode=byId.

## `trendingRangeDays` (type: `integer`):

How many trailing days of search activity to rank trending keywords over. Haraj's public trending-keywords endpoint only has data for 1-14 days.

## `priceMin` (type: `integer`):

Drop listings priced below this (SAR). Listings with no price stated always pass through.

## `priceMax` (type: `integer`):

Drop listings priced above this (SAR). Listings with no price stated always pass through.

## `onlyWithImage` (type: `boolean`):

Skip listings that have no photos.

## `onlyWithVideo` (type: `boolean`):

Skip listings that have no video.

## `hideShowRooms` (type: `boolean`):

Exclude listings from dealer/store-type sellers (e.g. car-parts shops, showroom accounts), keeping only individual sellers (mode=search only — Haraj's browse/byAuthor endpoint doesn't support this filter).

## `sortBy` (type: `string`):

Ordering of results (mode=search, browse).

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

Hard cap on emitted records. Haraj's own pagination for a given search/browse request only exposes so many unique listings before it starts repeating — narrow queries may return fewer than this cap even when nothing is wrong; the run's status message explains when that happens.

## Actor input object example

```json
{
  "mode": "search",
  "searchQuery": "ايفون",
  "category": "",
  "city": "",
  "carBodyType": "",
  "cities": [],
  "excludeCategory": "",
  "postIds": [],
  "includeSimilarListings": false,
  "trendingRangeDays": 1,
  "onlyWithImage": false,
  "onlyWithVideo": false,
  "hideShowRooms": false,
  "sortBy": "recentlyActive",
  "maxItems": 30
}
```

# Actor output Schema

## `listings` (type: `string`):

Dataset containing all scraped Haraj listings.

# 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 = {
    "mode": "search",
    "searchQuery": "ايفون",
    "category": "",
    "city": "",
    "subTag": "",
    "carBodyType": "",
    "cities": [],
    "neighborhood": "",
    "excludeCategory": "",
    "postIds": [],
    "includeSimilarListings": false,
    "trendingRangeDays": 1,
    "onlyWithImage": false,
    "onlyWithVideo": false,
    "hideShowRooms": false,
    "sortBy": "recentlyActive",
    "maxItems": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/haraj-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 = {
    "mode": "search",
    "searchQuery": "ايفون",
    "category": "",
    "city": "",
    "subTag": "",
    "carBodyType": "",
    "cities": [],
    "neighborhood": "",
    "excludeCategory": "",
    "postIds": [],
    "includeSimilarListings": False,
    "trendingRangeDays": 1,
    "onlyWithImage": False,
    "onlyWithVideo": False,
    "hideShowRooms": False,
    "sortBy": "recentlyActive",
    "maxItems": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/haraj-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 '{
  "mode": "search",
  "searchQuery": "ايفون",
  "category": "",
  "city": "",
  "subTag": "",
  "carBodyType": "",
  "cities": [],
  "neighborhood": "",
  "excludeCategory": "",
  "postIds": [],
  "includeSimilarListings": false,
  "trendingRangeDays": 1,
  "onlyWithImage": false,
  "onlyWithVideo": false,
  "hideShowRooms": false,
  "sortBy": "recentlyActive",
  "maxItems": 30
}' |
apify call crawlerbros/haraj-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/haraj-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/2GaccA2iHgAa885mX/builds/sH5nOxOYkRE34ODfS/openapi.json
