Skip to content

Instantly share code, notes, and snippets.

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);
@weisjohn
Copy link

weisjohn commented Apr 9, 2021

worked for me!

@SP3NGL3R
Copy link

SP3NGL3R commented Jun 10, 2021

Works great. Bit tedious (especially in FireFox) to click "save" on each pop-up, but a few minutes later I got my 60 photos. Thank you!

@beppodb
Copy link

beppodb commented Jul 6, 2021

Seems Zillow changed some class names between last comment and today... here are my modifications that Worked For Me™

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

script.onload = () => {
    $ = jQuery.noConflict();

    // 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 ($('svg.icon-arrow-right').length || $('svg.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 ($('svg.icon-reload').length) {
            break;
        }

        // go to the next slide
        $('svg.icon-arrow-right').parent().parent().click();
    }

    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);
            }

            let a = document.createElement('a');
            a.style = "display: none";
            console.log(i);

            let 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);

@shinryujimikihiko
Copy link

shinryujimikihiko commented Aug 12, 2021

Seems Zillow changed some class names between last comment and today... here are my modifications that Worked For Me™

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

script.onload = () => {
    $ = jQuery.noConflict();

    // 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 ($('svg.icon-arrow-right').length || $('svg.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 ($('svg.icon-reload').length) {
            break;
        }

        // go to the next slide
        $('svg.icon-arrow-right').parent().parent().click();
    }

    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);
            }

            let a = document.createElement('a');
            a.style = "display: none";
            console.log(i);

            let 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);

Hi! I am sorry to be a bother but I am entirely unskilled at this sort of thing and just want to save some images from Zillow. I scrolled through the images and pasted the script (your version) and pressed enter. Nothing happened. So:

1 - how do I make Chromium (or Firefox) execute this script

2 - what output should I expect? A download to a specific folder? A zip file? Or what?

Thank you!

@amolshambharkar
Copy link

amolshambharkar commented Aug 12, 2021

@Matthew-Hiebing
Copy link

Matthew-Hiebing commented Aug 18, 2021

@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

imageEngineer commented Jan 4, 2022

@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

imageEngineer commented Jan 5, 2022

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

ziyiwu9494 commented Jan 5, 2022

@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

imageEngineer commented Jan 5, 2022

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

@ziyiwu9494
Copy link

ziyiwu9494 commented Jan 5, 2022

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

@imageEngineer
Copy link

imageEngineer commented Jan 5, 2022

@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

ziyiwu9494 commented Jan 5, 2022

@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

@constellabella
Copy link

constellabella commented Jan 28, 2022

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

methodin commented May 11, 2022

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

MilesWells commented May 14, 2022

@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

ZackWhiteIT commented Jul 7, 2022

@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

@mzoz
Copy link

mzoz commented Sep 25, 2022

Thanks it works like a charm 🥳

@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!

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