Eventsatosu.com

08.2026

Every Ohio State club posts events on Instagram and nowhere else. This scrapes 1,800 of them nightly and turns the captions into one searchable calendar.

Ohio State's official student-org directory lists 1,930 registered organizations, and it lists exactly one useful thing about each: a name and a category. It does not list events. Clubs announce their events on Instagram — a kickoff meeting, a free-pizza info session, a 6pm practice in Enarson 304 — as a caption under a flyer image, in prose, with the date written as "Wednesday" and the year omitted entirely. A student who wants to know what's happening on campus tonight has no option but to already follow the right forty accounts. The information exists and is public; it is just scattered across 1,800 Instagram profiles in a format nothing can query. The system closes that gap end to end. Phase one crawls OSU's official student-org directory letter by letter (26 pages) and captures all 1,930 orgs with their names, UUIDs, and OSU-assigned categories. Phase two resolves each org to an Instagram handle via site:instagram.com search through the Serper API, producing 1,814 candidate handles. Phase three scrapes each handle's recent feed, filters to a rolling 45-day window, and sends batched captions to Gemini, which returns structured {title, date, time, location} per post or NO_EVENT. Every post image and profile picture is mirrored locally, because Instagram's CDN URLs are signed with an expiry and go dead within weeks. The merged result is written to data/events.json, committed, and pushed — the whole pipeline runs unattended at 10pm Eastern via a launchd agent, gated on a passing next build so a broken scrape can never take the live site down. Architecturally it is deliberately boring where it can afford to be. There is no database: the pipeline's output is JSON checked into the repo, and two Next.js route handlers (/api/events, /api/clubs) read those files off disk and join them at request time. Club metadata lives in club_handles.json and event data in events.json, kept separate because the scraper rewrites the events file every five clubs and would clobber anything merged in out-of-band. A third file, scrape_log.json, is the operational memory — last-scraped timestamp, numeric user id, cached identity verdict, and consecutive-empty counter per handle — and it is what makes the nightly run cheap. The frontend is one 1,538-line client component: it fetches both endpoints, flattens events, and does all grouping, filtering, scoring, and calendar rendering in the browser.

Eventsatosu.com
  • Web Scraping
  • Data Ingestion
  • LLM Pipelines
  • Python
  • Next.js
Kuldeep Debnath