I needed a rental, so I built a listing aggregator
I’m moving soon and in Santa Fe most real estate agencies post listings on Instagram. I was following 113 accounts and scrolling through them manually. That got old fast.
So I built MeMudo, a site that aggregates all those listings onto a map with filters, price pills, and contact buttons.

The whole thing is three independent pieces talking through files. No backend, no database.
The collector
A Tampermonkey userscript that runs in the browser. It reads posts from followed accounts over a configurable lookback window (30 days by default), downloads the images, and exports everything as a ZIP with a posts.json and an images/ folder.
It keeps a seen registry in localStorage so re-runs skip already-collected posts. One gotcha: JSZip must be pinned to 3.9.1 because 3.10+ breaks in the Tampermonkey sandbox (setImmediate polyfill issue).
The processor
An async Python CLI takes the ZIP and sends each post to Qwen 3.5-plus (a vision model) through DashScope’s API. The model extracts structured fields: listing type, price, address, neighborhood, rooms, bathrooms, area, amenities, contact.
The optimization that matters: if the post caption is 100+ characters, I skip the image entirely and do a text-only extraction call. Why? DashScope serializes vision requests per API key. You can send 15 at once but they queue server-side. Text-only calls actually run in parallel. Since 95% of real estate posts have detailed captions, this cuts processing time from hours to minutes. 461 posts in about 10 minutes at 15 workers.
The API comes from Alibaba’s Coding Plan ($10/month developer subscription). It’s meant for code assistants (tried it for a few days, can’t touch Claude Code) but the models work fine for structured extraction, and the usage is unlimited.
After extraction, addresses get geocoded through Nominatim (free, 1 req/s, cached locally). Posts that can’t be geocoded end up with coordinates in the middle of the Laguna Setúbal. That’s where the geocoder gives up.
Everything saves incrementally to a .progress.jsonl sidecar file. If the process crashes or I kill it, the next run picks up where it left off.
The viewer
Astro 6 with Svelte 5 components, Tailwind v4, and MapLibre GL JS for the map. The map uses pixel-space clustering (HTML buttons repositioned on move/zoom, not GeoJSON layers) because I wanted full control over pill styling. Each pill shows the price with the address underneath, color-coded left border for rent vs. sale.
Listings open in a slide-over panel with a swipeable image carousel, structured fields in a 2-column grid, and call/SMS/WhatsApp buttons that link directly to the contact number. On mobile the panel is a bottom sheet you can drag down to close.
The whole thing is static. Deployed on Cloudflare Pages with images on R2. Total hosting cost: $0/month.
How it went
It’s not production software. The collector is a userscript. The geocoding puts things in lagoons. The admin auth is a GitHub OAuth gate hardcoded to my account. But it works, and scrolling through 113 accounts now takes 2 seconds instead of an hour.
Try it: memu.do
Discussion on r/devsarg.