A website tracking used EV prices, currently live here. It's part market tracker, part price guide, part shopping tool. This is currently just a small hobby project, but bug reports and feature requests are welcome.
- Multiple Sources: CarMax, Carvana and Autotrader. Easiy extensible to more
- Tracks Individua EV Models: Currently tracks 16 different models, see tracked-models.json.
- Interactive Visualizations:
- Static Deployment: Hosted on GitHub Pages with no backend required
- Automated Daily Data Digestion: GitHub Actions runs scrappers daily at midnight UTC
- Simple Data Storage: JSON files in
/data/{source}/{date}.jsoncommitted to repository - Frontend: React app with Chart.js visualizations
npm install
npm run devVisit http://localhost:5173
Run all scrapers:
node scrapers/run-all.jsRun a specific scraper:
node scrapers/run-all.js --source=carvanaRun mock scraper for testing:
node scrapers/mock-source/scrape.jsFor testing visualizations with multiple days of data:
node scrapers/mock-source/generate-mock-history.js.
├── .github/workflows/
│ ├── deploy.yml # Deploy-only workflow (on push)
│ └── scrape-and-deploy.yml # Daily scraping workflow
├── data/ # Scraped price data (JSON)
│ ├── autotrader/
│ ├── carmax/
│ ├── carvana/
│ ├── plattauto/
│ └── mock-source/
├── scrapers/
│ ├── lib/ # Shared scraper utilities
│ │ ├── base-scraper.js # Abstract base scraper class
│ │ ├── config.js # Shared configuration
│ │ ├── file-writer.js # Data persistence utilities
│ │ ├── http-client.js # HTTP request utilities
│ │ ├── rate-limiter.js # Request rate limiting
│ │ └── status-validator.js # Data validation utilities
│ ├── autotrader/
│ │ ├── config.json
│ │ ├── scrape.js
│ │ ├── scrape.test.js
│ │ └── README.md
│ ├── carmax/
│ │ ├── config.json
│ │ └── scrape.js
│ ├── carvana/
│ │ ├── config.json
│ │ └── scrape.js
│ ├── plattauto/
│ │ ├── config.json
│ │ └── scrape.js
│ ├── mock-source/
│ │ ├── config.json
│ │ ├── scrape.js
│ │ └── generate-mock-history.js
│ ├── run-all.js # Run all scrapers sequentially
│ └── TEMPLATE.md # Template for new scrapers
├── src/
│ ├── components/
│ │ ├── DetailChart.jsx # Per-model price ranges chart
│ │ ├── DetailChart.css
│ │ ├── Footer.jsx # Site footer
│ │ ├── Footer.css
│ │ ├── ListingsTable.jsx # Individual listings table
│ │ ├── ListingsTable.css
│ │ ├── ModelListingsView.jsx # Model-specific listings view
│ │ ├── ModelListingsView.css
│ │ ├── NewListings.jsx # New listings panel
│ │ ├── NewListings.css
│ │ ├── NoTeslaToggle.jsx # Toggle to exclude Tesla
│ │ ├── NoTeslaToggle.css
│ │ ├── OverviewChart.jsx # Main overview with all models
│ │ ├── OverviewChart.css
│ │ ├── VehicleListingTabs.jsx # Tabs for switching between models
│ │ └── VehicleListingTabs.css
│ ├── services/
│ │ └── dataLoader.js # Load and process JSON data
│ ├── utils/
│ │ ├── chartLabels.js # Reusable chart label plugin
│ │ ├── inventoryScale.js # Inventory scale calculations
│ │ ├── modelCategories.js # Model categorization logic
│ │ └── numberFormat.js # Number formatting utilities
│ ├── App.jsx # Main application component
│ ├── main.jsx # Application entry point
│ └── index.css # Global styles
├── config/
│ └── tracked-models.json # EV models to track
├── index.html # HTML template
└── vite.config.js # Vite config with data copy plugin
All scrapers use shared utilities from scrapers/lib for consistency. See here for more information.
- Triggers: Push to main, manual workflow dispatch
- Purpose: Fast deployment of frontend changes
- Steps:
- Checkout repository
- Install dependencies
- Build frontend (includes copying data directory)
- Deploy to GitHub Pages
- Triggers: Daily at midnight UTC, manual workflow dispatch
- Purpose: Collect fresh price data and update site
- Steps:
- Checkout repository
- Install dependencies and Chromium
- Run all scrapers (model-first iteration)
- Commit scraped data to repository
- Build frontend
- Deploy to GitHub Pages
To manually run the scraping workflow:
gh workflow run "Scrape Prices and Deploy"npm run build# Build the app
npm run build
# Create symlink for correct base path
ln -s dist usedevpricetracker
# Serve from project root
python3 -m http.server 8001
# Visit http://localhost:8001/usedevpricetracker/The app is configured for GitHub Pages deployment at /usedevpricetracker/:
// vite.config.js
export default defineConfig({
base: '/usedevpricetracker/'
});MIT