# Anghami Scraper - Songs, Albums, Artists & Playlists (`abotapi/anghami-catalog-scraper`) Actor

Scrape Anghami's public catalog by keyword or player URL. Rows carry their kind (song, album, artist, playlist, tag) with title, artist, plays, followers, podcast flags and artwork. Detail views add the description and a podcast episode list, one per-record toggle.

- **URL**: https://apify.com/abotapi/anghami-catalog-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 catalog records

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

## Anghami Scraper: Songs, Albums, Artists, Playlists and Podcasts

Anghami Scraper turns Anghami, the MENA region's leading music and podcast streaming service, into structured catalog data. Get songs, albums, podcast episodes, audiobooks, artist profiles, curated playlists and the category tags behind genre and chart pages, with every row carrying its own kind alongside a unified core shape (title, artist, plays, likes, followers, artwork). Search by keyword for a catalog-wide sweep, or paste player URLs to walk one artist's discography, a playlist, a tag, or fetch a single song or album, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.

### Why This Scraper?

- **Full catalog coverage.** Songs, albums (including podcast and audiobook objects), artists, curated playlists and category tags, in one unified record shape.
- **Every row reports its own kind.** A mixed search feed filters cleanly in downstream code, and carries play counts, likes, follower counts, verified and podcast flags straight from the feed.
- **Two ways in.** Keyword search for catalog-wide sweeps, or paste player URLs to walk one artist's full discography, a playlist, a genre or chart tag, or fetch a single song or album object.
- **Detail view on demand.** A per-record toggle adds the store page description and, for album objects, the complete song and episode id list, charged once per record.
- **Built for recurring monitoring.** Incremental mode reports NEW, UPDATED, REAPPEARED and EXPIRED records for scheduled runs, and Resume continues an interrupted or capped run without paying twice.
- **MCP connector export.** Pipe results into Notion, Linear, Airtable or another app you already use.
- **Clean dataset contract.** A stable record identity (`kind:id`), canonical URL and artwork URL on every row, so downstream systems can join and dedupe reliably.

### Use Cases

- **Music discovery apps and playlist tools:** track new releases, charting songs and curated playlist changes across the MENA catalog.
- **Artist and label monitoring:** watch one artist's discography, play counts and follower growth over time with incremental mode.
- **Podcast and audiobook research:** collect podcast episodes and audiobook objects, with their own flags and descriptions, separately from music.
- **Market and catalog analytics:** build datasets of genres, tags and category collections for streaming trend analysis.
- **Data migration and cataloging:** pull structured song, album and artist metadata (ids, artwork, duration) into a companion app or database.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `recordId` | `"song:0000000"` |
| `kind` | `"song"` (also `"album"`, `"artist"`, `"playlist"`, `"tag"`) |
| `id` | `"0000000"` |
| `url` | `"https://play.anghami.com/song/0000000"` |
| `title` / `name` | `"Sample Song Title"` (song/album/playlist/tag rows use `title`, artist rows use `name`) |
| `artist` / `artistId` | `"Sample Artist"` / `"000000"` |
| `album` / `albumId` | `"Sample Album Title"` / `"0000000000"` |
| `durationSec` | `228` |
| `year` / `releasedate` | `2026` / `"2026-01-01"` |
| `genre` | `"Pop - Sample"` |
| `plays` / `likes` | `13000000` / `120000` |
| `followers` | `17600000` (artist and playlist rows) |
| `nbrSongs` / `itemCount` | `100` (album/podcast song count) / `42` (playlist item count) |
| `isPodcast` / `isAudiobook` | `false` / `false` |
| `isPodcaster` | `false` (artist rows) |
| `verified` / `explicit` | `true` / `false` |
| `lyrics` | `true` (song rows, lyrics availability flag) |
| `coverArt` / `imageUrl` | `"https://artwork.anghcdn.co/webp/?id=000000000&size=400"` |
| `keywords` | `["Sample Song Title", "Sample Artist"]` |
| `description` / `webTitle` / `webDescription` | detail-view extras (Fetch record details on) |
| `curator` / `songIds` | `"Anghami Editorial"` / `["0000000000", "0000000001"]` (album objects, detail toggle on) |
| `changeType` / `changedFields` | `"NEW"` / `[]` (incremental mode) |
| `scrapedAt` / `firstSeenAt` / `lastSeenAt` | `"2026-01-01T00:00:00Z"` (incremental mode adds the latter two) |
| `detailCarriedForward` | `true` (a failed detail re-read repaired from the previous run's snapshot) |

### How to Use

1. Pick a **mode**: `search` (by keyword) or `url` (paste player URLs).
2. Fill in the search terms or URLs, and narrow with **Kinds to keep** or **Exclude podcasts** if needed.
3. Turn on **Fetch record details** for the description and song/episode id list, and set **Max items** to control run size and cost.
4. Click **Start**, then download the dataset as JSON, CSV or Excel, or read it through the API.

**Search by keyword:**

```json
{
  "mode": "search",
  "searchTerms": ["Sample Artist"],
  "maxItems": 50
}
```

**Music only, from a search:**

```json
{
  "mode": "search",
  "searchTerms": ["sample new releases"],
  "kinds": ["song", "album"],
  "maxItems": 100
}
```

**Walk an artist's catalog (URL mode):**

```json
{
  "mode": "url",
  "urls": ["https://play.anghami.com/artist/000000"],
  "maxItems": 100
}
```

Paste a playlist, category tag or album URL the same way; each URL decides its own scope. A link Anghami clearly declines (removed, not available in the country, or sign-in only) is logged and skipped, whatever its kind. One known limitation when you paste several URLs together: if a **song or album** link keeps failing at the connection level after every retry, the run stops reading at that link, and every URL listed after it is not read at all. If earlier URLs already returned records, the run still ends as succeeded with only those records (the log shows "Could not reach the data source" after the last "Source N/M" line); if nothing was collected yet, the run fails with a connection message. Artist, playlist and tag links do not have this limitation: a failing one is recorded and the run moves on to the next URL. To be safe, list song and album links last or run them on their own, and check that every "Source N/M" line appears in the log.

**Recurring monitoring (incremental mode):**

```json
{
  "mode": "url",
  "urls": ["https://play.anghami.com/artist/000000"],
  "incrementalMode": true,
  "emitUnchanged": false
}
```

Run it on a schedule: the first run marks everything NEW, later runs report only the changes (play counts and follower moves included as UPDATED).

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/anghami-catalog-scraper").call(
    run_input={"mode": "search", "searchTerms": ["Sample Artist"]})
for record in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(record["kind"], record.get("title") or record.get("name"))
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/anghami-catalog-scraper').call({
  mode: 'search', searchTerms: ['Sample Artist'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### How Max items and Max pages shape a run

**Max items** is the single cap on a run: the walk stops once that many records have been returned, with the budget split evenly across your search terms and URLs. **Max pages** is a safety bound on how many pages of one source get walked; leave it at `0` and only Max items decides when to stop. A run that stops before every source ran out of results is incomplete for that source, and Incremental mode will not mark anything EXPIRED off an incomplete run. This cap does not, however, apply to EXPIRED rows themselves: once a run has genuinely scanned a tracked search's whole scope, every previously tracked record found missing is returned as EXPIRED regardless of Max items, so a large monitored scope where many records have disappeared can return, and bill, far more rows in that run than Max items would suggest.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted or capped run: paste its run or dataset ID and the actor skips every record it already collected there, so you don't pay twice. This only works against a previous run of this same actor: if the pasted id resolves to a dataset whose rows carry no `recordId` field (an unrelated dataset, or a run from a different actor), Resume finds nothing to skip and quietly re-scrapes, and re-bills, everything from zero. Check the run log line "Resume: skipping N record(s)" after a resumed run; if N is 0 and you expected a skip, the pasted id does not match.
- **Incremental mode** (`incrementalMode`) is for scheduled runs over the same search or URL. Each record is classified `NEW`, `UPDATED` (play or follower moves count as changes, with `changedFields`), `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on), `REAPPEARED` or `EXPIRED` (only after a run that scanned the whole tracked scope, and only with `emitExpired`). `stateKey` names or shares the stored state. With incremental mode off, output is exactly as before.
- **Give incremental runs room: set Max items to `0` or well above one page per source.** Max items is applied to each page before unchanged records are set aside, so when the remaining budget is smaller than a page (about 15 search results), unchanged records at the top of that page can use it up and new records further down the same page are left out of the run. The run then reports nothing changed, and on the next scheduled run the same layout can hide them again. Watch for this with a small Max items, or with several search terms or URLs sharing one (the budget is split between them).
- **Resume and Incremental mode together** only work on the first run of a campaign: if the state key already has tracked records, the run stops with a message instead. On that first run, records skipped through Resume are tracked without their values, so the next scheduled run reports each of them once as `UPDATED` (listing every field as changed) even if nothing moved; from then on they behave normally.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify > Settings > Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where records should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (keyword search) or `url` (paste player URLs). |
| `searchTerms` | array | `["Sherine"]` | Keywords to search. Search mode only; each term is scraped independently. |
| `urls` | array | sample | Player URLs: artist, song, album, playlist or category tag pages. URL mode only; each URL decides its own scope. |
| `kinds` | array | (none) | Keep only rows of these kinds (`song`, `album`, `artist`, `playlist`, `tag`). Empty keeps all kinds. Applies in both modes; a pasted song or album URL is returned in full regardless. |
| `excludePodcasts` | boolean | `false` | Drop rows flagged as podcast episodes, in both modes. A pasted song or album URL is returned in full regardless. |
| `fetchDetails` | boolean | `false` | Fetch each song's or album's own detail view: description, curator and (for albums) the song/episode id list. Charged once per record. |
| `maxItems` | integer | `20` | Stop after this many records across all search terms and URLs. `0` = unlimited. |
| `maxPages` | integer | `0` | Safety bound on pages walked per search term or URL. `0` = no page limit (Max items still applies). |
| `resumeFromRunId` | string | (none) | Continue an interrupted or capped run, skipping records it already collected. |
| `incrementalMode` | boolean | `false` | Recurring monitoring: return only NEW, UPDATED and REAPPEARED records on later runs of the same search. |
| `stateKey` | string | (none) | Name or share an incremental-mode monitoring campaign. Auto-derived from the search terms, URLs and filters when left empty. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) unchanged records, marked UNCHANGED. |
| `emitExpired` | boolean | `false` | Also return (and bill) records no longer found, marked EXPIRED. Only produced after a complete scan of the tracked scope. |
| `mcpConnectors` | array | (none) | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | (none) | Notion connector only: page under which record pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on records written to each connector per run. |
| `proxy` | object | Apify Proxy | Connection settings. Leave the default unless a run reports it could not read any results. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "recordId": "song:0000000000",
  "kind": "song",
  "id": "0000000000",
  "url": "https://play.anghami.com/song/0000000000",
  "coverArt": "https://artwork.anghcdn.co/webp/?id=000000000&size=400",
  "title": "Sample Song Title",
  "artist": "Sample Artist",
  "artistId": "000000",
  "album": "Sample Album Title",
  "albumId": "0000000000",
  "durationSec": 228,
  "year": 2026,
  "releasedate": "2026-01-01",
  "genre": "Pop - Sample",
  "plays": 13011686,
  "likes": 120000,
  "lyrics": true,
  "explicit": false,
  "isPodcast": false,
  "isAudiobook": false,
  "keywords": ["Sample Song Title", "Sample Artist"],
  "scrapedAt": "2026-01-01T00:00:00Z",
  "changeType": "NEW",
  "changedFields": [],
  "firstSeenAt": "2026-01-01T00:00:00Z",
  "lastSeenAt": "2026-01-01T00:00:00Z"
}
```

### Plan Requirement

The default proxy setting works out of the box, and the actor rotates connections automatically when one is refused. For large or frequent runs, the residential proxy group gives more headroom; pick it under Connection.

### FAQ

#### How much does it cost?

You pay per record returned, with the detail view billed only when Fetch record details is on. Incremental mode's EXPIRED rows are billed too, and are not limited by Max items once a run is eligible to detect them (see How Max items and Max pages shape a run). The **Pricing** tab shows current rates; use Max items to cap the cost of an ordinary run.

#### Is it legal to scrape Anghami?

This actor collects only publicly available catalog data from Anghami's anonymous, no-login surface. You are responsible for how you use it: follow Anghami's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Track and album metadata are generally facts, but artwork, descriptions and curated playlist text may be subject to third-party rights.

#### Can I get only new or changed catalog entries on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new, updated and reappeared records, and unchanged ones are not billed by default.

#### Why did my run return fewer records than the page seemed to have?

Anghami's search feed interleaves songs, albums, artists, playlists and tags in one list, so the Kinds to keep and Exclude podcasts filters are applied after the fact to the rows this run reads; a narrow filter combined with a small Max items can leave a page mostly filtered out. Two more cases return fewer than Max items: with several search terms or URLs, a record cut from one source by its share of the budget is not returned when a later source lists it again; and in Incremental mode, unchanged records can use up a small budget (see Resume and recurring updates). Widen the kinds filter, or raise Max items.

#### Why did my run fail instead of returning an empty dataset?

If Anghami refuses every request, the run stops with a clear message so "nothing matched" is never confused with "nothing could be read". The same connection message appears if a pasted song or album link keeps failing at the connection level before any record was collected: the run stops at that link instead of moving on to the remaining URLs (see How to Use; if earlier URLs already returned records, the run instead succeeds with only those). Run it again in a few minutes, or put song and album links last or in their own run.

#### Does Resume always pick up where I left off?

Only when the pasted run or dataset ID is a previous run of this same actor. If it resolves to an unrelated dataset, Resume silently finds nothing to skip and the run re-scrapes, and re-bills, everything; check the "Resume: skipping N record(s)" log line to confirm it actually matched.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 More scrapers you might like

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🧩 <a href="https://apify.com/abotapi/tidal-catalog-scraper"><b>TIDAL Scraper</b></a><br>Scrape TIDAL tracks, albums, artists, playlists and public mixes by search phrase or URL...</td><td>🤖 <a href="https://apify.com/abotapi/suno-music-scraper"><b>Suno Scraper</b></a><br>Collect public Suno music data: songs with lyrics, style tags, model version, play and...</td></tr>
<tr><td>⚽ <a href="https://apify.com/abotapi/hotstar-com-scraper"><b>JioHotstar Scraper</b></a><br>Scrape the JioHotstar catalog by content type or URL. Extract shows, movies, episodes...</td><td>🧩 <a href="https://apify.com/abotapi/bandsintown-concert-event-scraper"><b>Bandsintown Scraper</b></a><br>Scrape Bandsintown concert and festival dates: artist tour calendars, venue profiles...</td></tr>
<tr><td>🧩 <a href="https://apify.com/abotapi/tunein-radio-podcast-scraper"><b>TuneIn Scraper</b></a><br>Scrape TuneIn by keyword, category, genre, location or URL. Extract stations and podcasts...</td><td>🧩 <a href="https://apify.com/abotapi/roblox-scraper"><b>Roblox Scraper</b></a><br>Scrape Roblox experiences, marketplace items, users and communities. Player counts...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support & custom scrapers

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/anghami-catalog-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <abotapi@proton.me> or message [Telegram @abotapi](https://t.me/abotapi).
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

How to start the scrape. 'search' runs catalog searches. 'url' processes the player URLs you paste: each URL's own kind decides the scope, paginated kinds walk forward, and a pasted song or album URL is the one exception and returns that single object in full. The kind filter below applies to the rows this run reads in both modes.

## `searchTerms` (type: `array`):

One or more things to search Anghami's catalog for, for example 'Sherine', 'Amr Diab', a podcast name or an audiobook title. Each term is scraped independently. Search mode only: in URL mode the scope is read out of each pasted URL instead.

## `urls` (type: `array`):

Paste player URLs: artist pages (https://play.anghami.com/artist/000000, the /artist/<id>/<name> form works too), song pages, album pages, playlist pages (https://play.anghami.com/playlist/<name>-<id>), and category tag pages (https://play.anghami.com/tag/<id>, the curated collections behind genre and chart tags). A pasted URL decides its own scope. Add \&page=3 to start a paginated walk further in; songs and albums are single detail objects and ignore paging.

## `kinds` (type: `array`):

Only keep records of these kinds (song, album, artist, playlist, tag), one per line. Leave empty for all kinds. Applied to the rows this run reads, in BOTH search mode and URL mode. A pasted song or album URL is the one exception: the object you pasted is returned in full regardless of this filter.

## `excludePodcasts` (type: `boolean`):

Off by default. On: rows flagged as podcast episodes are dropped from the results, in BOTH search mode and URL mode. Podcast episodes arrive both as song rows carrying the podcast flag and as the podcast object itself (an album-kind row with its own podcast flag); this filter drops both. A pasted song or album URL is the one exception: the object you pasted is returned in full regardless of this filter.

## `fetchDetails` (type: `boolean`):

Fetch each song's or album's own detail view: the store page title and description, the curator of a playlist-style album object, and for an album object the full song/episode id list (a podcast object's episodes). Slower, and charged once per record. One exception: a pasted song or album URL in URL mode is NOT charged this fee, since its object's own page was read because you pasted it. Leave off for a fast crawl: the listing fields are included either way.

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

The single cap on this run: stop after collecting this many records across all search terms and URLs. Set 0 for unlimited.

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

Safety bound on how many result pages to walk per search term or URL. Leave empty (0) for no page limit: the run then stops at Max items, not here. Does not impose a separate cap below Max items.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large crawl without returning or charging for records already collected there. Use this after an interrupted or capped run. For recurring monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or weekly recurring monitoring of the same search, for example a new-release watch. The first run returns everything matching as NEW. Later runs normally return only NEW, UPDATED and REAPPEARED records. Play counts, likes and follower moves ARE reported as UPDATED: on a live streaming catalog they are the signal, not noise. Turn on Emit unchanged or Emit expired only when you also want those rows returned (and billed). State is kept separately per search, URL and filter setup; use State key to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs. Leave empty to let the actor derive a key automatically from the search terms, URLs and filters, so two different searches never mix state.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return records that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have, so leave it off unless you specifically want a full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return records that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search: not when Max items capped it, not when Resume was used, and not when the run was refused. This returns, and bills, extra synthetic rows, so leave it off unless you need expiry tracking.

## `mcpConnectors` (type: `array`):

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a page per record; other connectors get a best-effort write or digest. Each connector receives a condensed summary per record, not the full record; the complete record always stays in the dataset. Leave empty to skip; this never changes the dataset output. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which record pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on records written to each connector per run. Does not affect the dataset.

## `proxy` (type: `object`):

Leave the default for the cheapest run. The catalog gateway serves ordinary shared connections, so the default pool is enough. Change it only if a run reports that it could not read any results.

## Actor input object example

```json
{
  "mode": "search",
  "searchTerms": [
    "Sherine"
  ],
  "urls": [
    "https://play.anghami.com/artist/422"
  ],
  "kinds": [],
  "excludePodcasts": false,
  "fetchDetails": false,
  "maxItems": 20,
  "maxPages": 0,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50,
  "proxy": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (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 = {
    "mode": "search",
    "searchTerms": [
        "Sherine"
    ],
    "urls": [
        "https://play.anghami.com/artist/422"
    ],
    "kinds": [],
    "excludePodcasts": false,
    "fetchDetails": false,
    "maxItems": 20,
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "proxy": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/anghami-catalog-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",
    "searchTerms": ["Sherine"],
    "urls": ["https://play.anghami.com/artist/422"],
    "kinds": [],
    "excludePodcasts": False,
    "fetchDetails": False,
    "maxItems": 20,
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "proxy": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/anghami-catalog-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",
  "searchTerms": [
    "Sherine"
  ],
  "urls": [
    "https://play.anghami.com/artist/422"
  ],
  "kinds": [],
  "excludePodcasts": false,
  "fetchDetails": false,
  "maxItems": 20,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true
  }
}' |
apify call abotapi/anghami-catalog-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/anghami-catalog-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/GuPUiqNLwmLjHDIaA/builds/1UWYjqyBwP1Ys4jsZ/openapi.json
