Rental Listing Normalizer avatar

Rental Listing Normalizer

Pricing

Pay per usage

Go to Apify Store
Rental Listing Normalizer

Rental Listing Normalizer

Turns public rental listing pages (Craigslist and Zillow search results) into clean, comparable facts like price, beds, baths, size, location and amenities, with source links and no contact details.

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Show Before

Show Before

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Categories

Share

Rental Listing Normalizer + Scam Signals

Turn public rental listing pages into clean, comparable facts, plus rental-scam red flags and perceptual photo hashes for duplicate detection. Built at Cal Hacks 13.0 for Showbefore, a housing tool covering the cities defined in src/markets.js.

The output retains up to five remote photo URLs for listing cards. It never contains image bytes, listing descriptions, street addresses, names, phone numbers or emails. You get facts you can compare, plus a link back to the original.

What it does

Input URLOutput
Craigslist search page (…craigslist.org/search/apa, /roo, /sub)One item per result: monthly price, beds, baths, approx. location, kind (apartment / room / sublet), price basis
Craigslist real-estate search (…/search/rea)Explicit intent: "sale" and askingPrice; never a monthly rent
Craigslist posting pageOne detailed item: the facts above, plus laundry / parking / pets, red flags found in the ad text, and dHash of each photo
Zillow homedetails pageCalls the Store Actor maxcopell/zillow-detail-scraper (Actor-to-Actor), then normalizes its row the same way
Any page with schema.org JSON-LD / OpenGraphA best-effort item from the structured data
Facebook / InstagramRefused (login_walled_site_not_supported). Login-walled pages are out of scope by design

Normalization you would otherwise write yourself

  • Price basis. Student complexes often advertise rent per bedroom ("4 bd $549"). Rows like that get priceBasis: "per_bed" so they don't drag down whole-unit comps. Room shares get room; weekly or nightly prices get unknown.
  • Kinds. Craigslist apa, roo and sub map to apartment, room and sublet. Zillow home types map to house or apartment.
  • Market. Market ids come from the shared src/markets.js registry (eleven cities). ZIP, then city/state, then nearest bounded center for coordinate-only rows; unmatched rows remain other.
  • Location privacy. Coordinates are rounded to 3 decimals (about 100 m). Zillow homedetails URLs are rewritten to the address-free …/homedetails/<zpid>_zpid/ form.
  • Address key. addressKey is a SHA-256 of the normalized street, unit and ZIP, so you can join the same unit across sites without storing the address.
  • Photos. photoUrls keeps up to five HTTPS URLs from known listing image hosts. Search rows with no images fetch their posting page by default (capturePhotos: false disables this). Failed photo pages retain the original search facts with no photos; sale intent/prices are preserved. Craigslist sizes prefer 600×450; Zillow supports both snake_case and camelCase responsive-photo fields. These page requests use the existing Actor compute/proxy budget.

Scam signals

  • redFlags: patterns from FTC rental-scam guidance, matched in the ad text. The text itself is not output; each match comes with a short excerpt.
    • payment_wire_or_giftcard: wire transfer, gift cards, crypto, or a cashier's check
    • owner_abroad: the owner says they are away or out of the country
    • deposit_before_viewing: money before a viewing, or keys sent by mail
    • off_platform_contact: pushes you to text, email or a messaging app
    • urgency_pressure: pressure to act fast
  • photoHashes: a 64-bit dHash per photo. Each photo is downloaded into memory, hashed, and discarded. Compare hashes by Hamming distance (≤ 6 is a near-certain copy) to catch photos copied from another listing. Photos are hashed 4 at a time under one 40 s budget per listing; if it runs out, the listing is still returned with the hashes that finished and hashing: "incomplete".

Input

{
"startUrls": [
{ "url": "https://gainesville.craigslist.org/search/apa" },
{ "url": "https://sfbay.craigslist.org/search/sfc/roo" },
{ "url": "https://www.craigslist.org/view/d/<slug>/<id>" }
],
"hashPhotos": true,
"capturePhotos": true,
"maxPhotosPerListing": 12,
"maxItemsPerUrl": 500,
"zillowViaActor": true,
"proxyConfiguration": { "useApifyProxy": true }
}

Output (one dataset item)

{
"mode": "listing",
"sourceSite": "craigslist.org",
"sourceId": "craigslist:<id>",
"sourceUrl": "https://www.craigslist.org/view/d/<slug>/<id>",
"market": "gainesville", "city": "Gainesville", "state": "FL", "zip": "32608",
"lat": 29.613, "lng": -82.387,
"kind": "apartment", "beds": 1, "baths": 1, "sqft": null,
"priceMonthly": 1285, "priceBasis": "unit",
"amenities": { "laundry": "in_unit", "parking": "assigned", "pets": "allowed", "furnished": null },
"redFlags": [],
"photoHashes": ["c8d4b2a29292b2f0", "…"],
"photoUrls": [],
"addressKey": null,
"flags": [],
"retrievedAt": "2026-10-24T23:02:12.987Z"
}

Rows that fail come back as { "sourceUrl", "error", "detail" }, so you can see which URLs failed and why.

How Showbefore uses it

  • Live comps. The Worker starts this Actor on Craigslist apa, roo and sub searches for Gainesville and SF. It upserts the results into its market_comps table, with this run's ID as the provenance of every row.
  • "Is this listing legit?" A renter pastes a listing URL. The Worker calls run-sync-get-dataset-items with a $0.10 spend cap. It compares the price with a distance- and recency-weighted band of real comps, and the photo hashes with hashes of other real listings. It also checks for the same address on another site at a different price, and for the red flags above.

Pricing

Pay per event: listing-normalized is charged once per item output. Zillow calls are billed by maxcopell/zillow-detail-scraper.

Responsible use

  • Public, logged-out pages only.
  • Use the output as facts for comparison, with attribution and a link to the original. Do not republish other people's photos or descriptions.
  • Respect each site's terms. Craigslist and Zillow restrict automated collection and reuse. You are responsible for how you use this Actor.

Local development

npm install
node --test test/*.test.mjs
APIFY_LOCAL_STORAGE_DIR=./storage CRAWLEE_STORAGE_DIR=./storage node src/main.js # reads storage/key_value_stores/default/INPUT.json
apify push