Skip to content

Instantly share code, notes, and snippets.

@bryant988
Last active May 29, 2023 03:07
Embed
What would you like to do?
Zillow Image Downloader
/**
* NOTE: this specifically works if the house is for sale since it renders differently.
* This will download the highest resolution available per image.
*/
/**
* STEP 1: Make sure to *SCROLL* through all images so they appear on DOM.
* No need to click any images.
*/
/**
* STEP 2: Open Dev Tools Console.
* Copy and paste code below
*/
const script = document.createElement('script');
script.src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js";
script.onload = () => {
$ = jQuery.noConflict();
const imageList = $('ul.media-stream li picture source[type="image/jpeg"]').map(function () {
const srcset = $(this).attr('srcset').split(' '); // get highest res urls for each image
return srcset[srcset.length - 2]
}).toArray();
const delay = ms => new Promise(res => setTimeout(res, ms)); // promise delay
// get all image blobs in parallel first before downloading for proper batching
Promise.all(imageList.map(i => fetch(i))
).then(responses =>
Promise.all(responses.map(res => res.blob()))
).then(async (blobs) => {
for (let i = 0; i < blobs.length; i++) {
if (i % 10 === 0) {
console.log('1 sec delay...');
await delay(1000);
}
var a = document.createElement('a');
a.style = "display: none";
console.log(i);
var url = window.URL.createObjectURL(blobs[i]);
a.href = url;
a.download = i + '';
document.body.appendChild(a);
a.click();
setTimeout(() => {
window.URL.revokeObjectURL(url);
}, 100);
}
});
};
document.getElementsByTagName('head')[0].appendChild(script);
@amolshambharkar
Copy link

amolshambharkar commented Aug 12, 2021 via email

@Matthew-Hiebing
Copy link

@TheSharpieOne
Your code is off by one at the end. The final image has a reload icon instead of a next one, and it doesn't get downloaded. This works for me:

// can't map since there isn't a list, so just push as we find more.
const imageList = [];

// while there is a next button
while($('div.zsg-icon-expando-right').length || $('div.zsg-icon-reload').length) {
  const srcs = $('.hdp-photo-gallery-lightbox-content .hdp-gallery-image-content:visible source[type="image/jpeg"]').attr('srcset').split(' ');
  const src = srcs[srcs.length - 2];
  // just in case... let make sure the src is not already in the list.
  if (imageList.indexOf(src) === -1) {
    imageList.push(src);
  }
  // Last image, break out of loop
  if ($('div.zsg-icon-reload').length) {
    break;
  }

  // go to the next slide
  $('div.zsg-icon-expando-right').click();
}

Thanks for the help, for some reason I only got 6 downloaded before modifying the source to this code. Thanks everyone for your contribution!

I noticed that you have to vertically scroll through all of the photos before running the script. After that, click on one of the photos to enter the view modal. After that you'll see the forward and back arrows appear. Once you see the forward and back arrows, run the script.

@meshane
Copy link

meshane commented Aug 26, 2021

When I used this on closed listings it worked, but on open listing the last slide regarding request a showing would cause the entire script to error out. I had to remove || $('div.zsg-icon-reload' for the open listing.

@dmerrick
Copy link

dmerrick commented Dec 7, 2021

the code @beppodb posted worked for me today on a closed home

@imageEngineer
Copy link

@meshane which version of the script were you trying? where did you make that change? When I try to run @beppodb version on an open listing I get an error:

Uncaught TypeError: Cannot read properties of undefined (reading 'split') at HTMLScriptElement.script.onload (<anonymous>:12:138)

@ziyiwu9494
Copy link

ziyiwu9494 commented Jan 4, 2022

For the full resolution images, they use the same base url as the srcset ones so you can just call .replace(/cc_ft_384.jpg|cc_ft_576.jpg/, 'uncropped_scaled_within_1536_1152.jpg') to find the urls of the full res images for download. My quick and dirty modification is here but I don't know if it generalizes to all possible resolutions.

const script = document.createElement('script');
script.src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js";

script.onload = () => {
  $ = jQuery.noConflict();
  
  const imageList = $('ul.media-stream li picture source[type="image/jpeg"]').map(function () {
    const srcset = $(this).attr('srcset').split(' '); // get highest res urls for each image
    console.log(srcset);
    return srcset[srcset.length - 2].replace(/cc_ft_384.jpg|cc_ft_576.jpg/, 'uncropped_scaled_within_1536_1152.jpg')
  }).toArray();
  console.log(imageList);
  const delay = ms => new Promise(res => setTimeout(res, ms)); // promise delay
  
  // get all image blobs in parallel first before downloading for proper batching
  Promise.all(imageList.map(i => fetch(i))
  ).then(responses =>
    Promise.all(responses.map(res => res.blob()))
    ).then(async (blobs) => {
      for (let i = 0; i < blobs.length; i++) {
        if (i % 10 === 0) {
          console.log('1 sec delay...');
          await delay(1000);
        }
        
        var a = document.createElement('a');
        a.style = "display: none";
        console.log(i);
        
        var url = window.URL.createObjectURL(blobs[i]);
        a.href = url;
        a.download = i + '';
        document.body.appendChild(a);
      a.click();

      setTimeout(() => {
        window.URL.revokeObjectURL(url);
      }, 100);
    }
  });
};

document.getElementsByTagName('head')[0].appendChild(script);

@imageEngineer
Copy link

Thanks for looking into it @ziyiwu9494! Strangely this worked only for the first 6 images only. This is the listing I'm looking at.

@ziyiwu9494
Copy link

@imageEngineer I just changed it to match any initial resolution and it seems to work now. Try this:

const script = document.createElement('script');
script.src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js";

script.onload = () => {
  $ = jQuery.noConflict();
  
  const imageList = $('ul.media-stream li picture source[type="image/jpeg"]').map(function () {
    const srcset = $(this).attr('srcset').split(' '); // get highest res urls for each image
    return srcset[srcset.length - 2].replace(/cc_ft_.*.jpg/, 'uncropped_scaled_within_1536_1152.jpg')
  }).toArray();
  const delay = ms => new Promise(res => setTimeout(res, ms)); // promise delay
  
  // get all image blobs in parallel first before downloading for proper batching
  Promise.all(imageList.map(i => fetch(i))
  ).then(responses =>
    Promise.all(responses.map(res => res.blob()))
    ).then(async (blobs) => {
      for (let i = 0; i < blobs.length; i++) {
        if (i % 10 === 0) {
          console.log('1 sec delay...');
          await delay(1000);
        }
        
        var a = document.createElement('a');
        a.style = "display: none";
        console.log(i);
        
        var url = window.URL.createObjectURL(blobs[i]);
        a.href = url;
        a.download = i + '';
        document.body.appendChild(a);
      a.click();

      setTimeout(() => {
        window.URL.revokeObjectURL(url);
      }, 100);
    }
  });
};

document.getElementsByTagName('head')[0].appendChild(script);

@imageEngineer
Copy link

For some reason it's still only getting the first 6 images for me @ziyiwu9494. Not sure why.

@ziyiwu9494
Copy link

@imageEngineer Have you made sure to scroll through and load all the images before trying to download?

@imageEngineer
Copy link

@ziyiwu9494 yeah. I load the page, open the gallery, flip through all the images, return to the first image and run the script.

@ziyiwu9494
Copy link

@imageEngineer You don't need to go through the gallery, you just need to scroll through the images on the page itself.

@imageEngineer
Copy link

imageEngineer commented Jan 5, 2022 via email

@constellabella
Copy link

Just used @beppodb 's code on my home that closed a while back, but pix were still up - perfect! Thanks a ton!

@imderek
Copy link

imderek commented Mar 27, 2022

@ziyiwu9494's code worked great!

@bxs1260
Copy link

bxs1260 commented Apr 1, 2022

It worked fine for me today:

How to execute it :

  1. Open the listing photos you need to download on website (I used google chrome browser) and it should look like this

image

  • Then right click and select inspect it would look like this :

image

  • then select console and paste the code provided by @beppodb or @ziyiwu9494 (tested both and worked just fine) and hit enter.

image

it will then ask you to save on ur computer by using a pop up for every file. Keep hitting enter and it will stop producing more pop ups once all phots have been downloaded.

In last if you are still having issues, please let me know I'll be happy to help to use this utility

Thanks to @bryant988 and all other contributors. Good job!

@joncl
Copy link

joncl commented Apr 4, 2022

Only Firefox worked for me running code last posted by ziyiwu9494 (https://gist.github.com/bryant988/9510cff838d86dcefa3b9ea3835b8552?permalink_comment_id=4016913#gistcomment-4016913). However, I got only 12/99 photos every time. Increasing the delay to 2 seconds worked like a charm. Updated code with the 2 second delay below.

Also, I flipped through all the images from the gallery first, and then ran the code in the console (righ-click > Inspect) from the first image.

const script = document.createElement('script');
script.src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js";

script.onload = () => {
  $ = jQuery.noConflict();
  
  const imageList = $('ul.media-stream li picture source[type="image/jpeg"]').map(function () {
    const srcset = $(this).attr('srcset').split(' '); // get highest res urls for each image
    return srcset[srcset.length - 2].replace(/cc_ft_.*.jpg/, 'uncropped_scaled_within_1536_1152.jpg')
  }).toArray();
  const delay = ms => new Promise(res => setTimeout(res, ms)); // promise delay
  
  // get all image blobs in parallel first before downloading for proper batching
  Promise.all(imageList.map(i => fetch(i))
  ).then(responses =>
    Promise.all(responses.map(res => res.blob()))
    ).then(async (blobs) => {
      for (let i = 0; i < blobs.length; i++) {
        if (i % 10 === 0) {
          console.log('2 sec delay...');
          await delay(2000);
        }
        
        var a = document.createElement('a');
        a.style = "display: none";
        console.log(i);
        
        var url = window.URL.createObjectURL(blobs[i]);
        a.href = url;
        a.download = i + '';
        document.body.appendChild(a);
      a.click();

      setTimeout(() => {
        window.URL.revokeObjectURL(url);
      }, 100);
    }
  });
};

document.getElementsByTagName('head')[0].appendChild(script);

@methodin
Copy link

I kept getting 6 photos only so I had to change

$('ul.media-stream li picture source[type="image/jpeg"]').map(function () {

to

$('picture source[type="image/jpeg"]').map(function () {

@BradFD
Copy link

BradFD commented May 11, 2022

Same issue, 6 photos only but your mod gives me an error,
Uncaught TypeError: Cannot read properties of undefined (reading 'replace')
at HTMLSourceElement. (:9:38)
at VM1211 jquery.min.js:2:935
at Function.map (VM1211 jquery.min.js:2:3485)
at n.fn.init.map (VM1211 jquery.min.js:2:903)
at HTMLScriptElement.script.onload (:7:64)

I kept getting 6 photos only so I had to change

$('ul.media-stream li picture source[type="image/jpeg"]').map(function () {

to

$('picture source[type="image/jpeg"]').map(function () {

@MilesWells
Copy link

@joncl Worked perfectly for me, thank you! Scrolled so all the thumbnails loaded into the DOM and pasted this in. Firefox v100.0.

@ZackWhiteIT
Copy link

@joncl's version worked as expected for me. Thanks!

@teriross
Copy link

teriross commented Aug 5, 2022

Hi. Can I pay someone to help me with this? I have one listing on Zillow. TIA

@jaredpavan
Copy link

jaredpavan commented Aug 5, 2022

@teriross - are you having any issues? Here are the steps:

  1. Open the listing in Firefox or Google Chrome
  2. Click on the first image, then click through each image so they are loaded to your browser
  3. Right click anywhere on the page and select "Inspect"
  4. Click on the "console" tab of the window that just opened
  5. If on firefox, first type allow pasting and press enter
  6. Paste this code and click enter (this is @joncl's code with @methodin's fix added):
const script = document.createElement('script');
script.src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js";

script.onload = () => {
  $ = jQuery.noConflict();
  
  const imageList = $('picture source[type="image/jpeg"]').map(function () {
    const srcset = $(this).attr('srcset').split(' '); // get highest res urls for each image
    return srcset[srcset.length - 2].replace(/cc_ft_.*.jpg/, 'uncropped_scaled_within_1536_1152.jpg')
  }).toArray();
  const delay = ms => new Promise(res => setTimeout(res, ms)); // promise delay
  
  // get all image blobs in parallel first before downloading for proper batching
  Promise.all(imageList.map(i => fetch(i))
  ).then(responses =>
    Promise.all(responses.map(res => res.blob()))
    ).then(async (blobs) => {
      for (let i = 0; i < blobs.length; i++) {
        if (i % 10 === 0) {
          console.log('2 sec delay...');
          await delay(2000);
        }
        
        var a = document.createElement('a');
        a.style = "display: none";
        console.log(i);
        
        var url = window.URL.createObjectURL(blobs[i]);
        a.href = url;
        a.download = i + '';
        document.body.appendChild(a);
      a.click();

      setTimeout(() => {
        window.URL.revokeObjectURL(url);
      }, 100);
    }
  });
};

document.getElementsByTagName('head')[0].appendChild(script);

You should get a download prompt for each image if using Chrome, and if using Firefox the download center will open and show each image being downloaded.

If this works for you, then I'll take payment in the form of a donation of any amount to https://www.climatefoundation.org/ :)

@teriross
Copy link

teriross commented Aug 5, 2022 via email

@T150
Copy link

T150 commented Oct 1, 2022

TY! Still working as of today, using bxs1260 instructions. Great for cataloging favorite ZIllow listings/photos every now and then.
TY again, to all contributors!

@teriross
Copy link

teriross commented Oct 11, 2022 via email

@raghucbz
Copy link

Thanks for the code and all the comments so far. Here are some additional insights:

  • Code runs based on Zillow picture tiles when you click on the property. Scroll all the way down to load all the squares in the tiles, especially listings with over 10 pictures. Otherwise only 8 or so pics would be downloaded.
  • Zillow has changed the download function so only low resolution JFIF files would be downloaded by the browser.
  • I made a change to the code to download WEBP files (uncropped_scaled_within_1536_1152.webp)
  • You can convert WEBP files to JPG online using https://anywebp.com/ (20 pics at a time). Scale your JPG to 2x for converting and download HiRes pics
  • Here's the update code that I used:
const script = document.createElement('script');
script.src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js";

script.onload = () => {
  $ = jQuery.noConflict();
  
  const imageList = $('ul.media-stream li picture source[type="image/jpeg"]').map(function () {
    const srcset = $(this).attr('srcset').split(' '); // get highest res urls for each image
    return srcset[srcset.length - 2].replace(/cc_ft_.*.jpg/, 'uncropped_scaled_within_1536_1152.webp')
  }).toArray();
  const delay = ms => new Promise(res => setTimeout(res, ms)); // promise delay
  
  Promise.all(imageList.map(i => fetch(i))
  ).then(responses =>
    Promise.all(responses.map(res => res.blob()))
    ).then(async (blobs) => {
      for (let i = 0; i < blobs.length; i++) {
        if (i % 10 === 0) {
          console.log('1 sec delay...');
          await delay(1000);
        }
        
        var a = document.createElement('a');
        a.style = "display: none";
        console.log(i);
        
        var url = window.URL.createObjectURL(blobs[i]);
        a.href = url;
        a.download = i + '';
        document.body.appendChild(a);
      a.click();

      setTimeout(() => {
        window.URL.revokeObjectURL(url);
      }, 100);
    }
  });
};

document.getElementsByTagName('head')[0].appendChild(script);

@mhardin
Copy link

mhardin commented Apr 10, 2023

Thanks @raghucbz I can confirm your version works as of April 2023.

@samuelzamvil
Copy link

Working in May as well. Thank you @raghucbz !

@Camz-art
Copy link

Hi everyone!!
Thanks for the great information!! I was able to get the code to work on Zillow. Does anyone know of a similar code to work on Redfin?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment