# Multi Source Event Scraper (`alexdyn.com/multi-source-event-scraper`) Actor

Normalize and dedupe events from Eventbrite, Meetup, and Luma into a single canonical feed. Outputs clean JSON/CSV/iCal with source links, tags, and “last verified” timestamps, perfect for directories, newsletters, and automation.

- **URL**: https://apify.com/alexdyn.com/multi-source-event-scraper.md
- **Developed by:** [Alex Pavlov](https://apify.com/alexdyn.com) (community)
- **Categories:** Automation, Developer tools, Other
- **Stats:** 39 total users, 5 monthly users, 100.0% runs succeeded, 5 bookmarks
- **User rating**: 5.00 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/platform/actors/running/actors-in-store#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

## Multi-Source Event Extractor

An Apify Actor that extracts events from **Eventbrite**, **Meetup**, and **Luma**, normalizes them to a canonical schema, deduplicates across sources, and outputs clean data in multiple formats.

### Features

- **Multi-platform extraction**: Eventbrite, Meetup, and Luma
- **Two operation modes**: Direct URLs or location-based discovery
- **Smart deduplication**: Fingerprint-based matching to identify duplicate events across platforms
- **Multiple export formats**: JSON (Dataset), CSV, and iCal (.ics)
- **Canonical schema**: All events normalized to a consistent output format
- **Geo-coordinates**: Extracts venue latitude/longitude when available
- **Date filtering**: Filter events by start date range

### Input Configuration

#### Basic Example (URLs Mode)

```json
{
  "mode": "urls",
  "sources": ["eventbrite", "meetup", "luma"],
  "startUrls": [
    { "url": "https://www.eventbrite.com/o/your-organizer-page" },
    { "url": "https://www.meetup.com/your-meetup-group/" },
    { "url": "https://lu.ma/your-event" }
  ],
  "maxItems": 100
}
```

#### Discovery Mode Example

```json
{
  "mode": "discover",
  "sources": ["eventbrite", "meetup", "luma"],
  "discover": {
    "locationQuery": "San Francisco",
    "keywords": ["tech", "ai"],
    "dateRange": "this_week",
    "priceRange": "free"
  },
  "maxItems": 50
}
```

#### Input Parameters

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `mode` | string | Yes | - | `"urls"` for direct URLs, `"discover"` for location search |
| `sources` | array | No | All | Platforms to extract from: `eventbrite`, `meetup`, `luma` |
| `startUrls` | array | Required for urls mode | - | List of URLs to process |
| `discover` | object | Required for discover mode | - | Discovery configuration |
| `maxItems` | number | No | 0 (unlimited) | Maximum events to extract |
| `minStartDate` | string | No | - | ISO date string for minimum event start |
| `maxStartDate` | string | No | - | ISO date string for maximum event start |
| `debug` | boolean | No | false | Enable verbose logging |

#### Discover Configuration

| Parameter | Type | Description |
|-----------|------|-------------|
| `locationQuery` | string | City or location to search (e.g., "San Francisco", "New York") |
| `keywords` | array | Search keywords (e.g., \["tech", "networking"]) |
| `dateRange` | string | `today`, `tomorrow`, `this_week`, `this_weekend`, `next_week`, `this_month`, `custom` |
| `dateFrom` | string | Start date for custom range (ISO format) |
| `dateTo` | string | End date for custom range (ISO format) |
| `priceRange` | string | `free`, `paid`, or `all` |
| `sortBy` | string | `date`, `relevance`, or `popularity` |
| `onlineOnly` | boolean | Only include online events |

### Output Schema

Each event is output in the **CanonicalEvent** format:

```typescript
{
  canonicalId: string;       // Stable fingerprint-based ID
  title: string;
  startTime: string;         // ISO 8601
  endTime?: string;          // ISO 8601
  timezone?: string;
  url: string;               // Primary URL
  platform: "eventbrite" | "meetup" | "luma" | "multi";
  venue?: {
    name?: string;
    address?: string;
    city?: string;
    region?: string;
    country?: string;
    lat?: number;
    lng?: number;
  };
  organizer?: {
    name?: string;
    url?: string;
  };
  price?: {
    isFree: boolean;
    min?: number;
    max?: number;
    currency?: string;
    raw?: string;
  };
  images?: string[];
  descriptionSnippet?: string;  // Max 500 chars
  sourceListings: Array<{       // All sources where event was found
    source: string;
    url: string;
    extractedAt: string;
    sourceEventId?: string;
  }>;
  dedupe?: {
    fingerprint: string;
    mergedCount: number;
  };
  meta: {
    extractedAt: string;
  };
}
```

### Output Formats

#### 1. Dataset (JSON)

Events are pushed to the default Apify Dataset. Access via:

- Apify Console: Dataset tab in run details
- API: `https://api.apify.com/v2/datasets/{datasetId}/items`

#### 2. iCal Export

Automatically exported to Key-Value Store as `events.ics`:

- Import directly into Google Calendar, Apple Calendar, Outlook
- Includes location, description, and geo-coordinates
- Download from Key-Value Store tab or API

> **Tip:** For CSV export, use Apify's native Dataset export: add `?format=csv` to the Dataset API URL.

### Deduplication

The Actor automatically identifies and merges duplicate events across platforms using fingerprint-based matching:

- **Title matching**: Normalized (lowercase, no special characters)
- **Time window**: Events within 15 minutes are considered the same
- **Venue matching**: Geographic bucket (~1km) or city-level fallback

When duplicates are found, events are merged with:

- Platform precedence: Eventbrite > Meetup > Luma
- All source URLs preserved in `sourceListings`
- `platform` set to `"multi"` if from multiple sources

### Supported URL Patterns

#### Eventbrite

- Event pages: `eventbrite.com/e/{event-slug}`
- Organizer pages: `eventbrite.com/o/{organizer}`
- Search results: `eventbrite.com/d/{location}/{category}`

#### Meetup

- Event pages: `meetup.com/{group}/events/{event-id}`
- Group pages: `meetup.com/{group}`
- Search: `meetup.com/find/`

#### Luma

- Event pages: `lu.ma/{event-slug}`
- City pages: `lu.ma/sf`, `lu.ma/nyc`
- Category pages: `lu.ma/tech`, `lu.ma/ai`

### Usage Examples

#### Extract from Specific Organizers

```json
{
  "mode": "urls",
  "sources": ["eventbrite"],
  "startUrls": [
    { "url": "https://www.eventbrite.com/o/techcrunch-8868586981" }
  ],
  "maxItems": 20
}
```

#### Discover Tech Events in Multiple Cities

```json
{
  "mode": "discover",
  "sources": ["eventbrite", "meetup", "luma"],
  "discover": {
    "locationQuery": "New York",
    "keywords": ["tech", "startup", "networking"],
    "dateRange": "this_month",
    "priceRange": "free"
  },
  "maxItems": 100
}
```

#### Extract Free Events This Weekend

```json
{
  "mode": "discover",
  "sources": ["eventbrite", "luma"],
  "discover": {
    "locationQuery": "Los Angeles",
    "dateRange": "this_weekend",
    "priceRange": "free"
  }
}
```

### Local Development

```bash
## Install dependencies
npm install

## Build
npm run build

## Run locally
npx apify-cli run --input-file=input.json

## Run tests
npm test
```

### Proxy Configuration

For production use on Apify platform, configure proxy:

```json
{
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

### Limitations

- Rate limiting may apply on source platforms
- Some events may require login to access full details
- Geo-coordinates depend on venue data quality from source
- Discovery mode availability varies by platform and location

### Cost Estimation

Resource usage depends on:

- Number of URLs/events to process
- Whether JavaScript rendering is needed (always enabled)
- Network conditions and retries

Typical runs:

- 50 events from URLs: ~0.05 compute units
- Discovery mode (100 events): ~0.15 compute units

### License

Apache 2.0

# Actor input Schema

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

Choose how to find events: 'urls' for specific URLs you provide, 'discover' for search-based discovery.

## `sources` (type: `array`):

Which platforms to extract events from.

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

URLs to extract events from. Can be event pages, listing pages, organizer pages, or search result pages. Required for 'urls' mode.

## `discoverLocation` (type: `string`):

Select a city to search for events. Popular tech hubs are listed first.

## `discoverLocationCustom` (type: `string`):

Enter a custom location if your city isn't in the dropdown above. Note: Custom locations may have less precise geo-filtering.

## `discoverKeywords` (type: `array`):

Keywords to search for (e.g., 'tech', 'AI', 'startup', 'networking'). Leave empty to get all events.

## `discoverDateRange` (type: `string`):

Time period to search for events.

## `discoverDateFrom` (type: `string`):

Start date for custom date range (only used when Date Range is 'Custom').

## `discoverDateTo` (type: `string`):

End date for custom date range (only used when Date Range is 'Custom').

## `discoverPriceRange` (type: `string`):

Filter events by price.

## `discoverOnlineOnly` (type: `boolean`):

Only include virtual/online events.

## `discoverSortBy` (type: `string`):

How to sort discovered events.

## `discoverSearchRadiusKm` (type: `integer`):

Maximum distance in kilometers from search location. Events further away will be excluded.

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

Maximum number of events to extract. Set to 0 for unlimited.

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

Proxy settings for avoiding blocks. Apify Proxy recommended for production use.

## `maxConcurrency` (type: `integer`):

Maximum concurrent browser pages. Lower values are more polite to servers, higher values are faster.

## `requestDelayMs` (type: `integer`):

Delay between requests in milliseconds. Higher values reduce server load and blocking risk.

## `pageLoadTimeoutSecs` (type: `integer`):

Maximum time to wait for a page to load before timing out.

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

Maximum number of retries for failed requests.

## `sessionPoolSize` (type: `integer`):

Number of browser sessions to maintain in the pool.

## `blockResources` (type: `boolean`):

Block images, fonts, and media to improve performance. Disable if pages don't render correctly.

## `excludeKeywords` (type: `array`):

Skip events whose title contains any of these keywords (case-insensitive).

## `debug` (type: `boolean`):

Enable verbose logging for troubleshooting extraction issues.

## `logLevel` (type: `string`):

Logging verbosity level.

## `includeRawFields` (type: `boolean`):

Include raw extracted data in sourceListings. Useful for debugging but increases output size.

## `captureErrorScreenshots` (type: `boolean`):

Save screenshots when extraction errors occur. Useful for debugging but uses more storage.

## Actor input object example

```json
{
  "mode": "urls",
  "sources": [
    "eventbrite",
    "meetup",
    "luma"
  ],
  "discoverLocation": "",
  "discoverKeywords": [],
  "discoverDateRange": "this_week",
  "discoverPriceRange": "all",
  "discoverOnlineOnly": false,
  "discoverSortBy": "date",
  "discoverSearchRadiusKm": 50,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 3,
  "requestDelayMs": 1000,
  "pageLoadTimeoutSecs": 60,
  "maxRetries": 3,
  "sessionPoolSize": 5,
  "blockResources": true,
  "excludeKeywords": [],
  "debug": false,
  "logLevel": "info",
  "includeRawFields": false,
  "captureErrorScreenshots": false
}
```

# Actor output Schema

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

Table view of extracted events with title, time, source, and URL

## `detailed` (type: `string`):

Complete event data including venue, organizer, and price

## `sources` (type: `string`):

View showing source listings and dedupe information

## `json` (type: `string`):

Raw JSON data for all events

## `csv` (type: `string`):

CSV format for spreadsheet import

# 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 = {
    "sources": [
        "eventbrite",
        "meetup",
        "luma"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("alexdyn.com/multi-source-event-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 = { "sources": [
        "eventbrite",
        "meetup",
        "luma",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("alexdyn.com/multi-source-event-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "sources": [
    "eventbrite",
    "meetup",
    "luma"
  ]
}' |
apify call alexdyn.com/multi-source-event-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=alexdyn.com/multi-source-event-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/EzLLeMBaeC6qmMKbQ/builds/OFhHw3p8bPhiPyoMH/openapi.json
