Skip to content

Instantly share code, notes, and snippets.

@bryant988
Last active November 20, 2024 23:18
Show Gist options
  • Save bryant988/9510cff838d86dcefa3b9ea3835b8552 to your computer and use it in GitHub Desktop.
Save bryant988/9510cff838d86dcefa3b9ea3835b8552 to your computer and use it in GitHub Desktop.
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);
@hzarrabi
Copy link

hzarrabi commented Aug 8, 2024

This also worked for me. So long as you scroll down in the Zillow image gallery to get all the images it will gather all the images in a zip folder for you. I targeted all the .webp images since that's the image type that Zillow requests/uses.

// Load JSZip library
const script = document.createElement('script');
script.src = "https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js";
document.head.appendChild(script);

script.onload = function() {
    // Function to download the zip file
    function downloadZip(zip) {
        zip.generateAsync({type: 'blob'}).then(function(content) {
            const link = document.createElement('a');
            link.href = URL.createObjectURL(content);
            link.download = 'images.zip';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        });
    }

    // Function to gather and zip .webp image URLs from the network requests
    function gatherAndZipImages() {
        const imageUrls = [];
        const zip = new JSZip();
        const imgFolder = zip.folder("images");

        // Get all network requests from the Performance tab
        const requests = window.performance.getEntriesByType('resource');

        requests.forEach((request, index) => {
            if (request.initiatorType === 'img' && request.name.endsWith('.webp')) {
                imageUrls.push(request.name);
            }
        });

        if (imageUrls.length > 0) {
            console.log('Image URLs:', imageUrls);
            const downloadPromises = imageUrls.map((url, index) => {
                return fetch(url).then(response => response.blob()).then(blob => {
                    imgFolder.file(`image_${index + 1}.webp`, blob);
                });
            });

            Promise.all(downloadPromises).then(() => {
                downloadZip(zip);
            });
        } else {
            console.log('No .webp images found.');
        }
    }

    // Execute the function to gather and zip images
    gatherAndZipImages();
}

@jessereitz
Copy link

jessereitz commented Nov 2, 2024

I haven't been able to get the above solutions to work but I was able to use @hzarrabi's snippet as a base for this script. Theirs should be more reliable but for some reason only a small number of the actual images were ever found in the resources... I expect this to be fairly brittle given it's relying on data attributes to select DOM elements but as of now (November 2, 2024) it works a treat. I threw in some options at the top for image format and sizes Zillow makes available as well.

const TARGET_FORMAT = "jpeg";  // Options: `jpeg` or `webp`
const TARGET_SIZE = "1536";  // Options: `1536`, `1344`, `1152`, `960`, `768`, `576`, `384`, `192`

// Load JSZip library
const script = document.createElement('script');
script.src = "https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js";
document.head.appendChild(script);

script.onload = function() {
    // Function to download the zip file
    function downloadZip(zip) {
        zip.generateAsync({type: 'blob'}).then(function(content) {
            const link = document.createElement('a');
            link.href = URL.createObjectURL(content);
            link.download = 'images.zip';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        });
    }

    // Function to gather and zip image URLs from "media wall"
    function gatherAndZipImages() {
        // Gather the image URLs
        const mediaWall = document.querySelector('ul[data-cy="hollywood-vertical-media-wall"]');
        const sources = Array.from(mediaWall.querySelectorAll(`source[type="image/${TARGET_FORMAT}"]`));

        // Try to pull the largest src URL from a source's srcset
        // srcset is in the format "<url> <size>, <url> <size>" so we split it and try to grab the last (hopefully largest) URL
        // It shouldn't really matter, though, since the regex will replace the target size with the largest possible anyway
        const imageUrls = sources.map(source => {return source.srcset.split(",").at(-1).split(" ")[1].replaceAll(/_\d+.(jpg|webp)/g, `_${TARGET_SIZE}.${TARGET_FORMAT}`)});

        const zip = new JSZip();
        const imgFolder = zip.folder("images");

        if (imageUrls.length > 0) {
            console.log('Image URLs:', imageUrls);
            const downloadPromises = imageUrls.map((url, index) => {
                return fetch(url).then(response => response.blob()).then(blob => {
                    imgFolder.file(`image_${index + 1}.${TARGET_FORMAT}`, blob);
                });
            });

            Promise.all(downloadPromises).then(() => {
                downloadZip(zip);
            });
        } else {
            console.log(`No .${TARGET_FORMAT} images found.`);
        }
    }

    // Execute the function to gather and zip images
    gatherAndZipImages();
}

@jawaad-ahmad
Copy link

This latest one worked great for me! Just needed to remember to Continue in the debugger repeatedly for each image on the page. Thanks!

@jessereitz
Copy link

@jawaad-ahmad, glad it worked for ya! I completely forgot to remove that debugger statement. I edited my above comment to remove it so continuing the debugger shouldn't be necessary with the new coee

@macstainless
Copy link

@jessereitz your script worked!!! Thank you!!!

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