Event Scraper: Collect Events From Eventbrite, Meetup & Luma

An event scraper workflow that pulls typed event records from Eventbrite, Meetup and Luma, plus a fallback ladder for sites that publish no event data.

MMotherlode Lab9 min read
Magpie title card: Event Scraper: Collect Events From Eventbrite, Meetup & Luma

Event Scraper: Collect Events From Eventbrite, Meetup & Luma

Event data is trapped data. An Eventbrite city page, a Meetup group, a Luma calendar and a theater’s own program page all hold the same five facts: name, start time, end time, venue, organizer. No platform hands you a bulk export, so every events calendar or “what’s on this week” digest starts with scraping. This guide builds an event scraper that returns typed records, not text dumps, against the three big platforms plus a fallback for sites that publish nothing structured, and every command in Steps 1 through 4 ran against live pages on 2026-10-16.

Before you start: Magpie itself, a single static binary from github.com/motherlodelab/magpie (AGPL-3.0-or-later or commercial; the CLI is free with no run counter). No accounts are needed anywhere in Steps 1 through 4. Add jq if you want the one-line merge in Step 2.

The data is already in the page source

Most event pages embed their data twice: once for you, once for Google. The second copy is schema.org JSON-LD, a JSON blob in the page source that names the object an Event and spells out startDate, location and organizer as fields. If a page has it, you don’t need CSS selectors and you don’t need an LLM. You read the JSON and you’re done.

Magpie ships a vertical (its term for a zero-LLM extractor that reads exactly this JSON) named event. When the JSON-LD is missing, it refuses with a typed error instead of guessing. That refusal is a feature: it tells you which rung of the fallback ladder you’re on.

Step 1: Probe the page for event data

One command answers the question that decides everything else:

$ magpie vertical --list | grep -A2 '"name": "event"'
      "name": "event",
      "label": "Event",
      "desc": "Event record from schema.org Event JSON-LD (subtypes included). Explicit-only: matches any page.",

Then aim it at any URL:

$ magpie scrape "https://lu.ma/nebius-academy-gtc26" --vertical event --format json

You should see a "vertical": "event" block with the parsed record. If instead you get Error: vertical: event: no Event data at <url>, the page has no Event JSON-LD. Confirm cheaply before moving on:

$ magpie scrape "<url>" --page-format raw | grep -c '"@type": *"Event"'

Zero matches means the fallback ladder later in this guide is your route. Failure case to know about: listing pages often carry a different type, ItemList, and the vertical will refuse those even though the page is full of events. That’s not a bug. The detail pages are where the Event objects live.

Step 2: Eventbrite, from city listing to typed records

Eventbrite detail pages carry Event JSON-LD, and the listing pages are plain server-rendered HTML with the links in plain sight. So the chain is: pull the listing, collect detail URLs, scrape each one. This ran today against Berlin’s events listing:

$ magpie scrape "https://www.eventbrite.com/d/germany--berlin/events/" \
    --page-format raw \
  | grep -oE 'https://www\.eventbrite\.com/e/[a-z0-9-]+' | sort -u > ev_urls.txt

That file fills with real detail URLs like https://www.eventbrite.com/e/berlin-line-dance-party-halloween-edition-tickets-2002177491775. Now the loop. There is deliberately a sleep in it: one fetch per event, a couple of seconds apart, is the polite shape of this job, and Eventbrite’s listing pages are public, so staying polite is the whole access strategy.

$ mkdir -p ev && i=0
$ while read -r u; do
    i=$((i+1))
    magpie scrape "$u" --vertical event --format json > "ev/$i.json"
    sleep 2
  done < ev_urls.txt
$ jq -s 'map(.record) | map({name, startDate, venue: .location.name, organizer})' ev/*.json

The per-file dance is not optional: --format json pretty-prints one object per run, so appending runs into a single file produces garbage that no JSON parser will touch. Per-file plus a jq -s merge is the shape that survives. A record from today’s run (abridged):

{
  "vertical": "event",
  "record": {
    "name": "Beaujolais Nouveau 2026",
    "startDate": "2026-11-19T18:00:00+01:00",
    "endDate": "2026-11-19T22:00:00+01:00",
    "venue": "Institut français Berlin",
    "organizer": "ADEN Immo GmbH",
    "isOnline": false
  }
}

Fields you get that a text dump loses: timezone-aware ISO dates, a structured address block, ticket offers with currency and availability, the organizer as its own field. Failure behavior worth knowing before you scale: the city listing refuses the vertical outright (the ItemList case from Step 1, so the fix is detail URLs), and a dead page prints its error and the loop keeps going, so one bad listing never kills an overnight run.

Step 3: Luma events without a key

Luma detail pages publish Event JSON-LD, and the vertical reads them cleanly. Two details from today worth knowing. First, lu.ma/<slug> redirects to luma.com/<slug>; Magpie follows it and reports both URLs, so don’t be surprised by the final_url field. Second, sold-out shows stay scrappable: the record from a workshop page came back with "availability": "https://schema.org/SoldOut" and price: 0, which is exactly the field you want when building an availability filter.

The city calendars, like lu.ma/berlin, are a different animal: a 693,593-byte Next.js payload with the events embedded as raw JSON in the page source (event names, start_at, geo_address_info are all in there), but no Event JSON-LD, so the vertical refuses. You have two honest options: parse that embedded JSON yourself out of the raw HTML, or hand the page to the schema route below. I parse it. The structure is Luma’s internal API shape, so it changes without notice; pin the fields you use and alert on absence.

Step 4: Meetup, where the listing page is an app

Meetup is the one that will waste your afternoon if you assume it behaves like Eventbrite. A live test on 2026-10-11 pulled Event JSON-LD from a Meetup event detail page, and the vertical parsed it. Today, from my connection, a group page like meetup.com/<group>/ serves a 55,456-byte Next.js app shell with zero event data in the HTML, and forcing a browser render timed out at page load. I’m not going to pretend that makes a reliable pipeline.

The workable route: collect event detail URLs (they look like meetup.com/<group>/events/<id>) wherever they surface, the group’s own site, a partner page, a newsletter archive, and scrape those with --vertical event. There is no keyless API shortcut; my probes to api.meetup.com without OAuth answered Not Found. If a group page does render for you with --render browser, treat it as a bonus, not a dependency. The detail-page JSON-LD is the dependable part, and it’s the same record shape as Eventbrite’s, so nothing downstream changes.

When the event scraper comes up empty

Plenty of event sites publish no Event JSON-LD at all: the theater program, the venue calendar, the university events page. I probed four Berlin venue program pages today; none served it statically. This is the rung where an event scraper stops being free, and you have two options priced accordingly.

Option one is Magpie’s schema route: describe the fields you want as a JSON Schema, and Magpie drives an LLM provider (anthropic, openai, ollama for local models, and others, bring your own key) to fill it against the cleaned page:

type: object
properties:
  events:
    type: array
    items:
      type: object
      properties:
        name: { type: string }
        start: { type: string }
        venue: { type: string }
        url: { type: string }
      required: [name, start]
$ magpie scrape "https://www.schaubuehne.de/de/seiten/spielplan.html" \
    --schema events.yaml --max-cost 0.50

Two guards I verified today by breaking things on purpose. Feed it a malformed schema and Magpie refuses at compile time with a metaschema error before any provider call, so a typo costs zero tokens. Set --max-cost 0.50 and the run won’t exceed half a dollar; the flag is documented as a hard abort ceiling. The honest caveat: my provider key was dead when I ran this end to end, and the failure is a clean HTTP 401 from the provider, page untouched, nothing half-extracted. Treat the YAML above as the shape to start from. Cost is per page, which is exactly why the zero-LLM vertical earns its keep wherever JSON-LD exists.

Option two is the dumb one, and sometimes it wins: magpie scrape <url> --format markdown, read the human-clean output yourself, or let a downstream LLM (a Claude chat, a script against an API you already pay for) make sense of it. For a one-off “what’s playing this month” question, this beats building anything.

Make it stick

Three habits keep an events pipeline alive. First, cron the loop: the Eventbrite chain from Step 2 is a few lines of shell, and a nightly run with sleep 2 between fetches stays well inside polite territory. Second, prefer the vertical over any selector you write yourself: JSON-LD survives redesigns far better than CSS classes, because the site ships it for Google and breaking it costs them traffic. Third, for pages that change and matter, magpie watch monitors a URL and fires a webhook on change, so you find out about the venue’s redesign the day it happens, not the day your calendar is empty.

What this stack will not do: Google’s own events results (Magpie’s docs list Google surfaces as can’t-extract; that job belongs to a SERP API), login-walled attendee lists, and any bot-wall that triggers. On a challenge, Magpie reports the challenge. That’s the design, and I’d rather have the error than silently wrong data.

Where Magpie fits and where it stops

Magpie (free download, AGPL-3.0-or-later or commercial, the CLI has no run counter) is the right fit here because the work is local, keyless for the JSON-LD rungs, and typed end to end. It stops at: hosted or in-browser anything, defeating bot protections, and app-shell listing pages when browser rendering fails. For Google-Events-shaped needs, a SERP API is the honest tool. For the rest of the ladder on this page, the commands above are the whole job.

FAQ

Is it legal to scrape event listings? Public event pages, polite rates, factual fields: that’s the same legal ground as scraping generally. Contract terms and attendee personal data are the real constraints. I keep rates low and stay out of login walls. The long answer lives in our scraping legality guide.

Does Eventbrite have an official API? Yes: a REST API (v3) with OAuth, and the developer portal advertises free keys. If you get one, it beats scraping for volume and filtering. Everything in this guide is the keyless route, so nothing blocks on approval.

Why does the scraper say “no Event data”? The page has no schema.org Event JSON-LD. Grep the raw HTML for "@type":"Event" to confirm. Listing pages often carry ItemList instead; move to detail pages, or drop to the schema route for LLM-based extraction.

Can you scrape Meetup without logging in? Public event detail pages are the reliable target; they carried Event JSON-LD in tests. Group listing pages are a JavaScript app and need a rendered browser, which may or may not get through. Scrape detail URLs, not the group wall.

How do I get the results into a spreadsheet? Pipe the JSON through jq to CSV and open it anywhere. If the spreadsheet is the destination and you want it refreshed in place, the workflow in Google Sheets web scraping picks up where this page stops.

Start smaller than the loop. Scrape one event detail page with --vertical event, look at the record, and decide which fields your calendar actually needs. Then swap in your city’s Eventbrite listing URL and let the chain from Step 2 fill the file. The platform pages behind this guide get their own deep dives at Eventbrite, Meetup and Luma when one site is your whole problem.

Stop renting credits.

One flat price, your machine, your keys.

Download free for Windows, macOS and Linux

Windows · macOS · Linux $49/mo flat, cancel anytime