The Spotify Web API can return over a dozen audio features for a track, notably tempo - "The overall estimated tempo of a track in beats per minute (BPM)."

Given a Spotify ID, Spotipy's audio_features method can be called as follows:

"""
Artist: Above & Beyond, Richard Bedford
Track: Sun & Moon
Track link: https://open.spotify.com/track/2CG1FmeprsyjgHIPNMYCf4
Track ID: 2CG1FmeprsyjgHIPNMYCf4
"""
sun_and_moon_id = '2CG1FmeprsyjgHIPNMYCf4'
audio_features = sp.audio_features(sun_and_moon_id)
print(json.dumps(audio_features, indent=2))
[
  {
    "danceability": 0.691,
    "energy": 0.522,
    "key": 6,
    "loudness": -8.024,
    "mode": 0,
    "speechiness": 0.0908,
    "acousticness": 0.0216,
    "instrumentalness": 0.0141,
    "liveness": 0.125,
    "valence": 0.187,
    "tempo": 133.995,
    "type": "audio_features",
    "id": "2CG1FmeprsyjgHIPNMYCf4",
    "uri": "spotify:track:2CG1FmeprsyjgHIPNMYCf4",
    "track_href": "https://api.spotify.com/v1/tracks/2CG1FmeprsyjgHIPNMYCf4",
    "analysis_url": "https://api.spotify.com/v1/audio-analysis/2CG1FmeprsyjgHIPNMYCf4",
    "duration_ms": 326267,
    "time_signature": 4
  }
]

Nice! Looks like the "tempo", or BPM, of this track is around 133. Let's continue.

Conveniently, the entire back catalogue of A State of Trance - 950+ episodes - has been uploaded to Spotify under the artist "Armin van Buuren ASOT Radio". Spotipy's artist_albums method can list them for us, courtesy spotipy/examples/artist_albums.py:

"""
Artist: Armin van Buuren ASOT Radio
Artist link: https://open.spotify.com/artist/25mFVpuABa9GkGcj9eOPce
Artist ID: 25mFVpuABa9GkGcj9eOPce
"""

asot_radio_id = '25mFVpuABa9GkGcj9eOPce'

albums = []
results = sp.artist_albums(asot_radio_id, album_type='album')
albums.extend(results['items'])
while results['next']:
    results = sp.next(results)
    albums.extend(results['items'])
seen = set()  # to avoid dups
for album in albums:
    name = album['name']
    if name not in seen:
        seen.add(name)

albums.sort(key=lambda x: x['release_date']) # Sort by release date

Cool, our list albums should now contain every episode of A State of Trance! Let's take a quick look..

for album in albums[:10]:
    print(album['name'])
A State Of Trance Episode 001
A State Of Trance Episode 003
A State Of Trance Episode 004
A State Of Trance Episode 005
A State Of Trance Episode 007
A State Of Trance Episode 008
A State Of Trance Episode 009
A State Of Trance Episode 010
A State Of Trance Episode 012
A State Of Trance Episode 015

Hm, aren't we missing a few?

len(albums)
935

For some reason 25 early episodes are classified as "Singles and EPs". Let's grab those as well, and add them to the list.

"""
Artist: Armin van Buuren ASOT Radio
Artist link: https://open.spotify.com/artist/25mFVpuABa9GkGcj9eOPce
Artist ID: 25mFVpuABa9GkGcj9eOPce
"""

asot_radio_id = '25mFVpuABa9GkGcj9eOPce'

singles = []
results = sp.artist_albums(asot_radio_id, album_type='single')
singles.extend(results['items'])
while results['next']:
    results = sp.next(results)
    singles.extend(results['items'])
seen = set()  # to avoid dups
for single in singles:
    name = single['name']
    if name not in seen:
        seen.add(name)

episodes = singles + albums

episodes.sort(key=lambda x: x['release_date']) # Sort by release date

for episode in episodes[:10]:
    print(episode['name'])
A State Of Trance Episode 000
A State Of Trance Episode 001
A State Of Trance Episode 002
A State Of Trance Episode 003
A State Of Trance Episode 004
A State Of Trance Episode 005
A State Of Trance Episode 006
A State Of Trance Episode 007
A State Of Trance Episode 008
A State Of Trance Episode 009

Nice!

len(episodes)
960

Great, that's every available episode as of writing. Let's see what we can do with all this, starting with a tracklist courtesy of Spotipy's album_tracks method:

for track in sp.album_tracks(episodes[1]['uri'])['items']:
    print(track['artists'][0]['name'], '-', track['name'])
Armin van Buuren - A State Of Trance [ASOT 001] - Intro
Liquid DJ Team - Liquidation [ASOT 001] - Marco V Mix
The Ultimate Seduction - The Ultimate Seduction [ASOT 001] **ASOT Radio Classic** - Original Mix
System F - Exhale [ASOT 001] - Ferry Corsten & Armin van Buuren New Mix
Rising Star - Clear Blue Moon [ASOT 001] - Original Mix
Ralphie B - Massive [ASOT 001] - Original Mix
Rank 1 - Such is Life [ASOT 001] - Original Mix
Armin van Buuren - Blue Fear [ASOT 001] - Original Mix
Armin van Buuren - A State Of Trance [ASOT 001] - Outro

Seems most of the early episodes are missing a bunch of tracks unfortunately, A State of Trance's website reports twice as many tracks in this episode and we'll want to remove the intro and outro as well.

Looking at a more recent episode:

for track in sp.album_tracks(episodes[945]['uri'])['items']:
    track_artist = track['artists'][0]['name']
    for artist in track['artists'][1:]:
        track_artist += " & " + artist['name']
    print(track_artist, '-', track['name'])
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Intro
Armin van Buuren - Let The Music Guide You (ASOT 950 Anthem) [ASOT 950 - Part 1]
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Coming Up, Pt. 1
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Service For Dreamers Special, Pt. 1
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - ASOT 950 Event, Pt. 1
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Alana Katherine & Reanna Parsons from Canada, Pt. 1
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Alana Katherine & Reanna Parsons from Canada, Pt. 2
Tritonal & Jeza - I Can Breathe (ASOT 950 - Part 1) - Tritonal Club Mix
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Codrin Lustin from Romania
Super8 & Tab - Nino (ASOT 950 - Part 1)
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Jaime Oliveira from Portugal
Mark Knight & D. Ramirez & Underworld & Armin van Buuren - Downpipe (ASOT 950 - Part 1) - Armin van Buuren Remix
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by James Holloway from the UK
Oliver Smith - Chordplay (ASOT 950 - Part 1)
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Ilan Bluestone & Maor Levi, Pt. 1
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Ilan Bluestone & Maor Levi, Pt. 2
Armin van Buuren & Sharon Den Adel & Ilan Bluestone & Maor Levi - In And Out Of Love (ASOT 950 - Part 1) - ilan Bluestone & Maor Levi Remix
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Raymundo from Canada
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Coming Up, Pt. 2
Assaf & Cassandra Grey - All Of You (ASOT 950 - Part 1)
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Rafael from The Philippines
Jorn Van Deynhoven - New Horizons (A State Of Trance 650 Anthem) [ASOT 950 - Part 1]
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Michiel & Veronique from The Netherlands
Ferry Corsten & Betsie Larkin - Made Of Love (ASOT 950 - Part 1)
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Pedro Soussa from Portugal, Pt. 1
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Pedro Soussa from Portugal, Pt. 2
Eco - A Million Sounds, A Thousand Smiles (ASOT 950 - Part 1)
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Edwin and Susana from Canada
Dennis Sheperd & Cold Blue & Ana Criado - Fallen Angel (ASOT 950 - Part 1) - Dennis Sheperd Club Mix
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Abdul Rahman from Canada
Omnia & Everything By Electricity - Bones (ASOT 950 - Part 1)
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Thank You!
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Track Recap, Pt. 1
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Kyle Assue from Trinidad and Tobago, Pt. 1
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Kyle Assue from Trinidad and Tobago, Pt. 2
Armin van Buuren - The Train (ASOT 950 - Part 1)
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Track Recap, Pt. 2
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Stay Tuned For More
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Maxim from Germany
Armin van Buuren & Shapov - The Last Dancer (ASOT 950 - Part 1)
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Mauricio from Brazil
HALIENE & Ruben de Ronde - Dream In Color (ASOT 950 - Part 1) - Ruben de Ronde Remix
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Tita from Canada
Armin van Buuren & Avian Grays & Jordan Shaw - Something Real (ASOT 950 - Part 1)
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Mr. and Mrs. ReOrder from Czech Republic
Above & Beyond & Richard Bedford - Sun & Moon (ASOT 950 - Part 1) - Club Mix
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Jeffrey from the USA
Armin van Buuren & Fiora & Beat Service - Waiting For The Night (ASOT 950 - Part 1) - Beat Service Remix
Armin van Buuren - A State Of Trance (ASOT 950 - Part 1) - Requested by Damian Panek from Poland
Shogun - Laputa (ASOT 950 - Part 1)

The more recent episodes feature a Spotify exclusive - voiceover interludes! Seems they all contain "A State of Trance" though, same with the regular intros and outros.

Without them:

episode_tracks = sp.album_tracks(episodes[945]['uri'])['items']
pruned_tracks = []

for track in episode_tracks:
    if "a state of trance" in track['name'].lower() or "- interview" in track['name'].lower():
        continue
    else:
        pruned_tracks.append(track)
        track_artist = track['artists'][0]['name']
        for artist in track['artists'][1:]:
            track_artist += " & " + artist['name']
        print(track_artist, '-', track['name'])
Armin van Buuren - Let The Music Guide You (ASOT 950 Anthem) [ASOT 950 - Part 1]
Tritonal & Jeza - I Can Breathe (ASOT 950 - Part 1) - Tritonal Club Mix
Super8 & Tab - Nino (ASOT 950 - Part 1)
Mark Knight & D. Ramirez & Underworld & Armin van Buuren - Downpipe (ASOT 950 - Part 1) - Armin van Buuren Remix
Oliver Smith - Chordplay (ASOT 950 - Part 1)
Armin van Buuren & Sharon Den Adel & Ilan Bluestone & Maor Levi - In And Out Of Love (ASOT 950 - Part 1) - ilan Bluestone & Maor Levi Remix
Assaf & Cassandra Grey - All Of You (ASOT 950 - Part 1)
Ferry Corsten & Betsie Larkin - Made Of Love (ASOT 950 - Part 1)
Eco - A Million Sounds, A Thousand Smiles (ASOT 950 - Part 1)
Dennis Sheperd & Cold Blue & Ana Criado - Fallen Angel (ASOT 950 - Part 1) - Dennis Sheperd Club Mix
Omnia & Everything By Electricity - Bones (ASOT 950 - Part 1)
Armin van Buuren - The Train (ASOT 950 - Part 1)
Armin van Buuren & Shapov - The Last Dancer (ASOT 950 - Part 1)
HALIENE & Ruben de Ronde - Dream In Color (ASOT 950 - Part 1) - Ruben de Ronde Remix
Armin van Buuren & Avian Grays & Jordan Shaw - Something Real (ASOT 950 - Part 1)
Above & Beyond & Richard Bedford - Sun & Moon (ASOT 950 - Part 1) - Club Mix
Armin van Buuren & Fiora & Beat Service - Waiting For The Night (ASOT 950 - Part 1) - Beat Service Remix
Shogun - Laputa (ASOT 950 - Part 1)

Much better! Finally, for fun, let's track this episode's BPM over time using some visualization libraries:

import altair as alt
import numpy as np
import pandas as pd
bpm = []
for track in pruned_tracks:
    bpm.append(sp.audio_features(track['uri'])[0]['tempo'])

x = np.arange(len(pruned_tracks))   

source = pd.DataFrame({
  'track': x,
  'bpm': np.array(bpm)
})

alt.Chart(source).mark_line().encode(
    alt.X('track'),
    alt.Y('bpm', scale=alt.Scale(domain=(120, 150))),
).properties(
    title="ASOT 950 Part 2 - BPM of track"
)

Not great, but it gets the point across.

Now, let's get exploring! ..