Search for a book by title, author, or ISBN and instantly see which nearby UK bookshops have it on the shelf — plus online availability and direct links to reserve or buy. Give it a postcode (or use browser geolocation) and it checks stock across Waterstones, Foyles, and Hive in real time.
Online listings tell you a book exists; they rarely tell you which physical shop near you has a copy right now. This does — by driving each retailer's own click-and-collect flow and reading back the live store list.
React frontend ──API──▶ FastAPI backend
(Vite, Tailwind) ├─▶ Google Books API (title/author → ISBN)
├─▶ Waterstones (Playwright)
├─▶ Foyles (Playwright) ← run concurrently
└─▶ Hive (Playwright)
- Concurrent scraping — all retailers run together via
asyncio.gather, so total latency ≈ the slowest single retailer, not the sum. - Headless-browser scraping — each retailer's stock lives behind JS-rendered click-and-collect widgets, so every scraper drives real Chromium via Playwright (dismiss cookie banner → open C&C → enter postcode → read the store list).
- ISBN-accurate matching — Hive's search can surface related titles, so the scraper matches on the ISBN embedded in each cover-image URL rather than trusting result order.
- TTL cache — results are cached in-memory for 15 minutes per
isbn:postcode:retailerskey to stay light on retailer servers. - UK postcode handling — normalisation + validation, with geolocation → postcode via postcodes.io.
Backend — Python, FastAPI, Playwright (async, headless Chromium), httpx, Pydantic v2. Frontend — React, Vite, Tailwind CSS.
# Backend
cd backend
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
playwright install chromium
uvicorn app.main:app --reload --port 8000
# Frontend (separate terminal)
cd frontend
npm install
npm run dev # http://localhost:5173 — proxies /api/* to :8000Quick smoke test:
curl "http://localhost:8000/api/search?q=thursday+murder+club"
curl "http://localhost:8000/api/stock?isbn=9780241988268&postcode=SW1A+1AA"| Endpoint | Purpose |
|---|---|
GET /api/health |
Server + retailer status |
GET /api/retailers |
List supported retailers |
GET /api/search?q= |
Resolve a query to books/ISBNs (Google Books) |
GET /api/stock?isbn=&postcode=[&retailers=] |
Live stock across retailers |
Scrapers are intentionally polite: cached results, sensible timeouts, and an honest User-Agent. Selectors are pinned to each retailer's current markup (verified 2026) and may need updating if a site changes.