Intro

Today we are going to build an application that will cycle your desktop background to different randomized images from the web, based on a theme and frequency you specify. I've included a level of detail in this tutorial that I hope is suitable for programmers who are new to Node.js.

If you do not need an explanation and just want to download and use the application, you can access the full code on GitHub.

Prerequisites

  • I can verify that this will work on Windows 10+ and Mac Ventura+, there seems to be a problem with the wallpaper package on Linux systems.
  • You will need to be somewhat familiar with JavaScript. You will have an easier time if you are familiar with ES6 syntax and features, as well as Node and Npm.
  • You need to have the latest versions of Node and Npm installed on your system. If you don’t already have Node and Npm installed you can visit the following link for help with this process: https://docs.npmjs.com/downloading-and-installing-node-js-and-npm
  • You need to have an IDE installed on your system that supports JavaScript and Node.js. I recommend VSCode or Sublime, but any IDE that supports Node.js will do.
  • You will also need to sign up for a free SerpApi account at https://serpapi.com/users/sign_up.

Preparation

First you will need to create a package.json file. Open a terminal window, create a directory for the project, and CD into the directory.

mkdir wallpaper-changer
cd wallpaper-changer

Create your package.json file:

npm init

Npm will walk you through the process of creating a package.json file.
After this we need to make a few changes to the package.json file.
We will be using the ES Module system rather than CommonJs, so you need to add the line type="module" to your package.json.

{
  "name": "wallpaper-changer",
  "version": "1.0.0",
  "description": "Change your wallpaper to a randomized image from the web",
  "type": "module",
  "main": "index.js",

We will also add a start script for convenience:

  "scripts": {
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

Next we need to install our dependencies. For this project we will use the new SerpApi for JavaScript/TypeScript Node module. We will also use the image-downloader, wallpaper, write-json-file, node-schedule, and dotenv modules. Most of these have self explanatory names. If you're not familiar with dotenv, it is a module for handling environment variables (like file paths and API keys).

npm install serpapi dotenv wallpaper node-schedule write-json-file image-downloader

If you haven’t already signed up for a free Serpapi account go ahead and do that now by visiting https://serpapi.com/users/sign_up and completing the signup process.

Once you have signed up, verified your email, and selected a plan, navigate to https://serpapi.com/manage-api-key . Click the Clipboard icon to copy your API key.

Then create a new file in your project directory called .env and add the following line:

SERPAPI_KEY = “PASTE_YOUR_API_KEY_HERE”

You will also want to specify the absolute path to a folder where you want to store the images you download:

PATH_TO_IMG = “ABSOLUTE_PATH_TO_DIRECTORY”

Create a file called index.js and add the following:

import { config } from "serpapi";
import * as dotenv from "dotenv";

dotenv.config();
config.api_key = process.env.SERPAPI_KEY;

Scraping Google Images with SerpApi

We want to use SerpApi to search Google Images, and return a list of URLS we can download images from.

First we will need to determine our search parameters. We will want as much as possible to limit our search to only images with resolution and dimensions suitable for desktop wallpaper:

You can then further filter the images by selecting one or more "chips". You can see here I have selected nature, but you can select anything you like.

In order to use these filters or "chips" in our request to SerpApi, we will need to get the value of the chips parameter in the URL:

We want everything after "chips=" and before the "&" sign.

These are just suggestions. You can use any query you like, and skip "chips" if you don't need them to get a selection you like. For example instead of "4k desktop wallpaper" you can try searching for "desktop wallpaper nature" or even just "nature photos", or anything you like. Keep in mind that any image you see in the first page of results could be randomly selected as your desktop.

Now that we have selected the image search results page we want to use, we are ready to write the code to scrape it. For this we will use SerpApi. SerpApi is a web-scraping API that streamlines the process of scraping data from search engines. This can also be done manually, but SerpApi provides several distinct advantages. Besides providing real time data in a convenient JSON format, SerpApi covers proxies, captcha solvers and other necessary techniques for avoiding searches being blocked.  You can click here for an in depth tutorial to scrape Google Images both manually and with SerpApi, or here for an overview of the techniques you can use to prevent getting blocked when scraping the web.

Create a new file and name it image-search.js. Then add the following:

import { getJson } from "serpapi";

export async function getImageResults(q, chips) {
  // The parameters we will include in the the GET request
  const params = {
    q: q,
    location: "Austin, Texas, United States", //your location
    google_domain: "google.com", //the google domain for your location
    gl: "us", //your two letter country code
    hl: "en", //language
    chips: chips,
    tbm: "isch", //specifies that this an image search
  };

  // call the API and wait for it to return the data
  const data = await getJson("google", params);
  return data;
}

If you want to use the same images that came up in your browser, be sure to replace the location, google_domain, gl, and hl parameters with the correct values for your location. We are using method parameters to set q and chips so that these will be easy to change later if we decide we want a different image selection.

Now in your index.js file, import the method we just defined and call it with the q and chips parameters you chose for your image search.

index.js should now look like this:

import { config } from "serpapi";
import * as dotenv from "dotenv";
import { getImageResults } from "./image-search.js";

dotenv.config();
config.api_key = process.env.SERPAPI_KEY;

const results = await getImageResults("4k desktop wallpaper", "q:4k+desktop+wallpaper,g_1:nature:Vy6qxRTDEsA%3D");

console.log(results);

You should see something like this in your console:

If you scroll down in your terminal to where it says image_results you will see that each image has the title, link, and original fields. The link will take you to the website where the image was found. original is the URL you can use to download the image. We are going to scrape title and original in this tutorial.

We are going to write a method now that will extract these fields from each image in the image_results, and return an array of objects.

Add the following code to your image_search.js file:

export async function getImageUrls(q, chips) {
  // perform the search and store the results in a variable
  const results = await getImageResults(q, chips);

  // extract the image urls and titles and store them in an array of Objects
  const urls = results.images_results.map((img) => {
    return { link: img.original, title: img.title };
  });
  return urls;
}

Then return to your index.js file and import the new method:

import { getImageUrls } from "./image-search.js";

Modify the code you used to print the search results, so that it prints the image titles and URLs instead:

const results = await getImageUrls("4k desktop wallpaper", "q:4k+desktop+wallpaper,g_1:nature:Vy6qxRTDEsA%3D");
console.log(results);

Check your terminal output and make sure are printing a list of urls and titles.

Downloading the Image

Now that we have URLs for some images, we want to select a random one and download it. In index.js, comment out your console.log statement for now, and add the code to select a random URL from the list:

const urls = await getImageUrls("4k desktop wallpaper", "q:4k+desktop+wallpaper,g_1:nature:Vy6qxRTDEsA%3D");
// console.log(results);
const randInt = Math.floor(Math.random() * urls.length);
const randImgUrl = urls[randInt]["link"];

Create a new file called image-downloader.js. The code should look like this:

import * as download from "image-downloader";

export async function downloadImg(url) {
  //specify a url and the path to where you want to store the image
  const options = {
    url,
    dest: process.env.PATH_TO_IMG,
  };

  let img;

  // download the image
  await download
    .image(options)
    .then(({ filename }) => {
      console.log("Saved to", filename);
      img = filename.toString();
    })
    .catch((err) => console.error(err));

  // return the filename so we can find the image later
  return img;
}

Here we are accepting a URL as a parameter, and attempting to download an image from that URl into the directory we specified in our .env file. We return the file name so that we can find it when we want to set our wallpaper.

Go back to index.js. Import the downloadImg() function, and call it passing the randomly select URL as the parameter:

import { downloadImg } from "./image-downloader.js";

const urls = await getImageUrls("4k desktop wallpaper", "q:4k+desktop+wallpaper,g_1:nature:Vy6qxRTDEsA%3D");
// console.log(results);
const randInt = Math.floor(Math.random() * urls.length);
const randImgUrl = urls[randInt]["link"];
downloadImg(randImgUrl);

If you run this code, then check the directory you specified for image downloads in your .env file, you should see an image.

Setting the Desktop Wallpaper Programmatically

Create a new file and name it wallpaper-changer.js. Import the functions you wrote for scraping URLs and downloading images, as well as the wallpaper module:

import { getImageUrls } from "./image-search.js";
import { downloadImg } from "./image-downloader.js";
import { getWallpaper, setWallpaper } from "wallpaper";

Create an async function changePaper() inside wallpaper-changer.js. Copy the code you wrote in index.js in the previous step for selecting and downloading a random image and paste it inside the changePaper() function. Instead of hardcoding q and chips though, lets include them as method parameters:

export async function changePaper(q, chips) {
  const urls = await getImageUrls(q, chips);
  
    // select a random url from the array
  const randInt = Math.floor(Math.random() * urls.length);
  const randImgUrl = urls[randInt]["link"];
 
    // download the image
  const img = await downloadImg(randImgUrl);

  // set desktop wallpaper
  try{
    await setWallpaper(img);
    console.log("Set wallpaper to: " + urls[randInt]["title"]);
  }catch(e){
    console.log("e");
    console.log("current wallpaper is " + wallpaper);
  }
}

Then go back to index.js and import the changePaper() method. Replace the code you just copied and pasted with a call to changePaper(), passing in your desired q and chips values:

import { config } from "serpapi";
import * as dotenv from "dotenv";
import { changePaper } from "./wallpaper-changer.js";

dotenv.config();
config.api_key = process.env.SERPAPI_KEY;

changePaper(
  "4k desktop wallpaper",
  "q:4k+desktop+wallpaper,g_1:nature:Vy6qxRTDEsA%3D"
);

If you run npm start now, you should see your wallpaper change!

Scheduling the Wallpaper Change as a Recurring Task

Now you can download a random image from the web as your wallpaper. But what if you want to do this every Friday at 10 am? Or what if you want a new image every minute, like a slideshow?

The most common way to schedule repeating tasks as a background process is to use something called Cron Jobs. But in order to keep things simple, we are sticking to all JavaScript in this tutorial.

We can accomplish this with JavaScript using node-schedule.

First create a new file, name it scheduler.js and add the following code:

import * as schedule from "node-schedule"


export async function scheduleJob(callback, time){

    console.log("scheduling wallpaper change");
    
    // create a RecurrenceRule Object to define when you want the job to repeat
    const rule = new schedule.RecurrenceRule(); 
    
    // copy the properties from the time object to the RecurrenceRule instance
    Object.assign(rule, time); 
    
    // schedule the job
    schedule.scheduleJob(rule, callback); 
}

Notice the two parameters for the scheduleJob() function - callback and time.  

callback is the function we want to schedule to run, and time is an object with properties specifying when we want the function to run.

Go back now to your index.js. We don't want to call our changePaper() function right away anymore, instead we want to pass it in as the first argument to the scheduleJob() function. We also need to define a time object to specify when we want the code to run. Don't forget to add the import statement for the schedule Job function:

import { config } from "serpapi";
import * as dotenv from "dotenv";
import { changePaper } from "./wallpaper-changer.js";
import { scheduleJob } from "./scheduler.js";


dotenv.config();
config.api_key = process.env.SERPAPI_KEY;

const callback = () =>
  changePaper(
    "4k desktop wallpaper",
    "chips=q:desktop+wallpaper+4k,g_1:nature:uPhOf0Whosk%3D"
  );

const time = {
  second: 0,
  //hour: 0,
  //minute: 0,
  //dayOfWeek: 0,
  //tz: "PST"
};

scheduleJob(callback, time);

The code above will cause the wallpaper to change once every minute. You can change the values in the time variable to anything you want. I suggest starting with every minute through, to make sure it is working.

You can use any of the following key-value pairs in your time object:

  • second (0-59)
  • minute (0-59)
  • hour (0-23)
  • date (1-31)
  • month (0-11)
  • year
  • dayOfWeek (0-6) Starting with Sunday
  • tz

You can read more about how to specify different times at https://www.npmjs.com/package/node-schedule.

Now you can run npm start and watch what happens.

Saving the URLS to a file

You have successfully built an application that periodically downloads a random image from the web and sets it as your desktop wallpaper. You may have noticed there is a part of this process that is incredibly inefficient - we are scraping the image URLS again each time we change the image. It costs a search credit every time we call SerpApi (unless it is an identical call within the hour, in which case the results are fetched from the cache). In any case we want to avoid making unnecessary repeat API calls.

To solve this we will use the write-json-file node module to save the URLs in an external JSON file, instead of holding them in memory and repeating our request over and over again. We will also use the fs module here for interacting with the file system.  Node.js ships with fs , so you don't need to install it, but we do need to add import statements.

Open your wallpaper-changer.js file again. The code here is a bit more convoluted. We first have to check whether the ".json" file already exists. If it exists we want to read it.

If the file doesn't exist, or if it is empty, we want to call our the getImageUrls() function to request search results and extract the URLS, then we want to create the ".json" file and write the URLs to it.

import { getImageUrls } from "./image-search.js";
import { downloadImg } from "./image-downloader.js";
import { getWallpaper, setWallpaper } from "wallpaper";
import { writeJsonFile } from "write-json-file";
import { readFile } from "fs/promises";
import { existsSync } from "fs";

export async function changePaper(q, chips) {
  let urls;

  // check whether the file where we save the urls exists
  const fileExists = existsSync("./image-urls.json");

  // if the file exists, parse the data from it into the "urls" variable
  if (fileExists) {
    const json = JSON.parse(
      await readFile(new URL("./image-urls.json", import.meta.url))
    );
    urls = json.urls;
  }

  // If the file doesn't exist or is empty, download the image urls and create/write the urls to the file
  if (!fileExists | !urls) {
    urls = await getImageUrls(q, chips); // search google images and extract the image urls
    await writeJsonFile("image-urls.json", { urls });
  }

  // select a random url from the array
  const randInt = Math.floor(Math.random() * urls.length);
  const randImgUrl = urls[randInt]["link"];

  // download the image
  const img = await downloadImg(randImgUrl);

  // set desktop wallpaper
  try{
    await setWallpaper(img);
    console.log("Set wallpaper to: " + urls[randInt]["title"]);
  }catch(e){
    console.log("couldn't set wallpaper");
    console.log("e");
  }
  // verify that we've changed the wallpaper, useful for troubleshooting
  const wallpaper = await getWallpaper();
  console.log("current wallpaper is " + wallpaper);
}

Conclusion

We have created a Node.js application to make our desktop wallpaper cycle through random images from the web at scheduled intervals. You now have a template you can modify in any way you like. You can try out different themes by modifying the values of q and chips, and deleting the .json file.

It may have occurred to you that if you keep downloading images indefinitely, eventually they will take up too much space on your hard drive.

Ideally we would want to add code to check how many images are in the directory, and delete them after a specified limit. Or delete them every time we change the background, or after every time the program runs. I haven't covered this here but if you want to implement it, this blog post post explains how to delete files using fs: https://www.bezkoder.com/node-js-delete-file/#:~:text=To delete a file in,loop because it works asynchronously.

You will also have to keep the terminal window open for the program to run and continue changing your desktop background at scheduled intervals. If you want it to run as a background process, I suggest learning about Cron Jobs and replacing the node-schedule module and our scheduler.js file with a Cron Job.

I hope you found the tutorial informative and easy to follow. If you have any questions, feel free to contact me at ryan@serpapi.com.

SerpApi:

Node modules: