A self-hosted Google Maps business scraper with a full dashboard for managing cold calling leads. Built for web design agencies and sales teams.
- Google Maps scraping — search by business type and location, captures name, phone, address, website, rating, and a screenshot of their site
- Multi-tenant — personal workspaces and shared team workspaces; switch between them instantly
- Lead pipeline — track leads through New → Called → Interested → Not Interested → Sold
- Activity log — per-lead history of every status change and note, with timestamps and user attribution
- Scrape history — full log of every scrape job run across your workspaces
- Analytics dashboard — leads over time, pipeline breakdown, top categories, team activity
- Multi-user auth — bcrypt-hashed passwords, session-based auth, superadmin panel for managing users and teams
- CSV export — filter-aware export respecting your active workspace, status, and search filters
- Docker-first — runs as a single container with persistent SQLite and screenshot storage
- Backend — Node.js, Express, Playwright (Chromium), better-sqlite3
- Frontend — Vanilla JS, no framework
- Auth — express-session + bcryptjs
- Storage — SQLite (leads), local filesystem (screenshots)
- Docker Desktop (Windows/Mac) or Docker Engine (Linux)
# 1. Clone the repo
git clone https://github.com/PocketYogurt/leadharvest.git
cd leadharvest
# 2. Create your environment file
cp .env.example .env
# Edit .env with your credentials
# 3. Create the data directory
mkdir -p data/screenshots
# 4. Build and start
docker compose build
docker compose up -dThen open http://localhost:3737
Copy .env.example to .env and set:
| Variable | Description |
|---|---|
LH_USERNAME |
Admin login username |
LH_PASSWORD |
Admin login password |
LH_SESSION_SECRET |
Long random string for session signing |
PORT |
Port to run on (default 3737) |
Generate a secure session secret:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"# Start
docker compose up -d
# Stop
docker compose down
# View logs
docker compose logs -f
# Rebuild after code changes
docker compose build --no-cache && docker compose up -dleadharvest/
├── src/
│ ├── server.js # Express API + auth + routes
│ ├── scraper.js # Playwright Google Maps scraper
│ └── db.js # SQLite database layer
├── public/
│ ├── index.html # Main dashboard
│ ├── login.html # Login page
│ ├── admin.html # User & team management (superadmin only)
│ ├── history.html # Scrape history
│ └── analytics.html # Analytics dashboard
├── data/ # Created at runtime — not in git
│ ├── businesses.db # SQLite database
│ └── screenshots/ # Website screenshots
├── .env.example # Environment variable template
├── Dockerfile
└── docker-compose.yml
leads.yourdomain.com {
reverse_proxy localhost:3737
header Cache-Control no-store
}
- Scraping is done via headless Chromium inside the container
- Google occasionally changes their Maps layout — check logs if scrapes return 0 results
- Screenshots are stored in
data/screenshots/and served by Express - The
data/directory is mounted as a Docker volume so it persists across rebuilds