How To: Automatically download the latest and greatest wallpapers from Reddit
Intro
Do you set wallpapers for your PC? I do! I like having pretty art on my desktop. I have a folder of wallpaper images that I have collected over the years.
I set my OS to pick a random image from the folder at one-hour intervals. I like the surprise factor when I see a different wallpaper show up on the desktop every now and then.
About twice a year, I set out on a journey to the internet to find new wallpapers for my PC. In particular, I find that Reddit is a great source for wallpaper images. They have subreddits where people post art and images in large resolutions, suited for PC wallpapers. I would go in there, browse through the top posts and find pretty images that I want displayed on my desktop.
However, I have a fickle personality and I tend to get bored with things very quickly. Same goes for wallpapers. When I set a new image for my desktop, I get the "new wallpaper feeling" (much like the "new car feeling" or the "new phone feeling") for about 20 minutes, but the novelty quickly wears off.
Having said that, I also can't be bothered to search for wallpapers all the time. I mean, I do get bored of having the same wallpaper, and I like collecting new ones for my PC. But, me being satiated over the same set of wallpapers is usually not big enough of a problem to get my lazy ass into gear. It takes about 6 months for my boredom to "build up" before I finally decide on visiting r/wallpaper and downloading a new set of wallpapers.
Therefore, as any typical developer would do, I decided to automate the image hunting process!
How it works
The Reddit API
Reddit provides a very powerful data API that you can use to fetch content programmatically. I found that the methods inside the API were very intuitive and similar to the Reddit browsing experience. You can fetch subreddits, sort the posts inside the subreddit by top posts/hot posts/most recent, and you can even set time filters for weekly/monthly/all time.
You can also make PUT/POST requests to write data to Reddit, like creating a submission, writing a comment or upvoting on a submission. We will only use GET methods for this article, but have a look at the documentation on POST methods if you are interested.
PRAW
When you use an API programmatically, you want to use a wrapper to simplify the interactions and streamline the data fetching process. There are many Reddit API wrappers for different programming languages. In this article I will be using Python, so the wrapper of choice is PRAW.
The steps for using PRAW is simple. First, you create credentials for authenticating into the Reddit service. This can be done by visiting https://www.reddit.com/prefs/apps and creating a new app for yourself. Simply click "Create App" and fill in the following information:
- name: A name for your app
- App type: Choose the script option
- description: You can leave this blank
- about url: You can leave this blank
- redirect url: http://www.example.com/unused/redirect/uri (We won't be using this as a redirect)
Once you create the app, copy down the Client ID and Secret for your application. We will use this later to authenticate to the Reddit API.
Second, you create a USER_AGENT variable in Python to declare who you are. To use the Reddit API, you need a unique USER_AGENT value so that Reddit can authenticate you. The format for this value is <platform>:<app ID>:<version string> (by u/<Reddit username>)
. For my example, it will be linux:com.example.wallpaper-fetch:v1.0 (by u/<my username>)
.
Third, create a praw.Reddit
instance in Python. Here you pass in the Client ID and Secret from step 1, and the USER_AGENT variable from step 2.
USER_AGENT = (
f"linux:com.example.wallpaper-fetch:v1.0 (by u/{os.getenv('REDDIT_USERNAME')})"
)
reddit = praw.Reddit(
client_id=os.getenv("CLIENT_ID"),
client_secret=os.getenv("CLIENT_SECRET"),
user_agent=USER_AGENT,
)
With the praw.Reddit
instance created, you can go ahead and browse subreddits! To do this, use the subreddit
method. You can also set time filters by using the hot()
or top()
methods inside the Subreddit
class.
# Get the r/wallpaper subreddit
wp_subreddit = reddit.subreddit("wallpaper")
# Get the hottest posts in the sub
hot_posts = wp_subreddit.hot()
# Get the top posts for the week
weekly_top_posts = wp_subreddit.top(time_filter="week")
Downloading the image
Now that we have access to the r/wallpaper subreddit, the next step is to download content from it. The subreddit.top()
method returns a list of submission
items. These items contain the information required for us to fetch wallpapers from the submission.
Lets take a step back and browse the r/wallpaper subreddit. You will see that all the posts contain only images. There are no text in any of the posts. This is because the subreddit is moderated - the subreddit rules specifically states:
Submit direct links to images or imgur/reddit albums only
This is handy for us programmers, since we won't have to worry about edge cases of poorly formatted posts. All we have to do is look for the url
attribute in the submission items to get the link to the images:
for submission in reddit_instance.subreddit(subreddit).top(time_filter="week"):
# Get the image URL from the reddit post
url = submission.url
# Download the image
r = requests.get(url)
# Write the image to a file
with open("image.png", "wb") as f:
f.write(r.content)
That's it! Now we can find the top submissions in r/wallpaper and download images from it. Now lets add some checks to make the script more robust.
Checking the Image
The first thing we have to consider is image extension. Image files can come in different formats (.png, .jpg, .jpeg, etc) and we have to distinguish them before saving. To do this, we will extract the image extension from the URL of the image file. We will also use the submission title as the image name while we're here:
# Get the image URL from the reddit post
url = submission.url
# Create the file name
file_extension = "." + url.split(".")[-1]
image_name = submission.title.replace(" ", "_")
filename = image_name + file_extension
# Download the image
r = requests.get(url)
# Write the image to a file
with open(filename, "wb") as f:
f.write(r.content)
Another thing I want to check is the image resolution. I have two monitors: one is 3440x1440 and another is 1920x1080. Thankfully, the r/wallpaper subreddit is moderated so nicely that it requires all posts to have the resolution of the image stated in the post title.
Title must include the image resolution (width
xheight
surrounded by brackets) AND must include a description of the image
God bless content moderation! All I have to do is check if my desired resolution is in the submission title:
resolution = "1920x1080"
for submission in reddit_instance.subreddit(subreddit).top(time_filter="week"):
if resolution in submission.title:
# Get the image URL from the reddit post
url = submission.url
# Create the file name
file_extension = "." + url.split(".")[-1]
image_name = submission.title.replace(" ", "_")
filename = image_name + file_extension
# Download the image
r = requests.get(url)
# Write the image to a file
with open(filename, "wb") as f:
f.write(r.content)
HOWEVER, there is the edge case that an image is declared to be a certain resolution, but the actual file is not. To deal with this, I will use the Python Imaging Library pillow to check the resolution of the image before downloading the file:
from PIL import Image
def check_image_resolution(image_data, expected_resolution):
try:
with Image.open(BytesIO(image_data)) as img:
width, height = img.size
return f"{width}x{height}" == expected_resolution
except Exception as e:
return False
resolution = "1920x1080"
for submission in reddit_instance.subreddit(subreddit).top(time_filter="week"):
if resolution in submission.title:
# Get the image URL from the reddit post
url = submission.url
# Create the file name
file_extension = "." + url.split(".")[-1]
image_name = submission.title.replace(" ", "_")
filename = image_name + file_extension
# Download the image
r = requests.get(url)
# Check if the image resolution matches the expected resolution
if check_image_resolution(r.content, resolution):
with open(image_save_location + filename, "wb") as f:
f.write(r.content)
break
else:
continue
Running it Periodically
As I mentioned at the beginning, I want to run this script periodically to get the latest and greatest images. To do this, I set my script to run weekly using cron.
crontab -e
# Add the following line to your crontab file
0 8 * * 0 /path/to/your/script/fetch_reddit_wallpapers/run.py > /path/to/your/script/fetch_reddit_wallpapers/log.txt 2>&1
I set my script to run on 8AM on Sunday. You can change when the script runs and how often by editing the first five characters of the cron expression. For more information, visit https://crontab.guru/#0_8_*_*_0.
The code
I have published my code at https://github.com/rolzy/fetch-reddit-wallpaper. I have made some changes so that it is more configurable for different users, such as:
- Setting the image source subreddit, image save location and target resolution using a configuration file
- Removing old images from the image save location once the number of images in the folder exceeds a certain amount (by default I retain 50 images)
Hope you find the article helpful! Happy image-collecting!