A Go package for accessing Spotify data by scraping public pages. No authentication or API tokens required.
- π΅ Track Information - Track name, duration, album, artists, and cover art
- π€ Artist Profiles - Artist stats, biography, followers, and monthly listeners
- πΏ Album Details - Album info with track listings and artwork
- π No Authentication - Works without any API tokens or OAuth
- π Browser-Like Requests - Uses TLS fingerprinting to mimic real browsers
- β‘ Context Support - All operations support cancellation and timeouts
- π§ͺ Fully Testable - Mock support for comprehensive unit testing
- π‘οΈ Type-Safe Errors - Custom error types for precise error handling
go get github.com/FrostBreker/spotify-private-apiRequirements: Go 1.24 or higher
package main
import (
"context"
"fmt"
"log"
spotify "github.com/FrostBreker/spotify-private-api"
)
func main() {
client := spotify.NewClient()
ctx := context.Background()
// Fetch a track
track, err := client.FetchTrack(ctx, "4cOdK2wGLETKBW3PvgPWqT")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Track: %s\n", track.Data.TrackUnion.Name)
fmt.Printf("Artist: %s\n", track.Data.TrackUnion.FirstArtist.Items[0].Profile.Name)
fmt.Printf("Album: %s\n", track.Data.TrackUnion.AlbumOfTrack.Name)
}client := spotify.NewClient()
ctx := context.Background()
track, err := client.FetchTrack(ctx, "4cOdK2wGLETKBW3PvgPWqT")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Name: %s\n", track.Data.TrackUnion.Name)
fmt.Printf("URI: %s\n", track.Data.TrackUnion.URI)
fmt.Printf("Duration: %dms\n", track.Data.TrackUnion.Duration.TotalMilliseconds)
fmt.Printf("Album: %s\n", track.Data.TrackUnion.AlbumOfTrack.Name)
if len(track.Data.TrackUnion.FirstArtist.Items) > 0 {
fmt.Printf("Artist: %s\n", track.Data.TrackUnion.FirstArtist.Items[0].Profile.Name)
}client := spotify.NewClient()
ctx := context.Background()
artist, err := client.FetchArtist(ctx, "4tZwfgrHOc3mvqYlEYSvVi")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Artist: %s\n", artist.Data.ArtistUnion.Profile.Name)
fmt.Printf("Followers: %d\n", artist.Data.ArtistUnion.Stats.Followers)
fmt.Printf("Monthly Listeners: %d\n", artist.Data.ArtistUnion.Stats.MonthlyListeners)
// Access artist's avatar
if len(artist.Data.ArtistUnion.Visuals.AvatarImage.Sources) > 0 {
fmt.Printf("Avatar URL: %s\n", artist.Data.ArtistUnion.Visuals.AvatarImage.Sources[0].URL)
}client := spotify.NewClient()
ctx := context.Background()
album, err := client.FetchAlbum(ctx, "2noRn2Aes5aoNVsU6iWThc")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Album: %s\n", album.Data.AlbumUnion.Name)
fmt.Printf("URI: %s\n", album.Data.AlbumUnion.URI)
fmt.Printf("Total Tracks: %d\n", album.Data.AlbumUnion.Tracks.TotalCount)
if len(album.Data.AlbumUnion.Artists.Items) > 0 {
fmt.Printf("Artist: %s\n", album.Data.AlbumUnion.Artists.Items[0].Profile.Name)
}// Enable debug logging
client := spotify.NewClient(
spotify.WithDebug(true),
)
// Custom logger (uses log/slog)
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
client := spotify.NewClient(
spotify.WithLogger(logger),
)
// Custom HTTP client for testing
client := spotify.NewClient(
spotify.WithHTTPDoer(mockDoer),
)| Method | Parameters | Description | Returns |
|---|---|---|---|
FetchTrack() |
ctx, trackID |
Get track information | *TrackResponse |
FetchArtist() |
ctx, artistID |
Get artist profile | *ArtistResponse |
FetchAlbum() |
ctx, albumID |
Get album details | *AlbumResponse |
| Option | Description |
|---|---|
WithDebug(bool) |
Enable/disable debug logging |
WithHTTPDoer(Doer) |
Use custom HTTP doer (for testing) |
WithLogger(*slog.Logger) |
Use custom structured logger |
import "github.com/FrostBreker/spotify-private-api/errors"
track, err := client.FetchTrack(ctx, trackID)
if err != nil {
if errors.IsAPIError(err) {
fmt.Println("API returned an error")
}
if errors.IsParseError(err) {
fmt.Println("Failed to parse response")
}
if err == errors.ErrInvalidID {
fmt.Println("Invalid Spotify ID provided")
}
}| Error Type | Description |
|---|---|
APIError |
Spotify returned an error response |
ParseError |
JSON parsing or processing error |
RequestError |
HTTP request failed |
| Error | Description |
|---|---|
ErrInvalidID |
Invalid Spotify ID provided |
ErrEmptyResponse |
Empty response received |
spotify-private-api/
βββ client.go # Main client implementation
βββ track.go # Track operations
βββ artist.go # Artist operations
βββ album.go # Album operations
βββ doc.go # Package documentation
βββ errors/
β βββ errors.go # Custom error types
βββ internal/
β βββ http/
β βββ http.go # Browser-like HTTP client
βββ models/
β βββ track.go # Track response types
β βββ artist.go # Artist response types
β βββ album.go # Album response types
βββ example/
βββ main.go # Usage examples
// Create a mock HTTP client
type mockHTTPClient struct {
doFunc func(req *http.Request) (*http.Response, error)
}
func (m *mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
return m.doFunc(req)
}
// Use in tests
client := spotify.NewClient(
spotify.WithHTTPDoer(&mockHTTPClient{
doFunc: func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: 200,
Body: io.NopCloser(strings.NewReader(mockHTML)),
}, nil
},
}),
)Run tests:
go test ./...
go test -v ./...
go test -cover ./...This package scrapes Spotify's public web pages to extract embedded data. Spotify embeds JSON data in a <script id="initialState"> tag as base64-encoded content. The package:
- Makes HTTP requests with browser-like TLS fingerprints (using CycleTLS)
- Parses the HTML response to find the embedded data
- Decodes and unmarshals the JSON into typed Go structs
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
See CONTRIBUTING.md for detailed guidelines.
This project is licensed under the MIT License - see the LICENSE.MD file for details.
- This package is for educational and research purposes only
- The page structure may change without notice, potentially breaking this package
- Excessive use may result in rate limiting or IP blocks
- Not affiliated with or endorsed by Spotify
Happy Listening! π§