# Event Scraper Pro (`webdatalabs/event-scraper-pro`) Actor

Aggregate upcoming events from Meetup, Lu.ma and Eventbrite into one deduplicated dataset: title, date, venue, address, coordinates, category, image and links. Meetup and Lu.ma rows add RSVP counts and organizer details. Every run reports exactly which searches completed.

- **URL**: https://apify.com/webdatalabs/event-scraper-pro.md
- **Developed by:** [WebDataLabs](https://apify.com/webdatalabs) (community)
- **Categories:** Social media, Lead generation, Travel
- **Stats:** 172 total users, 20 monthly users, 100.0% runs succeeded, 6 bookmarks
- **User rating**: 3.96 out of 5 stars

## Pricing

Pay per event

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

## Event Scraper Pro — Meetup, Lu.ma & Eventbrite in one dataset

Search Meetup, Lu.ma, and Eventbrite for upcoming events in the cities and topics you care about, and get one **clean, unified, deduplicated** dataset — with RSVP/guest counts, organizer info, venue + geo, dates, and ticket status.

Every column in the output is a column at least one platform actually publishes — see the [capability matrix](#honest-capability-matrix) for exactly which.

Built for **event discovery, lead generation, community outreach, and market research**.

### What you get

One row per event, normalized across all three platforms:

```json
{
  "platform": "meetup",
  "title": "#37 AI Series: Stanford University — S. Sanborn",
  "startsAt": "2026-07-15T17:00:00.000Z",
  "timezone": "Europe/Berlin",
  "isOnline": false,
  "venueName": "C130, Institut für Chemie, Technische Universität Berlin",
  "address": "Chemiegebäude, Str. des 17. Juni 115, 10623 Berlin",
  "city": "Berlin",
  "country": "DE",
  "latitude": null,
  "longitude": null,
  "rsvpCount": 196,
  "capacity": 250,
  "organizerName": "BLISS AI Speaker Series 2026",
  "organizerUrl": "https://www.meetup.com/bliss-speaker-series",
  "ticketStatus": "free",
  "currency": null,
  "priceMin": null,
  "topics": ["Ai", "Machine Learning"],
  "category": "AI",
  "coverImageUrl": "https://secure.meetupstatic.com/photos/event/...",
  "eventUrl": "https://www.meetup.com/.../events/12345"
}
```

*(This event is free, so `priceMin`/`currency` are `null` and `ticketStatus` is `"free"` — we never write a fake `0`. Meetup publishes a street address but no coordinates, hence `latitude`/`longitude` are `null`. See the matrix below.)*

### Honest capability matrix

We only fill a field when the source actually exposes it. A ❌ below means **`null` by design**, not a scraper bug — so you can trust every non-null value. **There is no field on this Actor that is null for everybody**: anything no platform publishes has been removed from the schema rather than shipped as a permanently empty column.

| Field | Meetup | Lu.ma | Eventbrite |
|---|:---:|:---:|:---:|
| Title, date, URL, cover image | ✅ | ✅ | ✅ |
| Venue name + street address | ✅ | ❌ *(hidden until you register)* | ✅ |
| Latitude / longitude | ❌ *(address only)* | ✅ | ✅ |
| City / country | ✅ | ✅ | ✅ |
| End time | ❌ *(start only)* | ✅ | ✅ |
| **RSVP / guest count** | ✅ | ✅ | ❌ *(not public)* |
| **Organizer name** | ✅ | ✅ | ❌ *(search exposes only an id)* |
| **Organizer page link** | ✅ | ✅ | ✅ |
| Capacity | ✅ *(when capped)* | ❌ *(never set in listing)* | ❌ *(not in search)* |
| Ticket price / currency | ✅ *(when paid)* | ✅ *(when paid)* | ❌ *(not in search)* |
| Ticket status (free / paid / sold out) | ✅ | ✅ | ❌ *(reported as `unknown`)* |
| Description text | ✅ | ❌ *(not in listing)* | ✅ |

**Free events have no price, and we say so.** `priceMin` and `currency` are `null` for free events — never `0`, which would be indistinguishable from a genuinely free ticket. Use **`ticketStatus`** as the free/paid flag: `free`, `on_sale`, `sold_out`, or `unknown`. Likewise `capacity` is `null` when the organizer set no attendee limit, and `rsvpCount` of `0` means zero sign-ups so far — a real number, not a gap.

**Why Eventbrite is thinner:** we re-verified Eventbrite's public search end to end (its embedded search JSON, the page's JSON-LD, and the rendered result cards). It returns no RSVP counts, no organizer names, no ticket prices and no capacity — no scraper can extract what the source doesn't serve. You still get title, venue, geo + coordinates, category, description summary, links, and a working **organizer page link** built from the organizer id the search does return. Add Meetup/Lu.ma to the same run for the audience, organizer-name and pricing signals.

**Eventbrite runs a browser + residential:** Eventbrite's public search renders in a real browser and is reliable only over a residential proxy, so the Actor automatically routes *only* Eventbrite through residential (Meetup and Lu.ma stay on fast, cheap HTTP). All three are on by default; a full run finishes well under 5 minutes. Eventbrite is a bit slower and uses residential bandwidth — **deselect it in Platforms for the fastest, cheapest runs.**

### How it works & reliability

- **Meetup & Lu.ma are HTTP** — both serve their data as JSON/SSR, so the default run needs no browser and is fast and cheap. **Eventbrite** (optional) renders in a headless browser over a residential proxy for reliable access.
- **Deduplication before billing** — exact + fuzzy cross-platform matching removes duplicates *before* any event is written or charged. You never pay for a duplicate.
- **Honest run status** — every run writes an `OUTPUT` record to the Key-Value Store with a per-platform status (`ok` / `empty` / `blocked`). The run only **fails loudly** if *every* selected platform is blocked, or if it ran out of time before finding anything; a platform that reached its source but found no matches is a valid empty result, not a failure.
- **Honest coverage, never a silent partial** — each platform runs one search per **keyword x city**. If a run stops before searching all of them, the run says so in its status message *and* in `OUTPUT`, listing exactly which keywords/cities were never searched (`requestsPlanned`, `requestsCompleted`, `truncationReason`, `notSearched`). A run is never reported as complete when it is not.
- **"Max results per platform" is a hard cap** — the run stops the moment it is reached, so you are never charged past the limit you set. Because each Eventbrite search returns ~20 events, a cap of 50 is met after ~3 searches: **raise the cap if you want broad keyword/city coverage**, lower it to keep runs small.
- **Proxy** — Apify **Datacenter** by default for Meetup/Lu.ma; **Eventbrite always uses residential automatically**. Switch the whole run to Residential only if Meetup/Lu.ma start blocking.

### Input

| Field | Description |
|---|---|
| **Keywords** *(required)* | Topics to match, e.g. `["ai", "startup"]` |
| **Cities** *(required)* | e.g. `["Berlin", "Munich"]` |
| **Country** | Country the cities are in (builds the correct URLs) |
| **Platforms** | Any of Meetup / Lu.ma / Eventbrite |
| **Date From / To** | `YYYY-MM-DD`; defaults to today → +1 month |
| **Max results per platform** | **Hard** cap per platform (default 50). The run stops when it is hit — see the coverage note above |
| Filters | Online-only, min attendees, free/paid |
| Output | `.ics` calendar, webhook (n8n/Zapier/Make), incremental "only new events" |

### Common use cases

- **Lead gen:** find Meetup/Lu.ma organizers running well-attended events in your niche (sort by `rsvpCount`, use `organizerName` + `organizerUrl`). Eventbrite rows carry an `organizerUrl` too, so you can still reach the organizer's page there.
- **Market research:** track how many and what kind of events run in a city/topic over time.
- **Community/newsletter curation:** pull upcoming events, export the `.ics`, or push to n8n via webhook.

### Notes & limits

- **City scope follows each platform's own geo-scoping.** Results include the whole metro area (a "New York" search returns Brooklyn, Queens, etc.). For a US state that shares its name with its main city (e.g. **New York**), Eventbrite scopes to the *state*, so some far-flung events may appear — filter by `latitude`/`longitude` downstream if you need a tight radius.
- **Lu.ma is city-centric** — coverage is strongest in cities with an active Lu.ma scene; a sparse city can legitimately return few or zero Lu.ma events.
- **Eventbrite has a 180-second budget.** It is the only browser-based platform, and the budget is what keeps a full three-platform run comfortably under 5 minutes. Measured throughput is roughly **12-16 keyword x city searches (~150-250 events) per run** — so if you queue, say, 11 keywords x 3 cities = 33 searches, Eventbrite will finish part of them and the run will tell you exactly which ones it skipped (`notSearched` in `OUTPUT`). Searches are interleaved across cities, so a partial run still reaches every city you asked for; **which keywords make the cut is not guaranteed to follow your input order**, so if you need specific keywords covered, run them in a smaller query. For complete Eventbrite coverage keep the combinations under ~12, or split the query across several runs.
- Dates/timezones are normalized to **ISO 8601 UTC** (`startsAt`), with the original IANA `timezone` kept.
- Scraped text is treated as untrusted and never executed; only publicly available data is collected.
- **Compliance:** This Actor is an independent tool and is not affiliated with or endorsed by Meetup, Lu.ma, or Eventbrite; all product names and trademarks belong to their respective owners. You are responsible for using the collected data in line with applicable laws and each platform's terms.

# Actor input Schema

## `keywords` (type: `array`):

Topics to search for (e.g. tech, AI, startup, design). Eventbrite and Meetup run one search per keyword AND per city, so 11 keywords x 3 cities = 33 searches. If a run stops before finishing them all, it names the exact keyword/city pairs it skipped in the run status and the OUTPUT record — keep the list short if you need every keyword covered.

## `cities` (type: `array`):

Cities to search (e.g. Berlin, Munich, London). Every keyword is searched in every city, so cities multiply the work. Searches are interleaved across cities, so a run that stops early still reaches every city rather than exhausting the first one.

## `country` (type: `string`):

Country the cities are in (used to build the correct platform URLs).

## `platforms` (type: `array`):

Which sources to scrape. Meetup & Lu.ma are fast HTTP and include RSVP/guest counts. Eventbrite runs a browser over a residential proxy (auto-enabled), so it is slower and capped at a 180-second budget — with many keywords x cities it will not finish every search, and the run states in its status message and OUTPUT record exactly which combinations were skipped.

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

Earliest event start date (YYYY-MM-DD). Leave empty for today.

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

Latest event start date (YYYY-MM-DD). Leave empty for +1 month.

## `includeOnline` (type: `boolean`):

Include virtual/online events. Uncheck for in-person only.

## `minAttendees` (type: `integer`):

Only keep events with at least this many RSVPs/guests (0 = no filter). Applies to Meetup & Lu.ma only; Eventbrite has no public count and is always kept.

## `includeFree` (type: `boolean`):

Include free/no-cost events.

## `includePaid` (type: `boolean`):

Include paid/ticketed events.

## `maxItemsPerPlatform` (type: `integer`):

Hard cap on events per platform — the run stops the moment it is reached and you are never billed past it. Note this cap usually ends the run before all keyword/city searches happen: each Eventbrite search returns about 20 events, so a cap of 50 is met after roughly 3 searches. Raise the cap for broader keyword/city coverage. Eventbrite also has a 180-second budget that limits it to roughly 100-250 events per run no matter how high you set this; the run reports exactly how many searches completed.

## `onlyNewEvents` (type: `boolean`):

For scheduled/monitor runs: skip events already returned by previous runs of this exact query. Off by default so a normal run always returns the full result set.

## `generateICS` (type: `boolean`):

Save an .ics calendar of the results to the Key-Value Store (importable into Google/Outlook/Apple Calendar).

## `webhookUrl` (type: `string`):

POST the results to this URL on completion (n8n/Zapier/Make). Leave empty to skip.

## `webhookHeaders` (type: `object`):

Optional headers for the webhook (e.g. an Authorization token).

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

Proxy for Meetup & Lu.ma. Automatic (datacenter) is enough for these two. Eventbrite automatically uses a residential proxy for reliable access. Switch this to RESIDENTIAL only if Meetup/Lu.ma start blocking.

## Actor input object example

```json
{
  "keywords": [
    "tech",
    "startup"
  ],
  "cities": [
    "Berlin"
  ],
  "country": "DE",
  "platforms": [
    "meetup",
    "luma",
    "eventbrite"
  ],
  "includeOnline": true,
  "minAttendees": 0,
  "includeFree": true,
  "includePaid": true,
  "maxItemsPerPlatform": 50,
  "onlyNewEvents": false,
  "generateICS": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `upcoming` (type: `string`):

No description

## `popular` (type: `string`):

No description

## `by_platform` (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 = {
    "keywords": [
        "tech",
        "startup"
    ],
    "cities": [
        "Berlin"
    ],
    "platforms": [
        "meetup",
        "luma",
        "eventbrite"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("webdatalabs/event-scraper-pro").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 = {
    "keywords": [
        "tech",
        "startup",
    ],
    "cities": ["Berlin"],
    "platforms": [
        "meetup",
        "luma",
        "eventbrite",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("webdatalabs/event-scraper-pro").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 '{
  "keywords": [
    "tech",
    "startup"
  ],
  "cities": [
    "Berlin"
  ],
  "platforms": [
    "meetup",
    "luma",
    "eventbrite"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call webdatalabs/event-scraper-pro --silent --output-dataset

```

## MCP server setup

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

```

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/hfMJM9eTNKcyqLsbw/builds/aeehnH3KNB80UiCDM/openapi.json
