# Aqar.fm Scraper (`datascrapers/aqar-fm-scraper`) Actor

Scrapes Saudi real estate listings from aqar.fm. Supports Arabic search paths, start URLs, and categoryId/cityId structured searches.

- **URL**: https://apify.com/datascrapers/aqar-fm-scraper.md
- **Developed by:** [Data Scrapers](https://apify.com/datascrapers) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 dataset items

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/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

**Aqar.fm Scraper** creates a structured dataset of Saudi real estate listings collected from aqar.fm. Each dataset item is one listing and can include the listing id and URL, Arabic title, category, sale or rent type, price, area, beds, bathrooms, city, district, coordinates, images, and advertiser fields. Query the source with Arabic search paths, a category and city, or start URLs, limit results with `maxItems`, and retrieve records through the Apify Dataset API or export them as JSON, CSV, Excel, XML, or another supported format.

[![Aqar.fm Scraper — input to structured data](https://raw.githubusercontent.com/faizanalii/apify-actor-assets/main/aqar-fm-scraper/aqar-fm-scraper-value-demo.gif)](https://apify.com/datascrapers/aqar-fm-scraper)

### Dataset at a glance

| Property | Value |
|---|---|
| Source | aqar.fm (sa.aqar.fm listing URLs) |
| Record unit | One property listing |
| Input methods | `searchQueries`, `categoryId` plus `cityId`, or `startUrls` |
| Main identifiers | `id` |
| Delivery | Apify Dataset and API |
| Export formats | JSON, CSV, Excel, XML, and other Apify dataset exports |
| Update model | Fresh records per Actor run |
| Pricing | $0.002 per `dataset-item` ($2.00 per 1,000 listings) on every plan |

### Coverage and available records

The Actor collects listing cards from aqar.fm search results. A run needs at least one of `searchQueries`, `startUrls`, or `categoryId`. `cityId` is combined with `categoryId` when you use the dropdown search.

Supported:

- Arabic search paths such as `دور-للبيع/الرياض` and full search URLs on sa.aqar.fm
- Category and city filters from the input dropdowns
- Sale and rent categories exposed by those dropdowns
- Pagination, with 20 listings observed per search page
- `maxItems` and `maxPages` limits. `0` means no cap for that control
- Locale `ar` or `en` on the request

Observed on search cards:

- Listing id, public URL, title, category, price text, area, beds, living rooms, bathrooms
- City, direction, district, address, and coordinates
- Main image and image URL list
- Advertiser user id, name, user type, and company name when the card includes them

Not currently collected:

- A separate detail-page body beyond the search card
- Historical price series
- Listings the search response does not return

`rentPeriod` is present on the record and is null for sale listings. `isAuction` may be null.

### Data dictionary

Types match a saved Riyadh sale listing. Nested objects are listed after the main table.

| Field | Type | Nullable | Description | Example |
|---|---|---:|---|---|
| `id` | integer | No | Aqar listing id. Best field for deduplication. | `6710892` |
| `url` | string | No | Public listing URL on sa.aqar.fm. | `https://sa.aqar.fm/...-6710892` |
| `title` | string or null | Yes | Arabic listing title. | `دور للبيع في شارع بدر...` |
| `category` | integer or null | Yes | Numeric category id. | `22` |
| `categoryName` | string or null | Yes | Category label. | `دور للبيع` |
| `gaListingType` | string or null | Yes | Sale or rent marker from the card. | `sale` |
| `gaPropertyCategory` | string or null | Yes | Property category marker. | `floor` |
| `price` | number or null | Yes | Numeric price. Currency is the source listing currency, observed as SAR on these cards. | `599000` |
| `regaTotalPrice` | number or null | Yes | REGA total price when the card includes it. | `599000` |
| `priceText` | string or null | Yes | Formatted price text. | `599,000` |
| `rentPeriod` | integer or null | Yes | Rent period code. Null on the saved sale card. | `null` |
| `rentPeriodText` | string or null | Yes | Rent period label. Empty string on the saved sale card. | `""` |
| `area` | number or null | Yes | Area in square meters. | `183` |
| `beds` | integer or null | Yes | Bedroom count. | `3` |
| `livings` | integer or null | Yes | Living-room count. | `1` |
| `wc` | integer or null | Yes | Bathroom count. | `3` |
| `address` | string or null | Yes | Street, district, city, and region line. | `شارع بدر, حي بدر, مدينة الرياض, منطقة الرياض` |
| `addressText` | string or null | Yes | Shorter district and city line. | `حي بدر, الرياض` |
| `district` | string or null | Yes | District name. | `حي بدر` |
| `districtId` | integer or null | Yes | District id. | `648` |
| `direction` | string or null | Yes | City direction label. | `جنوب الرياض` |
| `directionId` | integer or null | Yes | Direction id. | `1` |
| `city` | string or null | Yes | City name. | `الرياض` |
| `cityId` | integer or null | Yes | City id. | `21` |
| `latitude` | number or null | Yes | Latitude copied from `location`. | `24.503137` |
| `longitude` | number or null | Yes | Longitude copied from `location`. | `46.723204` |
| `published` | boolean or null | Yes | Published flag from the card. | `true` |
| `dailyRentable` | boolean or null | Yes | Daily-rent flag. | `false` |
| `isAuction` | boolean or null | Yes | Auction flag. Null on the saved card. | `null` |
| `mainImage` | string or null | Yes | Primary image URL. | `https://images.aqar.fm/...` |
| `imageUrls` | array of strings | No | Image URLs. Empty when the card has none. | See example |
| `userId` | integer or null | Yes | Advertiser user id. | `5293649` |
| `userName` | string or null | Yes | Advertiser name as shown on the card. | Source name string |
| `userType` | integer or null | Yes | Advertiser type code. | `1` |
| `userReview` | number or null | Yes | Advertiser review score. Null on the saved card. | `null` |
| `companyName` | string or null | Yes | Company name when present. | Source company string |
| `searchPath` | string | No | Search path that produced the card. | `دور-للبيع/الرياض` |
| `page` | integer | No | Result page number. | `1` |
| `path` | string or null | Yes | Listing path. | `/دور-للبيع/الرياض/...` |

`location` object:

| Field | Type | Nullable | Description |
|---|---|---:|---|
| `lat` | number | Yes | Latitude |
| `lng` | number | Yes | Longitude |

`specs` is an array of `{ "value", "key", "icon" }` display chips. `value` and `key` are strings or null. The saved card uses keys such as `area`, `beds`, `wc`, and `livings`.

### Example dataset record

Saved from category `22` (floor for sale) in city `21` (Riyadh), search path `دور-للبيع/الرياض`, page 1. The stored `imageUrls` list is longer; this example keeps the first two URLs. `specs` keeps the first chip. Advertiser name fields are omitted here and are present on the stored record.

```json
{
  "id": 6710892,
  "url": "https://sa.aqar.fm/%D8%AF%D9%88%D8%B1-%D9%84%D9%84%D8%A8%D9%8A%D8%B9/%D8%A7%D9%84%D8%B1%D9%8A%D8%A7%D8%B6/%D8%AC%D9%86%D9%88%D8%A8-%D8%A7%D9%84%D8%B1%D9%8A%D8%A7%D8%B6/%D8%AD%D9%8A-%D8%A8%D8%AF%D8%B1/%D8%B4%D8%A7%D8%B1%D8%B9-%D8%A8%D8%AF%D8%B1-%D8%AD%D9%8A-%D8%A8%D8%AF%D8%B1-%D9%85%D8%AF%D9%8A%D9%86%D8%A9-%D8%A7%D9%84%D8%B1%D9%8A%D8%A7%D8%B6-%D9%85%D9%86%D8%B7%D9%82%D8%A9-%D8%A7%D9%84%D8%B1%D9%8A%D8%A7%D8%B6-6710892",
  "title": "دور للبيع في شارع بدر, حي بدر, مدينة الرياض, منطقة الرياض",
  "category": 22,
  "categoryName": "دور للبيع",
  "gaListingType": "sale",
  "gaPropertyCategory": "floor",
  "price": 599000,
  "regaTotalPrice": 599000,
  "priceText": "599,000",
  "rentPeriod": null,
  "rentPeriodText": "",
  "area": 183,
  "beds": 3,
  "livings": 1,
  "wc": 3,
  "address": "شارع بدر, حي بدر, مدينة الرياض, منطقة الرياض",
  "addressText": "حي بدر, الرياض",
  "district": "حي بدر",
  "districtId": 648,
  "direction": "جنوب الرياض",
  "directionId": 1,
  "city": "الرياض",
  "cityId": 21,
  "location": { "lat": 24.503137, "lng": 46.723204 },
  "latitude": 24.503137,
  "longitude": 46.723204,
  "published": true,
  "dailyRentable": false,
  "isAuction": null,
  "mainImage": "https://images.aqar.fm/m/mini/052936495_1780255618476.jpg",
  "imageUrls": [
    "https://images.aqar.fm/m/mini/052936495_1780255618476.jpg",
    "https://images.aqar.fm/m/mini/052936497_1780255618430.jpg"
  ],
  "specs": [{ "value": "183م²", "key": "area", "icon": "v2/area" }],
  "userId": 5293649,
  "userType": 1,
  "userReview": null,
  "searchPath": "دور-للبيع/الرياض",
  "page": 1,
  "path": "/دور-للبيع/الرياض/جنوب-الرياض/حي-بدر/شارع-بدر-حي-بدر-مدينة-الرياض-منطقة-الرياض-6710892"
}
```

### Query and input reference

| Input | Type | Required | Default | Accepted values | Description |
|---|---|---:|---|---|---|
| `searchQueries` | string array | No | `[]` | Arabic search path segments | Example prefill: `دور-للبيع/الرياض`. |
| `categoryId` | string | No | None | Category ids in the input dropdown | Combined with city to build a search. |
| `cityId` | string | No | None | City ids in the input dropdown | Prefill `21` is Riyadh. |
| `startUrls` | URL array | No | `[]` | Full aqar.fm search URLs | Used as search sources. |
| `maxItems` | integer | No | `0` | `0` or a positive integer | Maximum listings. `0` means no item cap. |
| `maxPages` | integer | No | `0` | `0` or a positive integer | Maximum pages per search. `0` means no page cap. |
| `locale` | string | No | `ar` | `ar`, `en` | Request locale. |
| `proxyConfiguration` | object | No | Apify residential proxy | Apify proxy editor | Proxy for search requests. |

A run must include `searchQueries`, `startUrls`, or `categoryId`.

Minimal request:

```json
{
  "categoryId": "22",
  "cityId": "21",
  "maxItems": 10,
  "maxPages": 1
}
```

Path request:

```json
{
  "searchQueries": ["دور-للبيع/الرياض"],
  "maxItems": 20
}
```

### Retrieve the data through the API

1. Start `datascrapers/aqar-fm-scraper` with the JSON input.
2. Wait until the run finishes, or call the synchronous dataset endpoint.
3. Read items from the run's default dataset.
4. Paginate with dataset `offset` and `limit`, or export from the Apify console.

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("datascrapers/aqar-fm-scraper").call(
    run_input={
        "searchQueries": ["دور-للبيع/الرياض"],
        "maxItems": 20,
    }
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["id"], item["city"], item["price"])
```

Other languages are listed on the Actor's API tab. This is the Apify Actor API, not an aqar.fm API.

### Data quality and record handling

- One pushed record is one search-card listing. The charge event name is `dataset-item`.
- Deduplicate on `id`. `url` is the public address for the same listing.
- The same id can appear if overlapping searches return it. Compare `id` before you merge runs.
- Null category, price, area, and advertiser fields mean the card omitted them. They are not backfilled.
- `latitude` and `longitude` repeat `location` when coordinates exist.
- `searchPath` and `page` are added by the Actor so you can trace which query produced the row.
- Image URLs point at aqar.fm image files. They are links, not downloaded files.
- A failed search page does not create a placeholder listing.

### Pricing and cost examples

The price file in the Actor folder sets **$2.00 per 1,000 listings**. That is **$0.002 per `dataset-item`**, the same amount on every Apify plan. One charge is created for each listing pushed to the dataset. `maxItems` and `maxPages` limit how many charges a run can create. Platform usage, including compute and proxy, is billed separately by Apify.

| Listings pushed | Estimated event cost |
|---:|---:|
| 1,000 | $2.00 |
| 10,000 | $20.00 |

The folder note about lower plan rates is not a separate tier table. These examples use the flat $0.002 event price.

### Limitations and responsible data use

- Coverage is the public search results your query returns, not a guaranteed national inventory.
- Arabic and English locales change request language. Stored labels follow the response.
- Prices, photos, and published flags change on the source. This dataset is not a historical archive.
- Advertiser names and company names are public card fields. Use them in line with the privacy and advertising rules that apply to your project.
- You are responsible for complying with aqar.fm's terms and applicable law.

### Dataset questions

#### What does one dataset item represent?

One aqar.fm property listing from a search result.

#### Which field should I use as a unique identifier?

Use `id`. The same number is the suffix of `url`.

#### Are fields nullable or conditional?

Yes. Price, area, rent period, auction state, and advertiser fields are null when the card omits them. Sale cards in the saved sample have `rentPeriod` null and `rentPeriodText` as an empty string.

#### Can I retrieve the records as CSV or JSON?

Yes. Export the default dataset as JSON, CSV, Excel, XML, or another Apify format.

#### How do I paginate large datasets?

Set `maxItems` and `maxPages` on the run. Read the dataset with `limit` and `offset`. Search pages observed in this Actor hold 20 listings.

#### Does the Actor return historical data?

No. Each run returns the listings available at request time. `page` is the search page, not a history version.

#### What counts as a billable result?

Each listing pushed with the `dataset-item` event. The price is $0.002 per listing.

### Related datasets from Data Scrapers

- [Apartments Scraper](https://apify.com/datascrapers/apartments-scraper) collects apartment listings you can compare on price, beds, and location fields.
- [99acres Scraper](https://apify.com/datascrapers/99acres-scraper) is another national listings dataset with property price and area fields.
- [99.co Scraper](https://apify.com/datascrapers/99-co-scraper) collects listing records for a different property portal.
- [Aengevelt Scraper](https://apify.com/datascrapers/aengevelt-scraper) collects property records from a commercial brokerage site.
- [Asunnot Oikotie.fi](https://apify.com/datascrapers/asunnot-oikotie-fi) collects Finnish listing cards with price, size, and address fields.

### Data Scrapers support

Need an additional field, record type, or export workflow? Contact Data Scrapers at stardustspotlight@gmail.com. Include a sample source URL, required fields, expected record volume, and preferred delivery format.

# Actor input Schema

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

Arabic search path segments from aqar.fm. Examples: دور-للبيع, شقق-للإيجار/الرياض, عقارات/الرياض.

## `categoryId` (type: `string`):

Property type filter. Combined with city to build the search automatically.

## `cityId` (type: `string`):

City filter (optional). Combined with category to narrow results.

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

Full aqar.fm search URLs. Examples: https://sa.aqar.fm/دور-للبيع, https://sa.aqar.fm/شقق-للإيجار/الرياض.

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

Maximum number of listings to scrape (0 = unlimited).

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

Maximum pagination pages per search (0 = unlimited, 20 listings per page).

## `locale` (type: `string`):

Site locale for requests.

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

Proxy settings for anti-bot protection.

## Actor input object example

```json
{
  "searchQueries": [
    "دور-للبيع/الرياض"
  ],
  "categoryId": "22",
  "cityId": "21",
  "startUrls": [],
  "maxItems": 0,
  "maxPages": 0,
  "locale": "ar",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

Dataset containing scraped aqar.fm property listings.

## `runStats` (type: `string`):

Run metadata including recordsScraped and timestamps.

# 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": [
        "دور-للبيع/الرياض"
    ],
    "categoryId": "22",
    "cityId": "21",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("datascrapers/aqar-fm-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 = {
    "searchQueries": ["دور-للبيع/الرياض"],
    "categoryId": "22",
    "cityId": "21",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("datascrapers/aqar-fm-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 '{
  "searchQueries": [
    "دور-للبيع/الرياض"
  ],
  "categoryId": "22",
  "cityId": "21",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call datascrapers/aqar-fm-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datascrapers/aqar-fm-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/FApL2LXsemoIGh9K5/builds/AMh6Mf9ansezSTROc/openapi.json
