# Discord Server & Community Scraper (All-in-One) \[$2.0💰] (`fanndev/discord-server-scraper`) Actor

Nine Discord scrapers in one - no login, no bot token, no self-botting. Search Discord's public server directory, pull a community's social links, resolve ANY invite to live member and online counts, see who is in voice now, track server growth, and read the weekly games chart.

- **URL**: https://apify.com/fanndev/discord-server-scraper.md
- **Developed by:** [Faisal Ahdan naufal](https://apify.com/fanndev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 results

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

## Discord Server & Community Scraper

Nine Discord extractors in one actor. No login, no bot token, no self-botting —
everything here is data Discord serves to an anonymous visitor.

Find servers across Discord's ~53,000-server public directory, pull a
community's full profile including its outbound social links, resolve **any**
invite link to live member and online counts, see who is online and in voice
right now, snapshot servers on a schedule to chart their growth, and read
Discord's weekly trending-games chart with week-on-week movement.

***

### What this actor does and does not do

**It does not read messages.** Message history, ticket transcripts, member
rosters and DMs all require a bot token that has been invited to the server, or
a user token — and a user token is self-botting, which Discord bans accounts
for. This actor asks for neither, so there is nothing to leak and no account to
lose.

**What it reads instead** is the public layer, which turns out to be the
commercially useful part: which communities exist, how big they are, how alive
they are, who runs them and where else they publish.

| You want | Use |
|---|---|
| Find Discord communities in a niche | `discoverySearch` |
| Everything in a whole category | `discoveryCategory` |
| A community's social links and long description | `serverProfile` |
| Live member/online counts for a link you already have | `inviteLookup` |
| Who is online and in voice right now | `serverWidget` |
| Track the same servers week after week | `serverSnapshot` |
| Which games are surging on Discord | `trendingGames` |
| One game in depth | `gameProfile` |
| Public metadata for a bot or app | `botProfile` |

***

### The number that matters: `engagementRatePct`

Every server-shaped record carries `engagementRatePct` — online members as a
percentage of total members. It is on every row because it is the one figure
that separates a real community from a vanity number:

- **150,000 members, 400 online (0.3%)** — a shell. Bought, botted, or dead.
- **9,000 members, 2,000 online (22%)** — worth an outreach email.

Discord's own directory sorts by size, so the biggest servers surface first and
the engaged ones do not. Sorting your dataset by `engagementRatePct` instead is
usually the whole job.

***

### Modes

#### `discoverySearch` — find servers by keyword

Searches Discord's public Server Discovery directory. Matches server names,
descriptions and the keywords owners tag their communities with.

```json
{
  "mode": "discoverySearch",
  "searchKeywords": ["crypto", "indie game dev", "spanish learning"],
  "maxPagesPerTarget": 10,
  "maxItems": 500
}
```

Discord serves **12 servers per request** and ignores any page size you ask
for, so `maxPagesPerTarget: 10` is up to 120 servers per keyword.

This is also how you reach the ~45 categories that have no browsable page —
Cryptocurrency, Finance, Anime & Manga, Memes, Esports and the rest exist only
as tags on server records.

#### `discoveryCategory` — browse a whole category

Discord publishes a browsable page for exactly **five** categories:
`gaming`, `music`, `entertainment`, `science & tech`, `education`.
Anything else is a 404 on Discord's side; the actor tells you so up front
rather than collecting error rows.

```json
{ "mode": "discoveryCategory", "categories": ["science & tech"], "maxPagesPerTarget": 20 }
```

#### `serverProfile` — the lead-generation mode

A discoverable server's full public page. Adds four things the search index
does not carry:

- `about` — the long-form description the owner wrote
- `socialLinks` — **the community's own GitHub, X, Patreon and YouTube URLs**
- `website`
- `reasonsToJoin` and the real `created_at`

```json
{ "mode": "serverProfile", "serverTargets": ["https://discord.gg/python"] }
```

#### `inviteLookup` — the universal resolver

The only mode that reaches servers which never joined Server Discovery, which
is the overwhelming majority of Discord. Give it any invite — a vanity
(`discord.gg/python`) or a random code (`discord.gg/B9qbPSzX`) — and get live
counts, boosts and boost tier, verification level, the server tag, the landing
channel and who created the invite.

```json
{ "mode": "inviteLookup", "inviteCodes": ["https://discord.gg/python", "B9qbPSzX"] }
```

`expiresAt: null` means the invite never expires. That is not a missing value.

#### `serverWidget` — live activity

Up to 100 people currently online, their status, and which voice channel they
are in — plus the voice channel list and a freshly minted invite.

Only works when the owner has switched the widget on under
**Server Settings → Widget**. Most have not; the actor reports
`widget_disabled` rather than failing.

**Discord anonymises these members itself.** The `id` field in its response is
a position in that response ("0", "1", "2"…), never a user ID, so the actor
publishes it as `positionInResponse`. These rows cannot be joined across runs,
and that is Discord's design, not a limitation of the scraper.

#### `serverSnapshot` — growth tracking

One comparable row per server per run. Point it at a list of servers, run it on
an Apify schedule, and the dataset accumulates membership, online count and
boost history with no diffing on your side.

```json
{
  "mode": "serverSnapshot",
  "serverTargets": [
    "https://discord.gg/python",
    "https://discord.gg/midjourney",
    "267624335836053506"
  ]
}
```

It combines all three open surfaces and records which of them answered in
`sources`. Invite and Discovery counts are kept side by side
(`memberCount` vs `discoveryMemberCount`) because Discord computes them on
different schedules — in testing the same server reported 431,648 members via
its invite and 431,638 via its Discovery page, seconds apart.

#### `trendingGames` — the weekly chart

Rank, week-on-week player change, 7-day streaming-activity delta, how many
weeks a title has been trending, and each game's official Discord server with
its own counts.

```json
{ "mode": "trendingGames", "weeksOfHistory": 12, "gameCategory": "survival" }
```

History walks backwards by following the previous-week link inside each chart.
That matters: Discord answers a date it has no chart for with the **current**
chart rather than a 404, so stepping dates by seven would quietly refill your
dataset with duplicates. The actor checks every response against the week it
asked for.

#### `gameProfile` and `botProfile`

`gameProfile` takes the `gameUrl` that `trendingGames` emits and returns
genres, platforms, publisher, developer, release date, Discord's own 30-day
popularity rank, and the game's official links (site, X, YouTube, Reddit,
Twitch).

`botProfile` takes any application ID — or any URL containing one — and
returns the app's public metadata: tags, verification and monetization status,
whether the bot is public, its install scopes and permissions, and its terms
and privacy URLs.

***

### Input

Every mode accepts whatever form you happen to have. For servers, all of these
resolve to the same place:

```
https://discord.gg/python
https://discord.com/invite/B9qbPSzX
https://discord.com/servers/python-267624335836053506
https://discord.com/servers/267624335836053506
267624335836053506
python
```

There is also a generic `targets` array that gets routed into whichever list
the selected mode needs, so this actor can be driven from another actor or a
scheduler with one field regardless of mode.

One asymmetry worth knowing: **a bare guild ID only resolves if the server is
in Discovery or publishes a widget.** An invite code always resolves. Discord
offers no ID-to-invite lookup, so when in doubt supply the invite.

### Speed, cost and proxies

| Setting | Default | Why |
|---|---|---|
| `requestDelaySecs` | 2 | Directory pages allow ~6 requests back to back per IP, then HTTP 429 with no `Retry-After`. 2s was measured clean over 16 consecutive requests. |
| `apiRequestDelaySecs` | 0.4 | Discord's JSON API is far more permissive — a 25-request burst drew no throttling at all. |
| `maxItems` | 200 | Your main cost control. |

**A proxy is optional here, which is unusual.** Discord served every surface
this actor uses to a plain datacentre address during testing. A proxy buys
rate-limit headroom, not access, so a long Discovery walk finishes sooner
behind rotating addresses — and `DATACENTER` is enough. You do not need
residential.

### Output

Every record shares one envelope, so a single dataset can hold servers,
invites, widgets, games, bots and diagnostics and still be joined on `id`
(the guild ID for everything server-shaped):

```json
{
  "item_type": "server_snapshot",
  "id": "267624335836053506",
  "data": {
    "name": "Python",
    "memberCount": 431648,
    "onlineCount": 34152,
    "engagementRatePct": 7.91,
    "boostCount": 28,
    "socialLinks": ["https://github.com/python-discord/", "https://twitter.com/PythonDiscord"],
    "createdAt": "2017-01-08T12:03:33+00:00",
    "sources": ["invite", "discovery", "widget"]
  },
  "metadata": { "scrapedAt": "2026-09-21T11:20:03+00:00", "mode": "serverSnapshot", "sourceUrl": "..." }
}
```

Icon, banner and splash fields arrive as ready-to-use CDN URLs rather than the
raw hashes Discord's API returns, animated assets included. `createdAt` is on
every record — published outright on profile pages, and decoded from the guild
ID's own timestamp bits everywhere else.

**The run never finishes with an empty dataset.** Anything that fails produces
an `error` row naming the input and the reason (`not_found`,
`not_in_discovery`, `widget_disabled`, `week_not_published`, …), so a
downstream join can tell "nothing matched" apart from "the run crashed".

### Limitations, stated plainly

- **No messages, members or transcripts.** See the top of this README.
- **Member counts are approximate.** Discord labels them
  `approximate_member_count`; for very large servers they are rounded.
- **Discovery only lists servers that opted in** — around 53,000 of them. Most
  Discord servers are not in it. `inviteLookup` is the way to reach the rest.
- **Widget data is opt-in and anonymised**, and capped at 100 members
  (`memberListTruncated` tells you when you hit the cap).
- **`nbHits` drifts.** Discord's index is live; the reported total changed by
  several hundred between consecutive requests during testing, so the actor
  never uses it as a loop bound and neither should you.

# Actor input Schema

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

Which extraction to run. No Discord login, bot token or self-bot is used or accepted - everything here is served to an anonymous client, which means public server data rather than message history. Start with 'discoverySearch' to find servers by keyword, 'inviteLookup' to get live member counts for any invite you already have, or 'serverSnapshot' to track the same servers over time.

## `searchKeywords` (type: `array`):

Keywords for discoverySearch. Each one is matched against server names, descriptions and the keywords owners tag their server with. This is the way to reach the ~45 categories that have no browsable page - Cryptocurrency, Finance, Anime & Manga, Memes, Esports and the rest - which exist only as tags on server records.

## `categories` (type: `array`):

Categories for discoveryCategory. Discord publishes a browsable page for exactly five: gaming, music, entertainment, science & tech, education. Anything else is a 404 on Discord's side - use searchKeywords for those.

## `serverTargets` (type: `array`):

Servers for serverProfile, serverWidget and serverSnapshot. Paste whatever you have: an invite link (https://discord.gg/python), a Discovery URL (https://discord.com/servers/python-267624335836053506), a bare guild ID (267624335836053506) or a bare invite code (python). The actor works out which is which. NOTE that a bare guild ID can only be resolved if the server is in Discovery or publishes a widget - an invite code always resolves.

## `inviteCodes` (type: `array`):

Invites for inviteLookup. Accepts discord.gg/CODE, discord.com/invite/CODE or the bare code. Vanity codes and random codes both work. This is the only mode that reaches servers which never joined Server Discovery, which is the overwhelming majority of Discord.

## `gameTargets` (type: `array`):

Games for gameProfile. Needs the full slug-and-ID form Discord uses, e.g. valheim-1124358970618953818, or the whole URL. The trendingGames mode emits exactly this as its gameUrl field, so the two chain together.

## `applicationIds` (type: `array`):

Discord application IDs for botProfile. A bare numeric ID, or any URL containing one (an OAuth2 authorize link works). Returns the app's public metadata: name, description, tags, verification and monetization status, install scopes and permissions, and its policy URLs.

## `targets` (type: `array`):

Optional catch-all. Whatever you put here is routed into whichever list the chosen mode needs, so this actor can be driven from another actor or a scheduler with one field no matter which mode is selected. Ignored when the mode's own list is filled in.

## `weeksOfHistory` (type: `integer`):

For trendingGames: how many weekly charts to collect, walking backwards. 1 is the current week. The walk follows the previous-week link inside each chart rather than stepping dates by seven, because Discord answers a date it has no chart for with the CURRENT chart instead of a 404.

## `startWeekDate` (type: `string`):

For trendingGames: the week to start from, as YYYY-MM-DD, matching a weekDate Discord publishes (e.g. 2026-09-10). Leave empty for the current week. If Discord has no chart for the date you give, the actor reports that rather than silently returning the current week.

## `gameCategory` (type: `string`):

Optional genre filter for trendingGames, as the slug Discord uses on the chart: survival, fantasy, sandbox, open-world, indie, adventure, rpg, crafting and so on. Leave empty for the overall chart.

## `includeWidgetMembers` (type: `boolean`):

For serverWidget: keep the per-member rows (up to 100 people currently online, their status and voice channel). Turn this off for just the counts. Discord anonymises these itself - the id field is a position in the response, never a real user ID - so they cannot be joined across runs.

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

Hard ceiling on dataset records for the whole run. 0 means no limit. This is the main control over cost.

## `maxPagesPerTarget` (type: `integer`):

How many result pages to walk per keyword or category in the two Discovery modes. Discord serves 12 servers per page and ignores any page size you ask for, so this is your item count divided by 12: 10 pages is up to 120 servers per keyword.

## `requestDelaySecs` (type: `number`):

Pacing for the server-rendered pages (/servers and /trending-games). Discord allows about six of these back to back per IP address and then answers HTTP 429 with no Retry-After header, so this is the setting that decides whether a long Discovery walk finishes. 2 seconds was measured clean over 16 consecutive requests. Lower it only behind a rotating proxy.

## `apiRequestDelaySecs` (type: `number`):

Pacing for Discord's JSON API (invites, widgets, applications). These are far more permissive than the directory pages - a 25-request burst drew no throttling at all - so this floor is deliberately low.

## `maxRetries` (type: `integer`):

How many times to retry a single request before giving up on it and writing a diagnostic row. Retries cover transport errors and rate limits; a 404 or a disabled widget is a final answer and is never retried.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout. Discovery listing pages are around 340 KB, so allow more than you would for a JSON endpoint on a slow proxy.

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

Optional. Discord served every surface this actor uses to a plain datacentre address during testing, so a proxy is not needed to get data - unlike most scrapers. What it buys is rate-limit headroom: the directory allows only about six requests back to back per address, so rotating addresses make a long Discovery walk finish sooner. DATACENTER is enough; residential is not required.

## Actor input object example

```json
{
  "mode": "discoverySearch",
  "searchKeywords": [
    "crypto"
  ],
  "categories": [
    "science & tech"
  ],
  "serverTargets": [
    "https://discord.gg/python"
  ],
  "inviteCodes": [
    "https://discord.gg/python"
  ],
  "gameTargets": [
    "https://discord.com/game/valheim-1124358970618953818"
  ],
  "applicationIds": [
    "936929561302675456"
  ],
  "weeksOfHistory": 1,
  "includeWidgetMembers": true,
  "maxItems": 200,
  "maxPagesPerTarget": 5,
  "requestDelaySecs": 2,
  "apiRequestDelaySecs": 0.4,
  "maxRetries": 4,
  "requestTimeoutSecs": 45,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Every server, invite, widget, game, bot and diagnostic record from this run.

# 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 = {
    "searchKeywords": [
        "crypto"
    ],
    "categories": [
        "science & tech"
    ],
    "serverTargets": [
        "https://discord.gg/python"
    ],
    "inviteCodes": [
        "https://discord.gg/python"
    ],
    "gameTargets": [
        "https://discord.com/game/valheim-1124358970618953818"
    ],
    "applicationIds": [
        "936929561302675456"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fanndev/discord-server-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 = {
    "searchKeywords": ["crypto"],
    "categories": ["science & tech"],
    "serverTargets": ["https://discord.gg/python"],
    "inviteCodes": ["https://discord.gg/python"],
    "gameTargets": ["https://discord.com/game/valheim-1124358970618953818"],
    "applicationIds": ["936929561302675456"],
}

# Run the Actor and wait for it to finish
run = client.actor("fanndev/discord-server-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 '{
  "searchKeywords": [
    "crypto"
  ],
  "categories": [
    "science & tech"
  ],
  "serverTargets": [
    "https://discord.gg/python"
  ],
  "inviteCodes": [
    "https://discord.gg/python"
  ],
  "gameTargets": [
    "https://discord.com/game/valheim-1124358970618953818"
  ],
  "applicationIds": [
    "936929561302675456"
  ]
}' |
apify call fanndev/discord-server-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fanndev/discord-server-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/BcFB2qNeGAtHfuFTX/builds/8ZxefAwRh4hM7a0vH/openapi.json
