What it covers
The Georgia scraper returns election results from the Georgia
Secretary of State at results.sos.ga.gov. It covers all
elections available on the site — primaries, runoffs, generals, and
special elections — from 2000 to the present.
Optionally, it can also return a vote-method breakdown (Advance in Person, Election Day, Absentee by Mail, Provisional) for each contest.
Note: The Georgia SOS site is a JavaScript-rendered Angular SPA. All scraping uses a headless Chromium browser via Playwright. County scraping is parallelised across up to
max_workersbrowsers (default 4). Georgia has 159 counties; a full state + county scrape for one year typically takes 20–60 minutes.
Arguments
| Argument | Default | Description |
|---|---|---|
state |
— |
"GA", "georgia", or
"Georgia"
|
year_from |
NULL |
Start year, inclusive; NULL = no lower bound |
year_to |
NULL |
End year, inclusive; NULL = no upper bound |
level |
"all" |
"all" — statewide + county; "state" —
statewide only (much faster); "county" — county-level
only |
max_workers |
4L |
Parallel Chromium browsers for county scraping |
include_vote_methods |
FALSE |
If TRUE, also return vote-method breakdown tables |
Examples
Statewide + county results for a single year
res <- scrape_elections(state = "GA", year_from = 2024, year_to = 2024)
# Statewide candidate totals
res$state %>%
filter(office == "President of the United States") %>%
select(election_name, candidate, party, votes, pct, is_incumbent)
# County-level breakdown
res$county %>%
filter(office == "President of the United States") %>%
select(county, candidate, party, votes, pct) %>%
arrange(county, desc(votes))Statewide totals only (faster — skips county scraping)
state_df <- scrape_elections(
state = "GA",
year_from = 2024,
year_to = 2024,
level = "state"
)
state_df %>%
select(election_name, office, candidate, party, votes, pct, is_incumbent)With vote-method breakdown
res <- scrape_elections(
state = "GA",
year_from = 2024,
year_to = 2024,
include_vote_methods = TRUE
)
# Statewide vote-method totals
res$vote_method_state %>%
filter(office == "United States Senator") %>%
select(candidate, party, votes_advance_in_person, votes_election_day,
votes_absentee, votes_provisional, votes_total)
# County vote-method breakdown
res$vote_method_county %>%
filter(office == "United States Senator", county == "Fulton") %>%
select(candidate, votes_advance_in_person, votes_election_day,
votes_absentee, votes_provisional, votes_total)Faster county scraping with more parallel browsers
Georgia has 159 counties. By default, county pages are scraped with 4
parallel Chromium browsers. Increasing max_workers can cut
runtime significantly on machines with enough CPU/RAM.
res <- scrape_elections(
state = "GA",
year_from = 2024,
year_to = 2024,
level = "all",
max_workers = 8L
)Multi-year scrape
res <- scrape_elections(
state = "GA",
year_from = 2020,
year_to = 2024,
level = "all",
max_workers = 4L
)Output columns
$state (when level = "all" or
"state")
| Column | Description |
|---|---|
election_name |
Human-readable election name
(e.g. "November General Election") |
election_year |
Calendar year of the election |
election_slug |
URL slug (e.g. "2024NovGen") |
election_date |
Date string from the page header |
result_status |
Reporting status (e.g. "OFFICIAL RESULTS") |
office |
Office name |
localities_reporting |
"X/Y" localities reporting string |
candidate |
Candidate name (cleaned — (I) and party suffix
stripped) |
party |
Party abbreviation |
is_incumbent |
TRUE if (I) appears in raw candidate
name |
is_winner |
Always NA — winner marker absent from GA SOS HTML |
votes |
Vote total |
pct |
Percentage of votes |
url |
URL of the election page |
$county (when level = "all" or
"county")
Same columns as $state, plus:
| Column | Description |
|---|---|
county |
County name |
$vote_method_state / $vote_method_county
(when include_vote_methods = TRUE)
Same identifying columns as above, with votes split by method instead
of a single votes + pct:
| Column | Description |
|---|---|
votes_advance_in_person |
Advance in Person ballots |
votes_election_day |
Election Day ballots |
votes_absentee |
Absentee by Mail ballots |
votes_provisional |
Provisional ballots |
votes_total |
Sum of all methods |
Performance notes
-
level = "state"is much faster. County scraping opens up tomax_workersparallel Chromium browsers across Georgia’s 159 counties. A full state + county scrape for one year takes 20–60 minutes. Uselevel = "state"when county breakdowns are not needed. -
include_vote_methodsis significantly slower. Each contest panel requires an extra Playwright click-and-re-render step. Budget an extra 5–15 minutes per election page. - Virtual scroll. The GA SOS site uses Angular virtual scrolling, rendering only panels visible in the viewport. The scraper automatically scrolls to load all panels before parsing — without this, pages with 100+ contests would silently return incomplete data.
- Multiple elections per year. The GA SOS site lists all elections for a year (primaries, runoffs, generals, specials). All are scraped and combined in the output.