Repository navigation
Complete scraper refactor (CRITICAL WARNING: MIGRATION NECESSARY BEFORE DEV/PROD DEPLOYMENT) - #92
Open
AnikDey-exe wants to merge 9 commits into
Open
AnikDey-exe wants to merge 9 commits into
AnikDey-exe wants to merge 9 commits into
Conversation
AnikDey-exe
requested review from
WyattC-ctrl,
cejiogu and
claiireyu
and
a balanced review from Copilot
November 12, 2026 03:39
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the schedule scraper's HTML page-scraping with Sidearm's calendar JSON
API, and gives every game a stable
event_idso it can be matched by identityinstead of by guessing from its fields.
The old scraper fetched one
/schedulepage per sport and parsed it withBeautifulSoup, then identified a stored game by comparing sport, gender, date,
city, state and location. Those fields are not unique — two games on the same
day at the same venue are indistinguishable — and they are not stable, because
the page writes a state five different ways and puts an end date in the time
column for multi-day events. A missed match creates a duplicate rather than
updating the row.
The calendar feed gives each event a stable numeric
id. Matching on it makesduplicates structurally impossible for any game that has one.
What changed
games_scraper.py—parse_calendar_datareads the feed a week at atime;
calendar_event_to_game_datamaps event fields to DB fields.process_game_datanow looks a game up byevent_idfirst and only fallsback to field matching when there is no id. Box-score and recap fetching is
folded into it, since the feed carries no play-by-play.
event_idadded to the model, the repository, the service, and as asparse unique index. Sparse so existing rows without one are not rejected.
normalize_statereduces states to a canonical two-letter form. The feedwrites "N.Y.", "NY", "Conn." and "Ohio" interchangeably, and state is part of
the fallback lookup key.
update_gamecatchesDuplicateKeyErrorand warns instead of raising, soone colliding row cannot abort a whole scrape cycle.
(
parse_game_links,parse_schedule_location,parse_schedule_date_and_time,extract_season_years,infer_game_year,absolute_url),is_game_active,and 30 now-unreferenced CSS-selector constants.
scripts/, all dry-run by default:backfill_event_id.py,dedupe_games.py,dry_run_scraper.py,snapshot_games.py.game_details_scrape.pyis unchanged and still required — it is what producesbox_scoreandscore_breakdown.Deploying this (CRITICAL WARNING: PLEASE READ THIS)
app.pycallsscrape_schedules()at import time, and gunicorn imports withno_scrape=False, so the new scraper runs as soon as the container starts.scripts/backfill_event_id.pymust run against the target database beforethis code deploys to it. Without ids, every event falls through to field
matching, and the rows that miss on time or state formatting are created as
duplicates instead of updated.
Per environment, in this order:
mongodumpthegamecollection.python3 scripts/dedupe_games.pythen--apply.python3 scripts/backfill_event_id.pythen--apply.python3 scripts/dry_run_scraper.py— confirm updates dominate creates.Creates in the hundreds means the backfill did not take; stop.
masterauto-deploys to dev; prod comes fromrelease.Running on dev first and watching a full cycle is worth it before prod.
Verification
scripts/dry_run_scraper.py: 122 updates, 4 creates across the fetch window.scripts/snapshot_games.py <file>before a full run and--diffafter:0 added, 0 changed, 0 removed, confirming the dead-code removal changed no
behaviour.
event_id, 0 duplicates.Wins
Stable identity, 7× fewer requests, duplicates structurally impossible for 86% of rows and 100% of new ones, no CSS-selector fragility, 184 lines of dead code gone.
Known limitations
event_id. The backfill only assigns one where exactlyone stored row matches; anything ambiguous is reported and skipped rather than
guessed at. 100 of those have a time column holding a date or placeholder
("Sep 27 (Sun)", "All Day") that can never match the feed's real time, and
most of the rest are multi-day meets the old scraper stored once and the feed
lists per day. They continue to match on fields.
since nothing rewrites rows outside it. 90% canonical within 14 days, 42%
beyond. Rows heal as they come into range.
hockey, lacrosse, baseball, softball. The rest log "Sport parser not found",
which is pre-existing and expected.