Bleeper - Downloading Full-Length Preview MP3s from bleep.com

by Derek Kurth

Bleep is an online store for indie music, especially electronic music.

On every album's page on the site, you can listen to the entire album in 30-second increments.  To accomplish this, Bleep is actually sending the entire song's MP3 to your browser, then using JavaScript to cut you off after 30 seconds.

By monitoring HTTP requests in your browser, you can see how the entire MP3 is being downloaded to your local cache.

Note that these MP3s are lower quality mono recordings.

Bleep sells much better lossless (or high bit-rate MP3) versions on the site.

Every album has a URL that looks like this:

https://bleep.com/release/55391-jon-hopkins-asleep-versions

Note the numeric ID 55391.  We'll use that in a minute.

Load that page in Chrome, then open the Developer tools (from the "Chrome menu > More tools > Developer tools").

Click the "Network" tab in the Developer tools, then click the play button next to the first song.  You will see a few network requests, but the important one has this URL:

https://bleep.com/player/resolve/55391-1-1

Notice how this URL includes that same album ID 55391.  The response to that request is just a URL that looks like this:

http://preview.bleep.com/f1fd2b6a-de5f-3d28-b061-87fe8a12f892-01-001.mp3

That is the URL for the entire preview MP3!

It looks like Bleep assigns a UUID (in this case, f1fd2b6a-de5f-3d28-b061-87fe8a12f892) to every album, and the number at the end corresponds to the track number.

So, if you want the MP3 for the album's second track, just change the 001 at the end to 002, and so on for the other tracks.  (I could not figure out what the 01 between that UUID and the track number is for, though.)

At this point, if you start downloading previews, you'll quickly realize that:

  • It's tedious.
  • The filenames are those inscrutable UUIDs.

So, we'll write a Python script for pulling down an entire album, naming the files using the correct artist, album, and track names.  (N.B., all the code in this article was written for Python version 3.4.)

We'll create two classes: BleepAlbum and BleepTrack.

A BleepAlbum has an ID, artist, title, and a list of tracks.  Each track in the list will be a BleepTrack, which has an URL, an index (the track number within the album), and a download method.

Also, since we don't want to type in the artist name, album title, and track titles, we'll scrape them from the album page's HTML.

When we're done, we'll be able to download the preview MP3s for an entire album with just three lines of Python, like this:

album = BleepAlbum(55391)
for track in album.tracks:
    track.download()

We are going to use two Python libraries that you might not have: Beautiful Soup and Requests

You might need to run pip install beautifulsoup4 and pip install requests before these imports will work.

Here is the full code for pulling down an album.

I left out the code to catch exceptions and to accept an album ID from the command line - those are "left as an exercise to the reader," as they say.

But if you save this as bleeper.py, set the album_id variable (near the bottom) to whatever album you want to download, and run: python bleeper.py

It should work.  If Bleep changes the structure of the album page, the parsing for the titles will need to change, also.

#!/usr/bin/python3

import requests
import re
import os
from bs4 import BeautifulSoup

class BleepTrack:
 def __init__(self, album, title, index):
   self.album = album
   self.title = title
   i = str(index)
   self.padded_index = "0" * (3-len(i)) + i # turn "5" into "005"

 @property
 def url(self):
   return self.album.url_prefix + self.padded_index + '.mp3'

 def download(self, dest_dir="."):
   r = requests.get(self.url, stream=True)
   dest_file = os.path.join(dest_dir, self.padded_index + " - " +
self.title) + ".mp3"
   with open(dest_file, 'wb') as f:
     for chunk in r.iter_content(chunk_size=1024):
       if chunk: # filter out keep-alive new chunks
         f.write(chunk)
         f.flush()

class BleepAlbum:

 def __init__(self, release_id):
   self.release_id = release_id
   album_url = "https://bleep.com/release/{}".format(self.release_id)
   r = requests.get(album_url)
   self.soup = BeautifulSoup(r.text)

 @property
 def artist(self):
   details = self.soup.find("div", "product-details")
   artist = details.find(itemprop="name")
   return artist.text

 @property
 def title(self):
   details = self.soup.find("div", "product-details")
   album_wrapper = details.find("dt", "release-title")
   title_elt = album_wrapper.find("a")
   return title_elt.text

 @property
 def tracks(self):
   tracks = []
   index = 1
   for span in self.soup.find_all("span", "track-name"):
     title = span.find("a").attrs['title']
     title = re.sub("^Play '(.*)'", r"\1", title)
     track = BleepTrack(self, title, index)
     tracks.append(track)
     index += 1
   return tracks

 @property
 def url_prefix(self):
   try:
     return self._url_prefix
   except:
     pass
   get_preview_url = 'https://bleep.com/player/resolve/{}-1-1'.format(self.release_id)
   r = requests.get(get_preview_url)
   preview_url = r.text
   match = re.search("(^http://.*?-01-)\d+.mp3", preview_url)
   self._url_prefix = match.group(1)
   return self._url_prefix

if __name__ == "__main__":
 album = BleepAlbum(55082)

 # This creates a directory like "Artist name/Album title", and saves the tracks there.

 save_path = os.path.join(album.artist, album.title)
 try:
   os.makedirs(save_path)
 except FileExistsError:
   pass
 for track in album.tracks:
   print(track.title)
   track.download(save_path)

Code: bleeper.py

Return to $2600 Index