DEV Community

Devil Scrapes
Devil Scrapes

Posted on

Meetup has no public API, and its Apollo cache keys change every page load

Quick answer

Meetup has no public, callable API — every group, event-listing, and event-detail page ships its data as a server-rendered Apollo GraphQL cache buried inside <script id="__NEXT_DATA__">. Reading it isn't a matter of grabbing one JSON key: the cache is a normalized store of {"__ref": "Type:id"} pointers, and the query keys that hold your data change shape on every single page load. The Meetup Group Scraper resolves that cache correctly — organizer identity, venue geo, RSVP counts, past and upcoming events — for $0.002 per result plus a $0.005 run-start charge, $2.005 for 1,000 events.

The cache key you need isn't fixed — you have to pattern-match for it 🔑

Apollo's normalized cache doesn't store events under a clean, static key. It stores it under something like events({"filter":{"status":["UPCOMING"]},"sort":"ASC"}) — the full serialized query variables baked directly into the key string, which differ from page to page and even between a group's upcoming-events connection and its past-events connection on the same page. An exact-key lookup breaks the moment Meetup changes an unrelated query parameter upstream.

The parser doesn't look for an exact key. It scans the cache object for any key starting with events( and containing "sort":"ASC" for upcoming events, or "sort":"DESC" for past events — prefix-plus-substring matching instead of an exact match. That distinction matters because the same Group entity also carries unrelated one-off connections (probe queries with "first":1 and no sort argument at all) that would otherwise get mistaken for the real events list.

A first-page read is quietly incomplete — so we detect that and fetch a second page 🎯

Here's the trap that would ship wrong data silently: the group and search-listing pages both include enough of each Event entity to look complete — title, date, venue name — but neither one carries the venue's lat/lon coordinates, and search-origin events don't resolve an organizer at all from the listing page alone. Only the event-detail page's Venue entity carries coordinates.

If the parser just extracted whatever the first page gave it, every row from a /find/ search would ship with a populated-looking venue_name right next to a null venue_lat/venue_lon — technically not wrong, but silently incomplete in a way nothing about the response would flag. Instead, a needs_detail_hop() check runs per event: it hops to the event-detail page whenever endTime is missing, whenever a present venue lacks coordinates, or whenever neither the event's own hosts nor the group's organizer resolve an identity. Group-URL-origin events with venue and organizer already resolved skip the extra hop entirely — the cost only lands where the data genuinely requires it.

What we handle for you 🛡️

  • We rotate browser fingerprintscurl-cffi impersonation across Chrome, Firefox, and Safari TLS profiles.
  • We rotate residential proxies through Apify Proxy — fresh session and exit IP on every block.
  • We retry with exponential backoff on 408 / 429 / 5xx, up to 5 attempts per page, honoring Retry-After.
  • We resolve the Apollo cache correctly — prefix-plus-substring key matching instead of a brittle exact-key lookup that breaks on unrelated upstream changes.
  • We detect incomplete first-page reads and issue a second, targeted fetch only when venue coordinates or organizer identity are genuinely missing.
  • You pay only for results that land. No data, no charge, beyond the small run-start fee.

Full output schema 📦

Twenty-two fields per event:

Field Type Notes
event_id string Meetup Event.id
event_url string Canonical event URL
event_name string Event title
group_name / group_url string Hosting group
organizer_name string | null Resolved via detail-hop when the listing page doesn't carry it
date_time_start / date_time_end string | null ISO-8601
timezone string | null IANA timezone
is_online bool True for online-only events
venue_name / venue_address / venue_city / venue_state / venue_country string | null Null when the event is online or the venue is hidden
venue_lat / venue_lon float | null Only ever populated via the event-detail page
rsvp_count int Aggregate count — never individual attendee identities
max_tickets int | null Capacity, if set
is_free bool True when no fee is configured
fee_amount / fee_currency float | string | null Ticket fee, when present
matched_via string group_url or location_topic_search
scraped_at string ISO-8601 UTC timestamp

Who this is for

B2B event-lead prospecting — organizer identity and RSVP count as a warm-signal filter before outreach.

Community and ecosystem research — re-run against known group URLs over time to track a niche's activity trend.

Sponsorship and field-marketing sizing — audit a community's event cadence and typical RSVP volume before committing sponsorship budget.

Competitive community analysis — compare event frequency and attendance across groups in the same space.

Frequently asked questions

Does this scrape individual RSVP identities?

No. rsvp_count is an aggregate number only — this Actor never emits individual attendee names or PII.

Why do some rows need an extra request and others don't?

Group-URL-origin events often already carry a resolved organizer and venue coordinates from the listing page. Search-origin events, and any event missing endTime or venue coordinates, trigger one targeted detail-page fetch to fill the gap — the extra cost is scoped to exactly the events that need it.

Why is Meetup's data structured as a GraphQL cache instead of a normal API?

Meetup renders every page server-side with Next.js and embeds the Apollo cache used to hydrate the React app client-side. There's no separately callable public REST or GraphQL endpoint — the cache inside the HTML response is the only data surface.

Do I need a Meetup login?

No. All four source surfaces — group page, group events, event detail, and /find/ search — return complete data unauthenticated.

What does 5,000 events cost?

$10.005 — 5,000 × $0.002, plus the $0.005 run-start charge.

Try it

Live on the Apify Store: Meetup Group Scraper.

Feed it group URLs — or a location and topic — and get back structured event rows with organizer, venue geo, and RSVP data resolved correctly, not silently incomplete. Pay-per-event, no subscription.


Built by Devil Scrapes — we build scrapers for the targets that fight back.

Top comments (0)