# Haraj (حراج) Scraper - Saudi Classifieds Listings + Prices (`lanternlab318/haraj`) Actor

Search haraj.com.sa, Saudi Arabia's largest classifieds site, and get public listings: title, price in SAR, city, category, photos, publish time and URL. No login, no cookies. Rows are deduplicated; empty and partial search terms are reported separately and never charged.

- **URL**: https://apify.com/lanternlab318/haraj.md
- **Developed by:** [Lantern Lab](https://apify.com/lanternlab318) (community)
- **Categories:** E-commerce, Lead generation, Real estate
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.25 / 1,000 listings

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

## Haraj (حراج) Scraper — Saudi Classifieds Listings + Prices

A Lantern Lab Actor. Give it search terms, optionally one city and one category, and get haraj.com.sa's public classifieds back: **title**, **price in SAR**, **city and district**, **category**, **photos**, **publish time** and the **listing URL** — for cars, real estate, jobs, electronics and everything else Saudi Arabia's largest classifieds site carries. Search in Arabic: the site is Arabic-first, and كورفيت returns far more than "Corvette". No Haraj login, no cookies, no account of your own.

**Price: USD 1.99 per 1,000 listing rows. No run-start fee.** Search terms that return nothing, per-term status rows and the run summary are free — you pay for listings, not for searches.

### What you get

- **The newest listings per search term.** Every term is searched separately, in the order you gave it, and every row is labelled with the term that found it and where it ranked.
- **Deduplicated across the whole run.** `listingId` is Haraj's own listing identifier, so an ad that matches several of your terms is written once and charged once — the term's status row says how many were skipped as duplicates.
- **City and district, not just a region.** Haraj's `city` is the region (الرياض, جده…); `geoCity` and `geoNeighborhood` carry the town and the district when the ad states them.
- **Prices, honestly.** `price` is the numeric asking price in SAR, `formattedPrice` exactly as the site prints it. Many ads state no price at all — those rows carry `price: null`, never a guess.
- **Nothing charged for an empty search.** A term Haraj answers with no listings is an empty result, and its status row says so in plain words. A term cut short by your `maxListings`, by the request ceiling, or by a refusal from Haraj is `partial: true` with the reason — partial is never reported as empty.
- **Seller privacy by design.** Haraj exposes the seller's username on every ad, but a classifieds seller is usually a private person, so this Actor does not collect it. No personal names, phone numbers or e-mail addresses anywhere in the output.
- **It caps itself.** `maxUpstreamRequests` is a hard ceiling on the requests a run sends to Haraj, worked out from your input when you leave it empty, so a run is never unbounded.

### What it does not do

- **One page per search term — that is everything Haraj's public search gives.** Each search page carries the newest ~19 listings and Haraj's public site offers no URL that turns the page, so a term cannot return more than that. The status row says `pageLimitReached` when the site's page size, not your limit, ended the list.
- `sellerType` is Haraj's own label — the site labels every listing "AD" and does not tell a dealer from a private seller.
- No listing detail pages, no comment threads, no seller profiles, no phone-number reveal, no image downloads.
- No scheduling or monitoring built in; use Apify schedules.

### Input

| Field | Default | Meaning |
| --- | --- | --- |
| `searchQueries` | required | One search term per line, Arabic preferred. Every term is searched separately. |
| `city` | — | Restrict every search to one city or region, exactly as Haraj writes it: الرياض, جده, الشرقيه… Empty searches all of Saudi Arabia. |
| `category` | — | Keep only listings in one Haraj category, exactly as the site writes it (e.g. حراج السيارات). Listings outside it are dropped and counted, not silently lost. |
| `maxListings` | `100` | Upper bound on listing rows for the whole run. |
| `maxUpstreamRequests` | derived | Hard ceiling on HTTP requests to Haraj for the whole run. |
| `proxyConfiguration` | Apify Proxy | Datacenter by default, which is what this Actor is measured on; Haraj serves its public search pages over it without a bot check. |

```json
{
  "searchQueries": ["كورفيت", "ايفون 16"],
  "city": "الرياض",
  "category": "حراج السيارات",
  "maxListings": 100
}
```

### Output

One row per listing, `recordType: "listing"`:

```json
{
  "recordType": "listing",
  "listingId": 123456789,
  "title": "كورفيت 2023 نظيفه جدا",
  "price": 285000,
  "currency": "SAR",
  "formattedPrice": "285,000 ريال",
  "city": "الرياض",
  "geoCity": "الرياض",
  "geoNeighborhood": "النرجس",
  "category": "حراج السيارات",
  "tags": ["حراج السيارات"],
  "publishedAt": "2026-10-23T14:12:00.000Z",
  "imageUrls": ["https://......mp4pic.com/..."],
  "imageCount": 6,
  "hasImage": true,
  "hasVideo": false,
  "url": "https://haraj.com.sa/123456789/",
  "sellerType": "AD",
  "isPromoted": false,
  "commentCount": 4,
  "carInfo": { "make": "شفروليه", "year": "2023" },
  "query": "كورفيت",
  "cityFilter": "الرياض",
  "categoryFilter": "حراج السيارات",
  "rank": 1,
  "partial": false,
  "partialReason": null,
  "collectedAt": "2026-10-24T03:00:00.000Z"
}
```

Each search term also gets one free `query_status` row (how many listings it returned, how many were duplicates, whether it was partial and why), and the last row is a `RUN_SUMMARY` of the whole run — both uncharged.

### Limits

- Search terms are capped at 100 per run, `maxListings` at 10,000, `maxUpstreamRequests` at 200.
- The site serves every page in Arabic; titles and categories come through in Arabic exactly as the site prints them.
- This Actor only collects what Haraj shows a logged-out visitor: the public search pages. If Haraj changes how those pages are built, runs report `upstream_error` or `blocked` per term instead of guessing.

# Actor input Schema

## `searchQueries` (type: `array`):

One Haraj search term per line. Search in Arabic - the site is Arabic-first and an Arabic term (for example كورفيت for Corvette) returns far more than its English spelling. Every term is searched separately and its listings are labelled with it. A listing that matches several of your terms is returned once and charged once.

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

Restrict every search to one city or region, exactly as Haraj writes it, for example الرياض (Riyadh), جده (Jeddah) or الشرقيه (Eastern Province). Leave empty to search all of Saudi Arabia. A wrong spelling can return an empty result, so copy the name from the site's city filter.

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

Keep only listings whose category matches this Haraj category name exactly, for example حراج السيارات (cars). Leave empty to keep every category. A listing outside the category is dropped and counted on the term's status row, so a wrong spelling shows up as dropped listings, not as a silent empty result.

## `maxListings` (type: `integer`):

Upper bound on the listing rows this run writes in total. Each search term returns at most about 19 listings - Haraj's search page carries no further pages through this Actor - so with many terms the bound is usually yours, and the term that hits it is marked partial with reason "listing_limit_reached".

## `maxUpstreamRequests` (type: `integer`):

Hard ceiling on the HTTP requests this run sends to Haraj. One search page per term is all a run needs, so the Actor works a ceiling out from your search terms when this is left empty. At the ceiling collection stops - the search terms not reached are written with partialReason "upstream_request_cap" and the run then ends normally.

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

The proxy the requests to Haraj go through. The default (Apify Proxy, datacenter) is the cheapest and is what this Actor is measured on; Haraj serves its public search pages over it without a login, a cookie or a bot check.

## Actor input object example

```json
{
  "searchQueries": [
    "كورفيت"
  ],
  "maxListings": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

## `runSummary` (type: `string`):

No description

# 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 = {
    "searchQueries": [
        "كورفيت"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/haraj").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 = { "searchQueries": ["كورفيت"] }

# Run the Actor and wait for it to finish
run = client.actor("lanternlab318/haraj").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 '{
  "searchQueries": [
    "كورفيت"
  ]
}' |
apify call lanternlab318/haraj --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lanternlab318/haraj"
        }
    }
}
```

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/hdcsIOCPVFNMGnxO4/builds/jeshuyfgYCDscp1g6/openapi.json
