Real-time Kamernet rental listing scraper with LLM-powered scoring and notifications anywhere.
Self-hostable. Not affiliated with Kamernet B.V. Educational / personal use.
Finding a place to rent on Kamernet.nl is brutal. Good listings vanish in minutes. Kamernet Radar watches the site for you, scores each new listing against your preferences with a local or free LLM, and pings you the moment something good drops. Choose Discord, Telegram, WhatsApp, ntfy, email, Slack, or any of 100+ other channels.
"I set it up in five minutes and had a notification for a good β¬1450 studio on my phone within an hour. Would recommend."
β every user, eventually, hopefully
- π Polite, robots.txt-compliant scraping. Public HTML pages only, rate-limited, with a transparent User-Agent.
- π€ LLM scoring 0β100 via OpenRouter (free models work). Rubrics live in editable YAML.
- π¬ Notify anywhere. Native Discord rich embeds, Telegram with a password-gated subscriber flow, or Apprise for 100+ channels.
- π― Preset profiles.
student-amsterdam,young-professional-randstad,family-utrecht,generic. Copy and tweak. - π Optional Next.js dashboard. Filter, sort, chart, and drill into listings. Runs locally.
- π³ Docker-first.
docker compose upand you're running. - ποΈ Postgres-backed. Full listing history, price snapshots, scrape audit log.
Option A: Docker (recommended)
git clone https://github.com/jasp-nerd/kamernet-radar
cd kamernet-radar
cp .env.example .env # edit at least APPRISE_URLS or DISCORD_WEBHOOK_URL
docker compose up -d db # start Postgres
docker compose run --rm scraper init-db # one-time schema bootstrap
docker compose up scraper # start scrapingWant the dashboard too?
docker compose --profile dashboard up
# β http://127.0.0.1:3000Option B: Terminal only (no Docker)
Requires Python 3.10+ and access to a Postgres instance.
git clone https://github.com/jasp-nerd/kamernet-radar
cd kamernet-radar
# One-time: install uv (https://docs.astral.sh/uv/getting-started/installation/)
uv sync # creates .venv, installs from uv.lock
cp .env.example .env # edit DATABASE_URL + one notification channel
uv run python -m radar init-db # run schema.sql
uv run python -m radar run # scrape foreverOne-shot dry-run (skips notifications and DB writes, handy for tuning your profile):
uv run python -m radar run --once --dry-run --profile student-amsterdamPrefer plain pip? pip install . works β pyproject.toml declares the same dependencies.
Option C: No notifications, database only
Leave every notifier env var blank. The scraper writes to Postgres silently. Pair it with the dashboard for a private searchable archive.
Environment variables (or .env) control behavior. Full list with comments: .env.example.
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL |
βοΈ | Postgres connection. Without it, no persistence. |
PROFILE |
βοΈ | Profile name (default: generic). |
OPENROUTER_API_KEY |
βοΈ | Enables AI scoring. Free tier works fine. |
DISCORD_WEBHOOK_URL |
βοΈ | Native Discord rich-embed notifications. |
TELEGRAM_BOT_TOKEN + TELEGRAM_PASSWORD |
βοΈ | Telegram bot with /start <password> subscription. |
APPRISE_URLS |
βοΈ | Any of 100+ channels (Slack, ntfy, WhatsApp, email, Pushover). |
CHECK_INTERVAL_MIN/MAX |
βοΈ | Seconds between checks (randomized, default 50-70). |
Configure at least one notification channel, otherwise listings go to the database silently. None are required.
A profile controls two things: what gets scraped (city, radius, price cap) and how the LLM scores listings. Four ship out of the box:
| Profile | Who it's for |
|---|---|
generic |
Neutral universal defaults. Start here. |
student-amsterdam |
Two students/young adults near VU Amsterdam, budget ~β¬2000/mo. |
young-professional-randstad |
Solo professional, β¬1800β2500, Randstad-wide. |
family-utrecht |
Family of 4, Utrecht + suburbs, 2+ bedrooms, long-term only. |
Writing your own takes two minutes of YAML. See docs/PROFILES.md.
Pick any channel. The scraper fans out to all configured notifiers:
- π΅ Discord. Rich embeds with images, price, availability, AI score. Set
DISCORD_WEBHOOK_URL. βοΈ Telegram. Personal bot with password-gated subscriptions. Users send/start <password>. SetTELEGRAM_BOT_TOKEN+TELEGRAM_PASSWORD.- π Apprise. One env var, 100+ channels: Slack, ntfy, email, WhatsApp (via Twilio), Pushover, Matrix, Home Assistant, and more. Set
APPRISE_URLS.
All three can coexist. A high-scoring listing fans out to each configured channel. Channel recipes live in docs/NOTIFICATIONS.md.
The optional Next.js dashboard shows listing history, trends, top-scored picks, and landlord leaderboards. Run it locally with npm run dev or docker compose --profile dashboard up. See dashboard/README.md.
Local use needs nothing beyond the quick-start. To run Radar 24/7 on a tiny VPS (β¬4/mo is plenty), read docs/DEPLOYMENT.md. It covers docker compose on a plain VPS, Coolify, Railway, and fly.io.
- π€ kamernet-mcp β MCP server that puts Kamernet search inside Claude, Cursor, Codex, or any AI agent. Radar watches and notifies in the background; kamernet-mcp lets you search, read, and rank listings interactively from a chat. They pair well: get pinged by Radar, then dig into the listing with your agent. Install with
claude mcp add kamernet -- uvx kamernet-mcp.
Pull requests welcome. Especially valuable:
- π New preset profiles. Your city or situation isn't covered? Copy
profiles/generic.yamland send a PR. - π Notification recipes. Add Apprise recipes for channels we haven't covered.
- π§ Provider plugins. Abstract the scraper to target Pararius, Funda, or HousingAnywhere.
- π¨ Dashboard polish. Map view, websocket live updates, browser extension.
See CONTRIBUTING.md for dev setup and the roadmap.
This project is not affiliated with Kamernet B.V. Use it for personal, educational, non-commercial purposes.
- The scraper respects Kamernet's
robots.txt. It hits the public HTML search pages and avoids the disallowed API endpoints (/SearchRooms/GetRooms,/ajax/, etc.). Pull requests that weaken this get rejected. - The scraper rate-limits requests with jittered intervals (default 50β70s).
- You are responsible for complying with Kamernet's Terms of Service and applicable law (including EU GDPR if you process personal data).
- Use a transparent User-Agent with a link back to this repo (default template in
.env.example).
If you're on the Kamernet operations team and this causes trouble, open an issue and let's talk. One HTML fetch per minute per user puts less pressure on your servers than a motivated human refreshing the site.
MIT. Do whatever you want, no warranty.