A community-maintained index of meetup groups and events, searchable at search.notanother.pizza.
The index is built by a distributed scraper. Anyone can contribute by submitting a group to be listed, or by running a worker to help scrape groups faster.
The easiest way to get listed. No coding required — just edit one text file and open a pull request.
1. Create a GitHub account
Go to github.com and sign up for a free account if you don't have one.
2. Fork this repository
Click the Fork button at the top right of this page. This creates your own copy of the project under your GitHub account.
3. Edit community/groups.txt
In your forked repo, navigate to community/groups.txt and click the pencil icon (✏️) to edit it.
Add your group URL on a new line:
https://www.meetup.com/your-group-name/
https://lu.ma/your-calendar-slug
One URL per line. Both Meetup and Luma are supported. The URL should be the public page for your group or calendar.
4. Commit the change
Scroll down to the Commit changes section. Add a short description like Add PyData Bristol and click Commit changes.
5. Open a pull request
Click Contribute → Open pull request at the top of your forked repo. Add a brief description of your group and click Create pull request.
6. Wait for review
Once the PR is merged your group will be scraped and appear in the search index within 24 hours.
Supported platforms:
- Meetup.com — any public group
- Luma — any public calendar
Workers scrape group pages and publish the results to the shared message queue. Running a worker helps the index stay fresh and increases scraping capacity. The more workers running, the faster new groups get indexed.
Total workers from last run: 3
A worker is a small Python process that:
- Picks up a group from a queue
- Scrapes its events and metadata from Meetup or Luma
- Publishes the raw results back to the queue for the sink to store
Workers are stateless: they don't write to any database directly. All they need is a Kafka connection. This means you can run as many as you like, on any machine, without risking data corruption.
- You run a large meetup network and want to make sure your groups are scraped frequently and reliably
- You want to contribute compute to the project without managing infrastructure
- You want to extend the scraper. The worker architecture makes it easy to add support for new platforms (Eventbrite, Facebook Events, etc.) by implementing a new
Platformclass - You want to index your own platform . The scraper isn't tied to the community seed. You can bypass the seed producer entirely and publish your own
GroupSeedmessages to the Kafka topic from your own system. This means any platform that can produce a group URL and basic metadata can feed into the index, whether that's a custom events platform, a university society system, or anything else
To run a worker you need credentials for the shared Aiven Kafka instance. Open an issue or message in the notanother.pizza Discord to request access. You will receive a .env file containing the Kafka connection details.
Note: the sink (database writer) is not available to community runners the data contracts (and rate limiting) are enforced at the sink level.
git clone https://github.com/notanotherpizza/meetup-map
cd meetup-map
python -m venv .venv && source .venv/bin/activate
pip install -e .Copy your .env file into the repo root.
source .env
python -m worker.scraperWorkers are stateless — run as many as you want. They share a Kafka consumer group so work is automatically distributed between them.
# Run three workers in parallel
source .env
python -m worker.scraper &
python -m worker.scraper &
python -m worker.scraper &docker build -t meetupmap .
# Worker only — all that's needed for community runners
docker run --env-file .env meetupmap python -m worker.scraperSeed producer → [groups-to-scrape] → Workers → [groups-raw] → Sink → Postgres → Renderer → GitHub Pages
→ [venues-raw] ↗
→ [events-raw] ↗
- The seed producer publishes one message per group to the
groups-to-scrapeKafka topic - Workers consume those messages, scrape group metadata and events from Meetup or Luma, and publish raw JSON to output topics
- The sink consumer reads the raw topics and upserts into Postgres — this runs centrally and is not available to community runners
- The map renderer queries Postgres and generates the static search and map pages deployed to GitHub Pages
Implement worker/platforms/base.py's Platform interface:
class MyPlatform(Platform):
def can_handle(self, url: str) -> bool:
return "myplatform.com" in url
async def scrape(self, seed, browser, http_client, max_past_events, worker_id):
# fetch data, return ScrapeResult
...Register it in worker/scraper.py and it will be picked up automatically for any seed URL that matches.