Inspiration
Finding an apartment in Hamburg is brutal. Listings on WG-Gesucht and Kleinanzeigen get 100+ applicants within minutes of going live — if you're not in the first wave, you don't get a reply. I spent weeks refreshing tabs during my internship search before realizing a bot could do it better, faster, and at 3am.
What it does
A renter fills in their budget, move-in window, preferred districts, and a short profile. The bot then:
- logs into WG-Gesucht with a saved session
- crawls fresh WG-Gesucht and Kleinanzeigen listings every 10 minutes
- applies deterministic hard filters (rent, dates, district, recency)
- sends survivors to OpenAI for fit-scoring and scam detection
- pushes only the keepers to Telegram with pros, cons, and a recommendation score
The result: one low-noise feed instead of manually refreshing five tabs.
How we built it
- Python 3.11+ as the runtime
- Playwright (Chromium) for browser automation — needed because WG-Gesucht's filters are JS-driven dropdowns, not URL params
- OpenAI API for structured listing analysis against a renter profile prompt
- Telegram Bot API for instant push delivery
- Streamlit for a no-code config editor so non-technical users can tune filters
- TOML + dotenv to cleanly split user preferences from secrets
- Cron on a Linux VPS for 10-minute polling
def process(listings, seen):
for listing in listings:
if listing.id in seen: continue
if not run_checks(listing).passed: continue
analysis = analyze(listing, scrape_details(listing.url))
send(format_listing_with_ai(listing, analysis))
Challenges we ran into
- Session expiry: WG-Gesucht silently invalidates Playwright storage state every few days. Solved with a headless
ensure_session()check + auto-relogin using saved credentials. - Crash-safe dedupe: Early versions re-spammed users when a crash happened mid-run. Fixed by persisting seen IDs before the AI/Telegram step.
- District filter false negatives: WG-Gesucht's card UI sometimes shows only a street name, not a district. Documented as a known limitation rather than over-engineering a geocoder.
- Rate limits: OpenAI calls capped per run and parallelised via
ThreadPoolExecutor.
Accomplishments that we're proud of
- This is exactly how I found my Hamburg internship apartment.
- One unified pipeline across two completely different listing sites.
- Drop-in Streamlit UI so someone non-technical can run it.
- Zero false re-notifications since the dedupe rewrite.
What we learned
- Browser automation beats API scraping when sites lock things down.
- Splitting deterministic filters from AI scoring keeps the bot useful even when the AI is off or down.
- Persistence ordering matters more than retry logic.
What's next
- Score-threshold gate (only notify if
recommendation_score ≥ N) - AI-drafted application messages with a Telegram approve/edit button
- Berlin and Munich support
Built With
- beautiful-soup
- chromium
- cron
- dotenv
- openai
- playwright
- python
- streamlit
- telegram-bot-api
- toml
Log in or sign up for Devpost to join the conversation.