Skip to content

Instantly share code, notes, and snippets.

@dotysan
Last active May 27, 2025 16:26
Show Gist options
  • Save dotysan/b5ad0b8296293590fc969236b50dd910 to your computer and use it in GitHub Desktop.
Save dotysan/b5ad0b8296293590fc969236b50dd910 to your computer and use it in GitHub Desktop.
Convert USPCA certified ammo list to spreadsheets.
#! /usr/bin/env -S uv run --verbose --script --managed-python --python=3.13
"""
USPSA Ammunition List Scraper
This script downloads the USPSA Certified Ammunition webpage, parses ammunition
entries including manufacturer, caliber, velocity, and bullet weight, and saves
them as CSV files. It also separates data into per-caliber files, including
Major-classified and PCC ammunition.
Debugging prints are included to help validate parsed content.
"""
# from stdlib
import re
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "beautifulsoup4==4.13.*",
# "cloudscraper25==2.6.*",
# "pandas==2.2.*",
# ]
# [tool.flake8]
# max-line-length = 127
# ///
# packages `uv run` will auto-install
from bs4 import BeautifulSoup as bs
import cloudscraper25
import pandas as pd
def main() -> None:
"""
Entry point: download the USPSA ammunition page and extract ammo data.
"""
url = 'https://uspsa.org/ammunition'
output_file = 'uspsa_ammo_list.csv'
scrape_uspsa_ammo_list(url, output_file)
def scrape_uspsa_ammo_list(url: str, output_file: str) -> None:
"""
Fetches the USPSA ammunition page, parses ammo cards, and writes CSVs.
Args:
url: The URL to scrape.
output_file: Path to save the combined CSV file.
Debug output will show each parsed row.
"""
scraper = cloudscraper25.create_scraper() # debug=True)
resp = scraper.get(url)
resp.raise_for_status() # Raise an error if the request fails
soup = bs(resp.content, 'html.parser')
ammo_cards = soup.find_all('div', class_='card')
rows: list[list[str]] = []
for card in ammo_cards:
# print(card)
# Each card contains multiple ammo entries (li elements)
for li in card.select('ul.list-group li'):
# print(li)
manufacturer = li.get('data-make', '').strip()
# Extract ammo name and SKU
name_div = li.find('div', style='font-weight:bold;')
if name_div is None:
print(f"WARN: Missing ammo name in li: {li}")
continue
name_text = name_div.get_text(strip=True)
match = re.match(r'^(.*?)\s*\(SKU\s+(.*?)\)$', name_text)
ammo_name, sku = match.groups() if match else (name_text, '')
# Default values
caliber_text = velocity = weight = ''
# Extract data from the row of divs
for div in li.select('div.row div.col-md-3'):
# print(f"div:|{div}|")
text = div.get_text(strip=True)
key, _, val = text.partition('-')
key, val = key.strip(), val.strip()
match key:
case 'Caliber':
caliber_text = val
case 'Velocity':
velocity = val.split()[0]
case 'Bullet Weight':
weight = val.split()[0]
# Normalize caliber for CSV file naming
caliber = normalize_caliber(caliber_text, ammo_name)
print(manufacturer, ammo_name, caliber_text)
rows.append([manufacturer, ammo_name, sku, caliber_text, caliber, velocity, weight])
# Create a DataFrame
header_list = ['Manufacturer', 'Ammo Name', 'SKU', 'Caliber Text', 'Caliber', 'Velocity', 'Weight']
df = pd.DataFrame(rows, columns=header_list)
# Save full list as the default CSV
df.to_csv(output_file, index=False)
print(f"Scraped data saved to {output_file}")
# Create separate CSVs for each normalized caliber
for caliber, group_df in df.groupby('Caliber'):
# Skip empty or unknown calibers
if not caliber:
print(f"WARN: Missing caliber in {group_df}")
continue
out_file = f"ammo_caliber_{caliber}.csv"
group_df.drop(columns=['Caliber']).to_csv(out_file, index=False)
print(f"Saved {len(group_df)} rows to {out_file}")
def normalize_caliber(caliber: str, ammo_name: str) -> str:
"""
Classify the caliber into one of: 9mm, .38, .40, .45, PCC, or their Major variants.
Args:
caliber: Caliber text from the webpage.
ammo_name: Full ammo name for additional clues (like "Major" or "PCC").
Returns:
Normalized classification string.
"""
c = caliber.lower().replace(' ', '').replace('_', '').replace('-', '')
a = ammo_name.lower()
if 'pcc' in a:
return 'PCC'
if 'major' in a or 'major' in c:
if '9mm' in c:
return '9mm_Maj'
elif '38' in c:
return '.38_Maj'
elif '40' in c:
return '.40_Maj'
elif '45' in c:
return '.45_Maj'
else:
return 'unk_Maj'
if c.startswith('9mm') or '9mm' in c:
return '9mm'
elif c.startswith('.38') or '38' in c:
return '.38'
elif c.startswith('.40') or '40' in c:
return '.40'
elif c.startswith('.45') or '45' in c:
return '.45'
else:
return 'unknown'
if __name__ == '__main__':
main()
Manufacturer Ammo Name SKU Caliber Text Velocity Weight
Atlanta Arms Atlanta Arms E38115JHPC .38 Super Minor 1130 115
Atlanta Arms Atlanta Arms E38SC115JHPC .38 SuperComp Minor 1130 115
Manufacturer Ammo Name SKU Caliber Text Velocity Weight
Atlanta Arms Atlanta Arms E38S125JHPC .38 Super Major 1360 125
Atlanta Arms Atlanta Arms E38SC125JHPC .38 SuperComp Major 1368 125
ELEY .38 SuperComp Major A09010 .38 SuperComp 1380 124
Manufacturer Ammo Name SKU Caliber Text Velocity Weight
Armscor Precision International .40 S&W 180gr FMJ Rock Pack Brand 50083 .40 S&W 953 180
Armscor Precision International 40 S&W 180gr FMJ 50316 .40 S&W 953 180
Armscor Precision International .40 S&W 180gr FMJ FAC40-2N .40 S&W 953 180
Atlanta Arms Atlanta Arms E40180JFPMC .40 Minor 722 180
Federal Premium Ammunition SYNTECH ACTION PISTOL AE40SJAP1 40 S&W 850 205
Federal Premium Ammunition Gold Medal Action Pistol GM40AP1 .40 S&W 950 180
Fenix Ammunition .40 S&W 180gr FP FA40180 .40 S&W 950 180
Fenix Ammunition .40 S&W 180gr FMJ Competition FC40180 .40 S&W 950 180
Fenix Ammunition .40 S&W 180gr "2011" FC40180L .40 S&W 950 180
Precision Delta Corp. .40 Reman D165FMR .40 S&W 1040 165
Precision Delta Corp. .40 Long D180FMJLONG .40 S&W 950 180
Precision Delta Corp. .40 Reman D40180FMJ .40 S&W 950 180
Manufacturer Ammo Name SKU Caliber Text Velocity Weight
Atlanta Arms Atlanta Arms E40180JFPLC .40 Long Major 944 180
Manufacturer Ammo Name SKU Caliber Text Velocity Weight
Federal Premium Ammunition SYNTECH ACTION PISTOL AE45SJAP1 45 ACP 795 220
Fenix Ammunition .45 ACP 230gr FMJ FA45230 .45 ACP 850 230
Precision Delta Corp. 45 Reman D230FMR .45 ACP 750 230
Precision Delta Corp. PDP 45 P45230 .45 ACP 750 230
Manufacturer Ammo Name SKU Caliber Text Velocity Weight
Atlanta Arms Atlanta Arms E45230FC .45 ACP Major 743 230
Manufacturer Ammo Name SKU Caliber Text Velocity Weight
2A Ammo, LLC 9mm 124 gr Luger Ammo 9mm124-REM 9mm 1050 124
2A Ammo, LLC 9mm 147 gr Subsonic Ammo 9mm147REM 9mm 890 147
Armscor Precision International FMJ-VP 50445 9mm 1090 124
Armscor Precision International 9mm 124 gr FMJ FAC 9-4 9mm 1090 124
Atlanta Arms Atlanta Arms E9147JHPC 9mm 884 147
Atlanta Arms Atlanta Arms P9115FC 9mm 1130 115
Atlanta Arms Atlanta Arms P9124FC 9mm 1048 124
Atlanta Arms Atlanta Arms P9147FC 9mm 884 147
Callaway Ballistics 124 JHP NEW 9MMJHP124GR-NEW 9MM 1060 124
Callaway Ballistics 124 PHP 9MM NEW 9MMPHP124GR-NEW 9MM 1050 124
Callaway Ballistics 115 9MM NEW 9MMTMJ115GR-NEW 9MM 1200 115
Callaway Ballistics 115 9MM REMAN 9MMTMJ115GR-REMAN 9MM 1200 115
Callaway Ballistics 147 SUB9 NEW SUBSONIC147GR-NEW 9MM 920 147
ELEY Minor 9 Competition Pistol 124 gr 650911090128 9mm 1050 124
ELEY Minor 9 Competition Pistol 147 gr 650911090135 9mm 885 147
ELEY Minor 9 Competition Pistol 115 gr 650911090166 9mm 1130 115
Excaliber Ammunition Competition Gold 124 Minor CG124M 9mm 1060 124
Excaliber Ammunition Competition Gold 147 Minor CG147M 9mm 890 147
Federal Premium Ammunition SYNTECH ACTION PISTOL AE9SJAP1 9MM 890 150
Federal Premium Ammunition Gold Medal Action Pistol GM9AP1 9mm 900 147
Fenix Ammunition 9mm 115gr FMJ FA9115 9mm 1100 115
Fenix Ammunition 9mm 124gr FJ FA9124 9mm 1050 124
Fenix Ammunition 9mm 147gr FMJ FA9147 9mm 890 147
Fenix Ammunition 9mm 147gr NAS3 FMJ FC9147 9mm 890 147
HOP Munitions 9MM 115 GR POLY HOP9115POLY/50 9MM 1150 115
HOP Munitions 9MM 115 GR POLY HOP9115POLY/500 9mm 1150 115
HOP Munitions 9MM 124GR POLY HOP9124POLY/50 9mm 1050 124
HOP Munitions 9MM 124 GR POLY HOP9124POLY/500 9mm 1050 124
HOP Munitions 9MM 147 GR POLY HOP9147POLY/50 9mm 900 147
HOP Munitions 9MM 147 GR POLY HOP9147POLY/500 9mm 900 147
Master Class Munitiions 124 Alien 124Alien 9mm 1100 124
Master Class Munitiions 124 Doberman 124DOB 9mm 1050 124
Master Class Munitiions Pitbull 147PIT 9mm 900 147
Outdoor Dynamics Andersons AND 9mm 1050 125
Outdoor Dynamics Beau Berries BBY 9mm 960 135
Outdoor Dynamics Librarians LBR 9mm 900 147
Precision Delta Corp. 115 FMJ D115F9R 9mm 1135 115
Precision Delta Corp. 124 FMJ D124F9R 9mm 1055 124
Precision Delta Corp. 147 JMJ D147F9R 9mm 890 147
Precision Delta Corp. PDP 9 P9124JHP 9mm 1055 124
Precision One Ammunition 9mm FMJ New 115 gr P19MM115FMJN50 9mm 1150 115
Precision One Ammunition 9mm FMJ Reman 115 gr P19MM115FMJR50 9mm 1175 115
Precision One Ammunition 9mm FMJ New 124 gr P19MM124FMJN50C 9mm 1055 124
Precision One Ammunition 9mm FMJ Reman 124 gr P19MM124FMJR50C 9mm 1060 124
Precision One Ammunition 9mm FMJ New 135 gr P19MM135FMJN50C 9mm 955 135
Precision One Ammunition 9mm FMJ New 147 gr P19MM135FMJR50C 9mm 910 147
Precision One Ammunition 9mm FMJ Reman 135 gr. P19MM135FMJR50C 9mm 970 135
Precision One Ammunition 9mm FMU Reman 147 gr P19MM147FMJSUBR50 9mm 920 147
Scorpion Ammo 9MM 124GR RN COMP C009124RN-50 9MM 1050 124
Scorpion Ammo 9mm 124 gr. RN Comp C009124RN-M200 9MM 1050 124
Scorpion Ammo 9mm 147gr RN Comp C009147RN-50 9mm 920 147
Scorpion Ammo 9mm 147gr RN Comp C009147RN-M200 9mm 920 147
Sig Sauer Inc. 9MM, 147GR, Match Elite Competition JHP E9MMA3-COMP-50 9MM Luger 880 147
SpecTrain, LLC Storm Troopers ST9147ST 9mm 893 147
Staccato Ammunition Staccato Match 1-919-MATCH136-STAC-M-ECOM 9MM 990 136
Stand 1 Armory 115 gr. 9mm 0915109 9mm 1175 115
Stand 1 Armory Chubby 0947209 9mm 880 147
Zeroed Ammunition, LLC 135 ZAC9G135 9mm 970 135
Zeroed Ammunition, LLC 147 ZAC9G147 9mm 900 147
Manufacturer Ammo Name SKU Caliber Text Velocity Weight
Atlanta Arms Atlanta Arms E9147MJRC 9mm Major 1156 147
Manufacturer Ammo Name SKU Caliber Text Velocity Weight
ELEY Minor 9 Competition PCC 115 gr (Note, certified for PCC use only, not certified for handguns) 650911090142 9mm 1130 115
ELEY Minor 9 Competition PCC 124 gr (Note, certified for PCC use only, not certified for handguns) 650911090159 9mm 1050 124
Federal Premium Ammunition SYNTECH PCC AE9SJPC1 9MM 1140 130
Outdoor Dynamics PCC Davinci Style Rifle PCC 9 mm 1050 124
Outdoor Dynamics PCC JP5 Style Rifle PCC 9MM 1140 115
Manufacturer Ammo Name SKU Caliber Text Caliber Velocity Weight
2A Ammo, LLC 9mm 124 gr Luger Ammo 9mm124-REM 9mm 9mm 1050 124
2A Ammo, LLC 9mm 147 gr Subsonic Ammo 9mm147REM 9mm 9mm 890 147
Armscor Precision International .40 S&W 180gr FMJ Rock Pack Brand 50083 .40 S&W .40 953 180
Armscor Precision International 40 S&W 180gr FMJ 50316 .40 S&W .40 953 180
Armscor Precision International FMJ-VP 50445 9mm 9mm 1090 124
Armscor Precision International 9mm 124 gr FMJ FAC 9-4 9mm 9mm 1090 124
Armscor Precision International .40 S&W 180gr FMJ FAC40-2N .40 S&W .40 953 180
Atlanta Arms Atlanta Arms E38115JHPC .38 Super Minor .38 1130 115
Atlanta Arms Atlanta Arms E38S125JHPC .38 Super Major .38_Maj 1360 125
Atlanta Arms Atlanta Arms E38SC115JHPC .38 SuperComp Minor .38 1130 115
Atlanta Arms Atlanta Arms E38SC125JHPC .38 SuperComp Major .38_Maj 1368 125
Atlanta Arms Atlanta Arms E40180JFPLC .40 Long Major .40_Maj 944 180
Atlanta Arms Atlanta Arms E40180JFPMC .40 Minor .40 722 180
Atlanta Arms Atlanta Arms E45230FC .45 ACP Major .45_Maj 743 230
Atlanta Arms Atlanta Arms E9147JHPC 9mm 9mm 884 147
Atlanta Arms Atlanta Arms E9147MJRC 9mm Major 9mm_Maj 1156 147
Atlanta Arms Atlanta Arms P9115FC 9mm 9mm 1130 115
Atlanta Arms Atlanta Arms P9124FC 9mm 9mm 1048 124
Atlanta Arms Atlanta Arms P9147FC 9mm 9mm 884 147
Callaway Ballistics 124 JHP NEW 9MMJHP124GR-NEW 9MM 9mm 1060 124
Callaway Ballistics 124 PHP 9MM NEW 9MMPHP124GR-NEW 9MM 9mm 1050 124
Callaway Ballistics 115 9MM NEW 9MMTMJ115GR-NEW 9MM 9mm 1200 115
Callaway Ballistics 115 9MM REMAN 9MMTMJ115GR-REMAN 9MM 9mm 1200 115
Callaway Ballistics 147 SUB9 NEW SUBSONIC147GR-NEW 9MM 9mm 920 147
ELEY Minor 9 Competition Pistol 124 gr 650911090128 9mm 9mm 1050 124
ELEY Minor 9 Competition Pistol 147 gr 650911090135 9mm 9mm 885 147
ELEY Minor 9 Competition PCC 115 gr (Note, certified for PCC use only, not certified for handguns) 650911090142 9mm PCC 1130 115
ELEY Minor 9 Competition PCC 124 gr (Note, certified for PCC use only, not certified for handguns) 650911090159 9mm PCC 1050 124
ELEY Minor 9 Competition Pistol 115 gr 650911090166 9mm 9mm 1130 115
ELEY .38 SuperComp Major A09010 .38 SuperComp .38_Maj 1380 124
Excaliber Ammunition Competition Gold 124 Minor CG124M 9mm 9mm 1060 124
Excaliber Ammunition Competition Gold 147 Minor CG147M 9mm 9mm 890 147
Federal Premium Ammunition SYNTECH ACTION PISTOL AE40SJAP1 40 S&W .40 850 205
Federal Premium Ammunition SYNTECH ACTION PISTOL AE45SJAP1 45 ACP .45 795 220
Federal Premium Ammunition SYNTECH ACTION PISTOL AE9SJAP1 9MM 9mm 890 150
Federal Premium Ammunition SYNTECH PCC AE9SJPC1 9MM PCC 1140 130
Federal Premium Ammunition Gold Medal Action Pistol GM40AP1 .40 S&W .40 950 180
Federal Premium Ammunition Gold Medal Action Pistol GM9AP1 9mm 9mm 900 147
Fenix Ammunition .40 S&W 180gr FP FA40180 .40 S&W .40 950 180
Fenix Ammunition .45 ACP 230gr FMJ FA45230 .45 ACP .45 850 230
Fenix Ammunition 9mm 115gr FMJ FA9115 9mm 9mm 1100 115
Fenix Ammunition 9mm 124gr FJ FA9124 9mm 9mm 1050 124
Fenix Ammunition 9mm 147gr FMJ FA9147 9mm 9mm 890 147
Fenix Ammunition .40 S&W 180gr FMJ Competition FC40180 .40 S&W .40 950 180
Fenix Ammunition .40 S&W 180gr "2011" FC40180L .40 S&W .40 950 180
Fenix Ammunition 9mm 147gr NAS3 FMJ FC9147 9mm 9mm 890 147
HOP Munitions 9MM 115 GR POLY HOP9115POLY/50 9MM 9mm 1150 115
HOP Munitions 9MM 115 GR POLY HOP9115POLY/500 9mm 9mm 1150 115
HOP Munitions 9MM 124GR POLY HOP9124POLY/50 9mm 9mm 1050 124
HOP Munitions 9MM 124 GR POLY HOP9124POLY/500 9mm 9mm 1050 124
HOP Munitions 9MM 147 GR POLY HOP9147POLY/50 9mm 9mm 900 147
HOP Munitions 9MM 147 GR POLY HOP9147POLY/500 9mm 9mm 900 147
Master Class Munitiions 124 Alien 124Alien 9mm 9mm 1100 124
Master Class Munitiions 124 Doberman 124DOB 9mm 9mm 1050 124
Master Class Munitiions Pitbull 147PIT 9mm 9mm 900 147
Outdoor Dynamics Andersons AND 9mm 9mm 1050 125
Outdoor Dynamics Beau Berries BBY 9mm 9mm 960 135
Outdoor Dynamics Librarians LBR 9mm 9mm 900 147
Outdoor Dynamics PCC Davinci Style Rifle PCC 9 mm PCC 1050 124
Outdoor Dynamics PCC JP5 Style Rifle PCC 9MM PCC 1140 115
Precision Delta Corp. 115 FMJ D115F9R 9mm 9mm 1135 115
Precision Delta Corp. 124 FMJ D124F9R 9mm 9mm 1055 124
Precision Delta Corp. 147 JMJ D147F9R 9mm 9mm 890 147
Precision Delta Corp. .40 Reman D165FMR .40 S&W .40 1040 165
Precision Delta Corp. .40 Long D180FMJLONG .40 S&W .40 950 180
Precision Delta Corp. 45 Reman D230FMR .45 ACP .45 750 230
Precision Delta Corp. .40 Reman D40180FMJ .40 S&W .40 950 180
Precision Delta Corp. PDP 45 P45230 .45 ACP .45 750 230
Precision Delta Corp. PDP 9 P9124JHP 9mm 9mm 1055 124
Precision One Ammunition 9mm FMJ New 115 gr P19MM115FMJN50 9mm 9mm 1150 115
Precision One Ammunition 9mm FMJ Reman 115 gr P19MM115FMJR50 9mm 9mm 1175 115
Precision One Ammunition 9mm FMJ New 124 gr P19MM124FMJN50C 9mm 9mm 1055 124
Precision One Ammunition 9mm FMJ Reman 124 gr P19MM124FMJR50C 9mm 9mm 1060 124
Precision One Ammunition 9mm FMJ New 135 gr P19MM135FMJN50C 9mm 9mm 955 135
Precision One Ammunition 9mm FMJ New 147 gr P19MM135FMJR50C 9mm 9mm 910 147
Precision One Ammunition 9mm FMJ Reman 135 gr. P19MM135FMJR50C 9mm 9mm 970 135
Precision One Ammunition 9mm FMU Reman 147 gr P19MM147FMJSUBR50 9mm 9mm 920 147
Scorpion Ammo 9MM 124GR RN COMP C009124RN-50 9MM 9mm 1050 124
Scorpion Ammo 9mm 124 gr. RN Comp C009124RN-M200 9MM 9mm 1050 124
Scorpion Ammo 9mm 147gr RN Comp C009147RN-50 9mm 9mm 920 147
Scorpion Ammo 9mm 147gr RN Comp C009147RN-M200 9mm 9mm 920 147
Sig Sauer Inc. 9MM, 147GR, Match Elite Competition JHP E9MMA3-COMP-50 9MM Luger 9mm 880 147
SpecTrain, LLC Storm Troopers ST9147ST 9mm 9mm 893 147
Staccato Ammunition Staccato Match 1-919-MATCH136-STAC-M-ECOM 9MM 9mm 990 136
Stand 1 Armory 115 gr. 9mm 0915109 9mm 9mm 1175 115
Stand 1 Armory Chubby 0947209 9mm 9mm 880 147
Zeroed Ammunition, LLC 135 ZAC9G135 9mm 9mm 970 135
Zeroed Ammunition, LLC 147 ZAC9G147 9mm 9mm 900 147
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment