Skip to content

Repository files navigation

Hobby Rocket Motor Finder

test License: MIT

A web aggregator for U.S. high-power rocketry (HPR) motor availability across multiple vendors.

Live at motor.fusionspace.co.

The U.S. hobby motor shortage makes finding a specific impulse / diameter / propellant motor difficult — flyers often check each vendor manually. This project scrapes a curated set of vendors on a schedule, normalizes their listings against a canonical motor catalog (ThrustCurve.org), and presents a single searchable view of stock status by motor — and when a motor is sold out everywhere, points you to comparable in-stock substitutes you could fly instead.

Features

  • One searchable view of live stock and pricing for AeroTech, Cesaroni, and Loki across U.S. vendors, each listing matched to a canonical ThrustCurve motor. The full catalog ships to the browser, so search, filtering, and sorting are instant — no round-trip per click.
  • Whole-catalog coverage — every AeroTech/Cesaroni/Loki D+ motor is browsable, including ones no tracked vendor stocks: search a real motor and you always land on an honest page ("not sold by any tracked vendor — here's the closest in-stock swap") instead of an empty result.
  • Filter and sort by impulse class, diameter, total-impulse range, certification level (L1–L3), manufacturer, vendor, propellant, reload case, and in-stock-only; sort by class, impulse, thrust, diameter, or cheapest in-stock price. Every filter lives in the URL, so any view is shareable.
  • Per-motor detail pages (/motor/<mfr>/<designation>) — full specs, every vendor's price and stock side by side, an availability history (how often the motor's been buyable since the scrape cadence became reliable, with a per-vendor in-stock/out timeline), similar in-stock motors, and a link to the ThrustCurve thrust curve; each carries Product/Offer structured data and a generated social card.
  • In-stock substitutes — when a motor is sold out at every vendor, surfaces comparable in-stock motors you could fly instead: same diameter and impulse class, total impulse within ±15% and average thrust within ±35%, ranked by closest fit.
  • Compare side by side — line up the full specs of up to four motors (class, diameter, total impulse, average thrust, burn time, specific impulse, propellant, case/type) and overlay their thrust curves in one view, so you can pick between candidates — e.g. two reloads that fit the same rocket. The selection lives in the URL, so a comparison is shareable.
  • My Rockets — save a rocket by its motor-mount diameter (the only required field), optionally pinning a cert level, impulse class, reload case, and/or total-impulse band; each shows how many in-stock motors fit it, and one tap opens a "fly it" loadout — the in-stock motors that fit (cheapest first), one tap to add them all to a Plan order, and the closest buyable swaps when nothing that fits is in stock.
  • Plan your order — star the motors you want, then get the cheapest way to buy them all across vendors: set a quantity per motor and an estimated shipping/HAZMAT cost per order, and it minimizes motor cost + shipping × number of shipments (and is pack-aware — where a motor is sold only in a multipack, it buys whole packs and prices per motor). When a motor on your list is sold out everywhere, it surfaces in-stock swaps you can add in one tap to keep the order buyable. The plan is shareable as a link (re-priced live for whoever opens it) and exportable as plain text.
  • Restock email alerts — get notified when a specific motor, or anything that fits a saved rocket, comes back in stock — or appears in stock for the first time (watch a "phantom" motor no vendor stocks and hear the moment one lists it) (setup).
  • History-powered buying signals — a catalog badge flags motors that are in stock now but rarely are (grab it) vs. often out (the scarcity verdict only fires once a motor has been tracked for several days, and never on discontinued stock); each in-stock listing's price gets a marker vs. its own tracked history (↓ lowest tracked / ↓ price dropped / ↑ above its low). Cadence-sensitive stats are clipped to the reliable-scrape epoch; price min/max isn't, so it uses full history with a noise guard.
  • Restock & last-in-stock history plus best-price-across-vendors (compared per motor, so multipacks — some vendors sell small motors in 2/3/12-packs — are priced by the unit with the pack total noted), derived from successive hourly snapshots.
  • Scrape-health monitoring — catches silent breakages (carry-forward, sustained staleness, below-baseline count / in-stock / match-rate anomalies, a block served as a successful HTTP 200, and a registered vendor contributing zero listings) and opens a single auto-closing tracking issue (details).
  • Free public JSON API — the whole dataset (every motor + per-vendor stock and pricing) as static, CORS-open, no-key, no-rate-limit JSON, refreshed hourly (docs · on the site).
  • Plus a ★ watchlist, dark mode, and an on-page explainer of exactly how every figure is derived. Interactive behavior is covered by a headless-browser (Playwright) end-to-end suite in CI.

API

A free, read-only JSON API of everything on the site — served as static files on a CDN, so there's no key, no rate limit, and no cost, and it's Access-Control-Allow-Origin: * (call it from a browser). Refreshed on the hourly deploy.

Base URL: https://motor.fusionspace.co/api/v1/

Endpoint What it is
meta.json schema version, generated_at, counts, manufacturer list
motors.json every matched motor we have a listing for
in-stock.json same shape, only motors in stock somewhere
vendors.json the vendors we track + per-vendor counts
motors/{mfr}/{designation}.json a single motor (slugs mirror the /motor URL)
openapi.json OpenAPI 3.1 spec
# Cheapest in-stock H motors right now
curl -s https://motor.fusionspace.co/api/v1/in-stock.json \
  | jq '.motors[] | select(.impulse_class=="H")
        | {designation, from: .cheapest_in_stock.unit_price_cents, vendor: .cheapest_in_stock.vendor}'

Full field reference, schema, and terms: docs/api.md.

Disclaimer

A personal hobby project. Not affiliated with any vendor or manufacturer listed. Stock and price data are best-effort, often stale, and not authoritative — always confirm on the vendor's own site before purchasing. If you operate a listed vendor and would like the scraper changed or removed, please open an issue and we'll comply.

Released under the MIT License — fork it, modify it, deploy your own copy, no attribution required.

What's covered

Today: AeroTech across ten vendors (including manufacturer-direct from AeroTech itself); Cesaroni (CTI) on Chris' Rocket Supplies, Wildman Rocketry, Performance Hobbies, Moto-Joe Rocketry, and Animal Motor Works; and Loki Research direct from the manufacturer and via Performance Hobbies.

Vendor State Platform Motors
Chris' Rocket Supplies GA Custom (Schema.org JSON-LD) AeroTech, Cesaroni
BuyRocketMotors TX Shopify AeroTech
Wildman Rocketry IL Shopify AeroTech, Cesaroni
Animal Motor Works AZ VirtueMart AeroTech, Cesaroni
Sirius Rocketry WI Zen Cart AeroTech
Loki Research MO Custom (ASP store) Loki
Performance Hobbies VA Custom (ASP.NET store) AeroTech, Cesaroni, Loki
AeroTech (direct) UT Shopify AeroTech (manufacturer-direct)
Moto-Joe Rocketry — OpenCart AeroTech, Cesaroni
Balsa Machining Service NV Custom (single-page ASP) AeroTech
New Century Rocketry SC Shopify AeroTech
eRockets OH BigCommerce AeroTech (low/mid-power)

AeroTech currently backorders nearly everything rather than holding stock, and their store doesn't expose real inventory — so AeroTech-direct listings are shown as special-order with a fulfillment lead-time (e.g. "special order · ~16–20 weeks"), parsed live from AeroTech's own published lead-time banner, rather than as "in stock."

eRockets' host blocks the GitHub Actions data-center IPs outright (403 on every request; it works fine from residential IPs). Its listings are live anyway: the block trips the scraper's egress fail-over, and the free Cloudflare Worker relay fetches the pages from a clean IP. Nothing else engages that path today — it stays dormant unless a vendor blocks us.

The architecture isn't motor-specific — see Adding a vendor and Extending beyond motors if you want to grow it.

Running locally

You need Python 3.12+ and Node 20+.

# Backend (scraper + catalog + CLI)
cd backend
python3 -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'

# Frontend (Next.js UI)
cd ../frontend
npm install
npm run dev          # http://localhost:3000 — uses snapshot.example.json out of the box

To run a real scrape against live vendors:

cd backend
hpr catalog refresh         # download AeroTech, Cesaroni, and Loki motors from ThrustCurve
hpr scrape run all          # ~5-10 min across all 12 vendors
hpr snapshot export         # writes data/snapshot.json

After that, npm run dev will pick up snapshot.json automatically (preferred over the example seed).

Architecture

┌───────────────────┐    cron     ┌──────────────────┐
│  vendor websites  │ ──────────▶ │ backend scrapers │
└───────────────────┘             └────────┬─────────┘
                                           │ Listings
                                           ▼
                                  ┌──────────────────┐
                                  │ SQLite (hpr.db)  │
                                  └────────┬─────────┘
                                           │ snapshot export
                                           ▼
                                  ┌──────────────────┐
                                  │  snapshot.json   │
                                  └────────┬─────────┘
                                           │ read at build/request
                                           ▼
                                  ┌──────────────────┐
                                  │ Next.js frontend │
                                  └──────────────────┘

Key pieces:

  • backend/hpr_finder/normalize.py — turn vendor product titles into canonical AeroTech designations (e.g. D13-10W ↔ D13W).
  • backend/hpr_finder/db.py — find_motor_id runs a fallback chain of designation transforms to match listings against the ThrustCurve catalog. Currently 99%+ match rate.
  • backend/hpr_finder/scrapers/ — one file per vendor; each implements the Scraper protocol and returns a list of Listing. Discovery strategies vary (Shopify vendor=AEROTECH filter, VirtueMart category sweep, Zen Cart manufacturer-page pagination).
  • backend/hpr_finder/http.py — PoliteAsyncClient enforces per-host concurrency cap + minimum-interval pacing + Retry-After honoring.
  • frontend/lib/snapshot.ts — the only contract between backend and frontend. If data/snapshot.json exists it wins; otherwise the loader falls back to the tracked data/snapshot.example.json. A predev/prebuild script (frontend/scripts/copy-snapshot.mjs) copies both files into frontend/data/ because Next 16 + Turbopack refuses to bundle files from outside the project root.

Adding a vendor

  1. Add a scraper at backend/hpr_finder/scrapers/<slug>.py implementing Scraper.scrape(). Look at csrocketry.py (Schema.org JSON-LD) or sirius.py (Zen Cart manufacturer-page paginator) as templates.
  2. Register it in backend/hpr_finder/scrapers/init.py.
  3. Verify the vendor's robots.txt permits crawling and set conservative max_concurrent_per_host / min_start_interval_s on the scraper class.
  4. Run hpr scrape run <slug> and check the match rate; tune normalize.py if their title format trips the regex.

Extending beyond motors

The data model is generic: a Listing is {vendor, designation, sku, status, price, url}. To support kits, electronics, recovery, etc., add a product_type to Listing / Motor, generalize the catalog table, and broaden extract_designation. The scraping infrastructure (polite client, scheduling, snapshot contract, normalize fallback chain) doesn't change.

Deploying

Free hosting works since the architecture is essentially a static site fed by a periodic snapshot.

  • Frontend: Cloudflare Pages (static export) — deploy on git push via GitHub Actions.
  • Scrapes: GitHub Actions cron, e.g. every 6h: run hpr scrape run all && hpr snapshot export, upload snapshot.json as a build artifact, trigger a frontend deploy.

A test workflow lives at .github/workflows/test.yml. A deploy workflow is left as an exercise — pick the host you prefer.

For OpenGraph / Twitter share cards, the site origin defaults to the production domain (https://motor.fusionspace.co). If you deploy your own copy, set NEXT_PUBLIC_SITE_URL to your deployed origin so card image URLs resolve to your domain instead.

Scraping ethics

This project scrapes vendors without prior permission. The bar we hold ourselves to:

  • Identify ourselves. The User-Agent includes the project URL and a contact channel (GitHub Issues).
  • Honor robots.txt. Each vendor's robots.txt is reviewed before the vendor is added; those that disallow generic crawlers are excluded (e.g. Apogee Components, Miller Motor Works — see the vendor report). This is a manual check at onboarding, not a runtime fetch.
  • Rate limit. Per-host concurrency cap (typically 2-4) and minimum interval between request starts (0.25-1s) — see each scraper's class attributes.
  • Honor Retry-After. On 429/503 we sleep the full duration before continuing.
  • No evasion. No rotating User-Agents, no residential proxies, no captcha solvers. If a vendor blocks us, we stop.
  • Take down on request. Open an issue and we comply, no questions asked.

Layout

backend/                Python scraper, normalizer, SQLite store, CLI
frontend/               Next.js UI (App Router, TypeScript, Tailwind)
data/                   Runtime data
  snapshot.example.json   tracked, frozen reference for `npm run dev`
  snapshot.json           gitignored, generated by `hpr snapshot export`
  thrustcurve_aerotech.json  tracked, refreshed via `hpr catalog refresh`
docs/                   Research notes and the original vendor directory
.github/workflows/      CI (pytest, vitest, next build, Playwright e2e) + hourly scrape

Contributing

Issues and PRs are welcome — especially vendor parser fixes and new vendors. See CONTRIBUTING.md for setup, the checks to run before a PR (they mirror CI), how to add a vendor, and how the golden-fixture scraper tests work. Optional email-alert config is documented in frontend/.env.example and docs/email-alerts.md.

License

MIT.

About

Find U.S. high-power rocketry motors in stock across 11 vendors — AeroTech, Cesaroni & Loki. Scrapes dealers hourly, matches against ThrustCurve, and surfaces best price, restock alerts, and in-stock substitutes when a motor's sold out. Python scraper + Next.js UI. MIT.

Topics

Resources

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Contributors

Languages