Scrape Realtor listings for free

Intro

I wrote a script that will scrape property listings from Realtor and export as a csv!

How to Use It

Go to the search page.

Open your browsers console and copy the code below and paste it into the console. Here's a video to show you how to do that.

Hit enter and let it do it's magic! πŸ§™β€β™‚οΈ

It will take a few minutes to run. When it's done, it will download a csv file with all the data.

The script is pretty slow. If you need something super speedy, I can run a custom scraper for you that I built that is lightning fast.

How to Hire Me

If you need anything else scraped, I am accepting new clients, send me an email to get a custom scraper/bot created πŸ‘‰ adrian@thewebscrapingguy.com

async function scrollToBottom() {
  return new Promise((resolve) => {
    const scrollHeight = document.documentElement.scrollHeight
    const windowHeight = window.innerHeight
    const scrollStep = 20 // Adjust this value to control scrolling speed
    let currentPosition = window.scrollY

    function scroll() {
      if (currentPosition < scrollHeight - windowHeight) {
        currentPosition += scrollStep
        window.scrollTo(0, currentPosition)
        requestAnimationFrame(scroll)
      } else {
        window.scrollTo(0, scrollHeight)
        resolve() // Resolve the Promise when scrolling is complete
      }
    }

    scroll()
  })
}

function createCSV(jsonData, fileName) {
  // Convert JSON to CSV
  const csvData = []

  // Extract the headers
  const headers = Object.keys(jsonData[0])
  csvData.push(headers.join(','))

  jsonData.forEach((item) => {
    const row = []
    for (const key in item) {
      if (item.hasOwnProperty(key)) {
        if (typeof item[key] === 'number') {
          row.push(item[key])
          continue
        }
        const value = item[key]?.includes(',') ? `"${item[key]}"` : item[key]
        row.push(value)
      }
    }
    csvData.push(row.join(','))
  })

  // Create a Blob containing the CSV data
  const csvBlob = new Blob([csvData.join('\n')], {
    type: 'text/csv;charset=utf-8',
  })

  // Create a URL for the Blob
  const csvUrl = URL.createObjectURL(csvBlob)

  // Create a link element
  const link = document.createElement('a')
  link.href = csvUrl
  link.target = '_blank'
  link.download = fileName

  // Append the link to the body
  document.body.appendChild(link)

  // Trigger a click event on the link
  link.click()

  // Remove the link and revoke the Blob URL
  document.body.removeChild(link)
  URL.revokeObjectURL(csvUrl)
}

function getListings() {
  const listings = []
  const listingDivs = document.querySelectorAll(
    'div[id*="placeholder_property"]',
  )
  for (let i = 0; i < listingDivs.length; i++) {
    const listingDiv = listingDivs[i]
    const status = listingDiv.querySelector(
      "div[class*='StatusBadgestyles']",
    )?.textContent

    const priceString = listingDiv
      .querySelector("div[data-testid*='card-price']")
      ?.textContent?.replace('From', '')

    const price = Number(
      priceString?.replace('$', '')?.replace(',', '').replace(',', ''),
    )

    const beds = listingDiv
      .querySelector("li[data-testid*='property-meta-beds']")
      ?.textContent?.replace('bed', '')

    const baths = listingDiv
      .querySelector("li[data-testid*='property-meta-baths']")
      ?.textContent?.replace('bath', '')

    const sqftString = listingDiv.querySelector(
      "li[data-testid*='property-meta-sqft'] > span > span",
    )?.textContent

    const sqft = Number(
      sqftString?.replace('sqft', '')?.replace(',', '')?.replace(',', ''),
    )

    const lotSize = listingDiv.querySelector(
      "li[data-testid*='property-meta-lot-size'] > span > span",
    )?.textContent

    const streetAddress = listingDiv.querySelector(
      "div[data-testid*='card-address-1']",
    )?.textContent

    const cityStateZip = listingDiv.querySelector(
      "div[data-testid*='card-address-2']",
    )?.textContent

    const link = listingDiv.querySelector('a')?.href

    listings.push({
      price,
      priceString,
      beds,
      baths,
      sqft,
      lotSize,
      status,
      link,
      streetAddress,
      cityStateZip,
    })
  }
  return listings
}

async function scrapeRealtorListings() {
  const allListings = []
  let page = 1
  let nextButton = document.querySelector('.next-link')
  let isDisabled = nextButton?.classList?.contains('disabled')

  while (!isDisabled) {
    await scrollToBottom()
    await new Promise((resolve) => setTimeout(resolve, 200))
    console.log(`Scraping page ${page}`)
    const listings = getListings()
    console.log(`You scraped ${listings.length} listings`)
    console.log(
      `If you need anything on the web scraped, email me: adrian@thewebscrapingguy.com`,
    )
    allListings.push(...listings)
    nextButton = document.querySelector('.next-link')
    isDisabled = nextButton?.classList?.contains('disabled')
    nextButton.click()
    await new Promise((resolve) => setTimeout(resolve, 2000))
    page++
  }

  console.log(`Congrats! πŸŽ‰ You scraped ${allListings.length} listings`)
  createCSV(allListings, `realtor-listings-${new Date().getTime()}.csv`)
}

await scrapeRealtorListings()

Sign up for my email list

Sign Up