Creating a beautiful website for your Anchor podcast programatically

I couldn’t think of a good title, so let me break down the problem.

I wanted to start a podcast, and I wanted to have a website for that podcast, with all my episodes on it. There are a couple solutions for this, but none that quite fit my needs.

What I landed on was a WordPress site hosted on NodeHost, that updates its contents when I run a local Python script. Said script goes to Anchor (my podcast host), and downloads the feed. Then it iterates over each episode in the feed, checking if it already has a page on the website, and if not, creating one for it.

However, seeing as my podcast does not have any episodes currently, I’ll be borrowing the feed from Couples Therapy, if you’re following along at home, you can find the feed here.

First things first, the website. I landed on WordPress for the backend of this system as I was familiar with (thanks to this blog), but also because it’s incredibly simple to set up with NodeHost. As for why I chose Anchor… it’s free, and I’m cheap.

Downloading episodes from Anchor

First step is to download a list of episodes from Anchor, for that we need feedparser, an open-source Atom and RSS parser for Python. First we import feedparser, then make a request to our feed URL.

import feedparser
feed = feedparser.parse("https://anchor.fm/s/e82a60/podcast/rss")

Now we want to create an array of just the titles of each episode, as the titles on our blog are to match. We can do that with some list comprehension:

existing_episodes = [post.title for post in wordpress.get_posts()]

But wait a minute, what’s that wordpress.get_posts() call!? Well that function comes from another file I’ve created called wordpress.py, let’s just sidestep a minute and visit that file:

wordpress.py

This is the file we’ll use for all of our WordPress communications. Start by installing the wordpress-xmlrpc library here, then import the necessary classes:

from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods import posts

Now we need to define our credentials to log in to WordPress, I highly recommend you use Application Passwords for WordPress as otherwise you risk leaking your credentials should you be hacked.

wpUrl='yourwordpressurl/xmlrpc.php' 
wpUserName='your_username'
wpPassword='your_password'

Every WordPress site has a file called xmlrpc.php by default. This file accepts only POST requests, and allows you to post to your blog from third-party services (like the script we’re creating)

Now we can initialise our client object

client = Client(wpUrl, wpUserName, wpPassword)

Finally, let’s define that get_posts() function we tried to use earlier:

def get_posts():
	return client.call(posts.GetPosts())

Alright, let’s go back to our main file…

Back to the main file

Now that line we tried earlier,

existing_episodes = [post.title for post in wordpress.get_posts()]

, will actually work.

Iterating over the feed

Remember way back when when we requests our RSS feed in our script? Well, it’s time to start processing it. This call results in a feedparser.FeedParserDict, which is a string-subscriptable object, meaning we lookup our episodes really easily. Each episode is an object inside of the "entries" array, so we can get that with feed["entries". So now let’s create a variable called episode, and use it to iterate over feed["entries"

for episode in feed["entries"]:

Now in each episode, we want to check if the "title" matches a post in existing_episodes, thankfully Python makes this easy:

	title = episode["title"]
	if title not in existing_episodes:

By using not in, we can make it so we only process the episodes which we haven’t already processed before. This is useful as it means you won’t end up with duplicate posts on your website.

Now this next part will vary based on your podcast host, so keep in mind this is Anchor specific. That said, it will be easy to migrate it to other services, as all we’re doing is finding a link to our podcast file.

link = next(x for x in episode["links"] if x["rel"] == "enclosure")["href"]

That’s a long line, so let’s break it now. First we create a variable called link, which represents the URL to the podcast file (an mp3 in our case). Then we use next, which returns the first object in an array which matches predication. So our episode objects holds an array of links in the "links" object, with each link containing a "rel". It so happens that with Anchor, well the "rel" is equal to "enclosure", we know that object contains the link to our mp3, which we then lookup with the ["href"] subscript.

Now that we have our link, we can move on to posting.

Posting

We again use our wordpress.py file, and now call the post function of it

wordpress.post(episode["title"], episode["summary"], link, episode["published"])

But you’ll probably have realised we haven’t written that function yet, so back to the wordpress.py file!

Writing our posting function

This is the most complicated part of the whole project. This part is also dependent on your WordPress site having the Seriously Simple Podcasting installed.

First we define our function:

def post(title, content, audioUrl, publishDate, explicit = False):

As you can see it takes a title for our post, the content (show notes), the link we found earlier, a publish date, and a boolean on whether or not it is explicit (default is False).

So now we create a new WordPressPost object, we’ll call it post

post = WordPressPost()

Then we configure our title, content, date, and post ID.

post.title = title
post.content = content
post.id = client.call(posts.NewPost(post))
date = datetime.datetime.strptime(publishDate, '%a, %d %b %Y %H:%M:%S %Z') #We'll use this variable later
post.date = date

Now comes the tricky part, the custom_fields. For your website to look and feel like a podcast website, it needs a podcast player on each episode. That’s where the Seriously Simple Podcasting plugin comes in, but that depends on a couple of custom field being set on your post.

These fields are the audio_file, block, episode_type, explicit, and date_recorded (or 'date_published', depending on how you configure the plugin).

First initialise an empty custom_fields array for your post

post.custom_fields = []

Now for each of those above fields, append a new object to the array:

post.custom_fields.append({
	'key': 'audio_file',
	'value': audioUrl
})
post.custom_fields.append({
	'key': 'block',
	'value': ""
})
post.custom_fields.append({
	'key': 'date_recorded',
	'value': date.strftime("%d-%m-%Y")
})
post.custom_fields.append({
	'key': 'date_published',
	'value': date
})
post.custom_fields.append({
	'key': 'episode_type',
	'value': "audio"
})
post.custom_fields.append({
	'key': 'explicit',
	'value': ""
})

And now for the fun part! Set the post_status to "publish", and then post it!

client.call(posts.EditPost(post.id, post))

We’re done!

If you follow along, that should all work. Alternatively, you can go to the GitHub repository and just download the code.

If you do it all right, it should look something like this (depending on your theme):