# Meetup Scraper: Events by Topic and City (`deriverge/meetup-scraper`) Actor

\[$1.00 / 1K] Meetup events by topic, city and date. Search by keyword and location or by topic, and get the group, venue, date, attendee count and link. Queries that resolve to nothing are not billed, and the search fee is a twentieth of what it used to be.

- **URL**: https://apify.com/deriverge/meetup-scraper.md
- **Developed by:** [deriverge s.r.o.](https://apify.com/deriverge) (community)
- **Categories:** Travel, Social media, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 matching events

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?

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

## Meetup Event Scraper

### At a glance

- A search costs $0.10 plus $1 per 1,000 matching events. Comparable scrapers charge $4.99 per 1,000 and return a fraction of the results.
- You pay only for events that actually match your keyword. The padding Meetup adds to fill pages is dropped and never billed.
- Every run writes a coverage report stating what was reached and what was not.

### What does Meetup Event Scraper do?

**Meetup Event Scraper** returns the whole result set for a Meetup search instead of the first slice of it. Meetup's search API stops serving results at roughly offset 200 and then reports `hasNextPage: false`. Cursor pagination written correctly therefore stops early, believes it is finished, and raises nothing. Keyword "tech" within 50 miles of New York gives you about 211 rows that way. Search September 2026 with this actor and you get 754 events that actually match the keyword.

The actor splits the search recursively. Day first. If a single day still exceeds the cap, six-hour windows. If a window still exceeds it, physical and online separately. The slices are merged and deduplicated on event ID. London came back 849 and Prague, a small market where the cap barely bites, 138.

One thing worth knowing before you buy, because no other listing will tell you: **Meetup's keyword ranks, it never filters.** It sorts nearby events by how well they match and then tops the page up with whatever else is around, so a wide request comes back padded. On that New York search Meetup handed us 8,514 rows and only 754 of them had anything to do with "tech". We drop the other 7,760 and you are not charged for a single one. A keyword Meetup does not recognise returns nothing and costs nothing.

- It queries Meetup's own GraphQL API, so person-bearing fields are simply never asked for. No organizer names and no attendee data.
- No login and no cookies. The actor never opens a browser.
- Events from private groups stay invisible to it, the same way they are invisible to Meetup's public search.

### Why every other Meetup scraper returns a fraction of the data

Meetup's search API stops serving results at roughly offset 200. Past that it does not return an error or a warning: it returns **HTTP 200 with an empty array and `hasNextPage: false`**.

So a scraper doing textbook cursor pagination gets a clean "that's everything" signal after about 200 events, stops, and reports a successful run. There is no way for the caller to tell the difference between "that is all the events" and "that is all we were given."

**Measured live on 2026-08-30, identical filters, keyword `tech`, 50-mile radius:**

| City | Textbook pagination | This scraper | Padding dropped, never charged |
|---|---|---|---|
| New York | 211 rows | **754 matching events** | 7,760 |
| London | 216 rows | **849 matching events** | 6,390 |
| Prague | 209 rows | **138 matching events** | 870 |

Keyword `tech`, September 2026, 50-mile radius. The middle column counts only events that match the keyword. The 211 rows on the left are unfiltered, and roughly a third of any padded Meetup page is on topic, so textbook pagination gets you somewhere near 65 usable events for New York against our 754.

Every one of those runs finished with **zero failed requests and no proxy**.

### How this one works

Two mechanisms, because the cap is not the only problem:

1. **Recursive splitting.** When a slice comes back at the ceiling, it is split by day, then into six-hour windows, then by physical and online, and each part is drained separately. The cap binds *inside a single day* in a dense city, so splitting once is not enough; it has to recurse.

2. **Splitting below the ceiling too.** Meetup's search is ranked, and a broad query silently omits events even when it has not hit the cap. Narrowing the query surfaces them, which is why Prague ends up at 138 matching events rather than the handful a single wide request returns.

3. **Dropping the padding before you pay for it.** Splitting narrowly has a side effect worth being honest about: the narrower the slice, the smaller the pool Meetup ranks, and once the pool is smaller than a page you get all of it whether it matches or not. Chasing completeness therefore blunts your own keyword. So every event is checked against the keyword across its title, description, topics and group name, and anything that matches none of them is discarded before billing.

Every run writes a `COVERAGE_REPORT` to the key-value store naming any slice that stayed capped even after full subdivision, along with `eventsReturned`, `eventsDiscarded` and the topics Meetup files your keyword under. When the report says coverage is complete, that is a measured claim, not a hopeful one.

### Input

```json
{
  "query": "tech",
  "latitude": "40.7128",
  "longitude": "-74.0060",
  "radiusMiles": 50,
  "dateFrom": "2026-09-01",
  "dateTo": "2026-09-07",
  "maxItems": 2000
}
```

| Field | Notes |
|---|---|
| `query` | Required by Meetup itself. A broad keyword returns the widest set. |
| `latitude` / `longitude` / `radiusMiles` | Centre and reach of the search |
| `dateFrom` / `dateTo` | **Set these when you can**: a date range is the single strongest lever against the cap |
| `maxItems` | Hard limit on returned events, so run cost stays predictable |

### Output

One row per event: title, description, start and end time, duration, venue with address and coordinates, the hosting group with its URL and founding date, topics, fee amount and currency, and `foundInSlice` recording which query surfaced it.

### What it does not collect, and why that is structural here

**No organizer names, no attendee lists, no RSVPs, no comments.** These are not filtered out after the fact. They are never requested. Meetup's API is GraphQL, so the query names exactly the fields it wants, and the person-bearing ones (`eventHosts`, `rsvps`, `hostRsvps`, `comments`, `speakerDetails`) simply do not appear in it.

This is a stronger guarantee than scraping the website, where personal data arrives whether you want it or not: Meetup's own search page embeds member records with real names into the HTML. If you need organizer contact details for lead generation, this is not the tool, and that is deliberate: it is what makes the output safe to process under GDPR.

### Speed and cost

No headless browser, no residential proxies, no API key. New York's 754 matching events took 285 API calls across a full month. Requests are cheap, so most of what you pay for is results, and the padding we throw away costs you nothing.

### Typical uses

- **Event aggregators and city guides**: fill a calendar without missing 90% of the city
- **Community and market research**: which groups are active in a topic, how often they meet, how large they are
- **Venue and sponsorship research**: find recurring events by topic, size and location
- **Trend tracking**: schedule the same query monthly and watch a topic grow or fade
- **AI agents**: call it through MCP; the output schema is declared, so the agent knows the shape before running

### FAQ

**Does Meetup have an official public API?**
Not an open one. The modern GraphQL API is aimed at Meetup's own clients and partners; there is no self-serve public search API, which is why scrapers exist for this.

**Why does a search need a keyword?**
Meetup's search filter requires one. To sweep a whole city, run the actor once per broad keyword and merge the results: each row carries `eventId`, so deduplication is trivial.

**Why did I get more events than the first page claimed existed?**
`totalCount` reflects a relevance window, not the corpus. Deduplication is by event ID, so every row is a distinct real event.

**Can I cover several months?**
Yes: give it a date range and it splits day by day. Longer ranges mean more requests; `maxItems` bounds the cost.

**Something broke. How fast will it be fixed?**
Issues are answered within 12 hours. If Meetup changes its GraphQL schema, the run **fails loudly** rather than returning partial data silently.

# Actor input Schema

## `query` (type: `string`):

Keyword to search for, e.g. tech, music, yoga, startup. Meetup requires one: a broad word returns the widest set.

## `latitude` (type: `string`):

Centre of the search area. Defaults to New York City.

## `longitude` (type: `string`):

Centre of the search area. Defaults to New York City.

## `radiusMiles` (type: `integer`):

How far from the centre point to search.

## `dateFrom` (type: `string`):

First day to cover, YYYY-MM-DD. A date range is the strongest way to break through the result cap, so set it whenever you can.

## `dateTo` (type: `string`):

Last day to cover, YYYY-MM-DD.

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

Stop after this many events, so run cost stays predictable.

## Actor input object example

```json
{
  "query": "tech",
  "latitude": "40.7128",
  "longitude": "-74.0060",
  "radiusMiles": 50,
  "maxItems": 1000
}
```

# Actor output Schema

## `events` (type: `string`):

Every event found, deduplicated by event ID.

## `coverage` (type: `string`):

What the run reached and what it could not, including any slice that stayed at Meetup's result ceiling after subdivision.

# 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 = {
    "query": "tech",
    "latitude": "40.7128",
    "longitude": "-74.0060"
};

// Run the Actor and wait for it to finish
const run = await client.actor("deriverge/meetup-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 = {
    "query": "tech",
    "latitude": "40.7128",
    "longitude": "-74.0060",
}

# Run the Actor and wait for it to finish
run = client.actor("deriverge/meetup-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 '{
  "query": "tech",
  "latitude": "40.7128",
  "longitude": "-74.0060"
}' |
apify call deriverge/meetup-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,deriverge/meetup-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/8Sa1cqsHVX2cpthSm/builds/KaZrUaoXh6wbIvITX/openapi.json
