SpotifyScraper

repository·master·Indexed 43 days ago

https://github.com/aliakhtari78/spotifyscraper

A Python library for extracting public Spotify metadata—including tracks, albums, artists, playlists, podcasts, and lyrics—without requiring an official API key or OAuth flow. It provides both synchronous (SpotifyClient) and asynchronous (AsyncSpotifyClient) interfaces, a CLI tool, and an MCP server for LLM hosts. Features include support for downloading cover art and 30-second audio previews, localized display names, and browser-assisted login for accessing restricted content like lyrics and transcripts.

Tokens
70.5K
Snippets
234
Records
344
Agent score
52%

What's inside SpotifyScraper

  1. Handle library errors using the SpotifyScraperError hierarchy

    master

    All errors raised by the library derive from a single base class: SpotifyScraperError. To catch any error produced by the library, you should catch this base exception.

    Specific error subtypes include:

    • URLError: Raised when input is not a valid Spotify URL, URI, or ID.
    • NetworkError: Raised during transport failures (wraps underlying httpx errors).
    • RateLimitedError: A subtype of NetworkError that includes a retry_after: float | None attribute to indicate how long to wait before retrying.
    • TokenError: Raised when anonymous token bootstrapping or refreshing fails.
    • AuthenticationError: Raised when user credentials are missing or expired for authenticated features.
    • NotFoundError: Raised when an entity does not exist or is unavailable.
    • ParsingError: Raised if the Spotify payload shape has changed.
    • MediaError: Raised during download or tagging failures.
    try:
        # Perform SpotifyScraper operation
        pass
    except SpotifyScraperError as e:
        # This catches all library-specific errors
        print(f"An error occurred: {e}")
    except RateLimitedError as e:
        # Specific handling for rate limits
        if e.retry_after:
            print(f"Retrying after {e.retry_after} seconds")
  2. How preview URLs are handled during track extraction

    master

    Because the Tier 1 (GraphQL) fetch does not provide preview audio, the extraction process is designed to ensure preview_url is populated.

    When get_track(value) is called, the implementation must ensure that track.preview_url is populated if Spotify provides one. This is achieved by reading the embed entity payload. Even when using Tier 1, the system may fetch the embed page (either lazily or eagerly) to ensure that track.preview_url contains a valid p.scdn.co URL instead of None for playable tracks.

  3. Handle nullability in models (Tier 1 vs Tier 2 data)

    master

    Models follow a two-tier nullability contract based on the data source:

    1. Tier 2 (Embed Page Data): Fields obtainable directly from the embed page are non-optional.
    2. Tier 1 (Pathfinder API Data): Fields that require the pathfinder API are typed as optional (| None) and default to None.

    When constructing a model (e.g., a Track) using only embed data, construction will succeed, but Tier 1-only fields like play_count or track_number will be set to None.

  4. Understand the data model immutability and structure

    master

    The project uses frozen, slotted dataclasses for all entity models and value objects. This ensures that once a model is constructed, it cannot be modified.

    Entity Models: Track, Album, Artist, Playlist, Episode, Show, Lyrics. Value Objects: Image, ArtistRef, AlbumRef, ShowRef, UserRef, PlaylistTrack, LyricsLine.

    If you attempt to assign a new value to a field of an existing model instance, the system will raise a dataclasses.FrozenInstanceError.

  5. Handle partial failures in batch requests

    master

    Batch operations are designed to be resilient: a single malformed or invalid input will not abort the entire batch. Instead, the failure is captured within a specific BatchItem.

    To process results, check item.ok. For each item:

    • item.ok: True if the fetch succeeded.
    • item.result: The typed model on success (otherwise None).
    • item.error: The captured SpotifyScraperError on failure (otherwise None).
    • item.value: The original input value (echoed back to allow correlation).
    • item.unwrap(): A method that returns the model or re-raises the captured error if the fetch failed.
    # Extracting successful results and failed errors
    ok = [i.result for i in items if i.ok]
    failed = {i.value: i.error for i in items if not i.ok}
  6. Handle tier-1 to embed fallback degradation

    master

    SpotifyScraper uses a two-tier extraction ladder. If the rich tier-1 GraphQL payload fails (raises a ParsingError), the library automatically catches it and falls back to the entity's embed page.

    Key implications for developers:

    • Partial Data: A successful get_* call might return a model where tier-1-only fields are None or empty. Always guard these fields (e.g., if track.play_count is not None:).
    • Logging: Degradation is logged as a WARNING on the spotify_scraper logger. It does not break your execution flow.
    • When errors propagate: A ParsingError will only reach your code if both the tier-1 and the embed fallback fail. In this case, you should update the library.
    import logging
    logging.basicConfig(level=logging.WARNING)
    # A tier-1 -> embed fallback will log:
    # WARNING spotify_scraper: Tier-1 track fetch degraded to embed page: ...
  7. Use AsyncSpotifyClient for asynchronous parity

    master
    The library maintains strict parity between synchronous and asynchronous operations. For every public data method available on SpotifyClient, there is an identically-named, awaitable counterpart on AsyncSpotifyClient. Both clients return the same model types, ensuring that logic can be easily ported between sync and async environments.
  8. Error handling and rate limiting with browser transport

    master

    Error Types

    The browser transport raises the same standard library errors as the default HTTP transport:

    • NotFoundError: Returned on 404 responses.
    • RateLimitedError: Returned on 429 responses.
    • NetworkError: Raised on any Playwright driver failure.

    Rate Limiting Warning

    The built-in token-bucket rate limiter and retry logic are features of the HTTP transport. When using PlaywrightTransport, these built-in rate_limit and retry knobs do not apply. You must manage your request volume manually to avoid being blocked.

  9. Manage the client lifecycle and avoid SpotifyScraperError

    master

    Clients follow a deterministic lifecycle and must be closed to release resources. You should use them as context managers (with or async with) or call close() (sync) / aclose() (async) explicitly.

    Warning: Attempting to call any method on a client after it has been closed will raise a SpotifyScraperError.

    # Recommended: Context manager usage
    with SpotifyClient() as client:
        client.get_track(url)
    # Client is automatically closed here
    
    # Manual usage
    client = SpotifyClient()
    client.get_track(url)
    client.close()
    
    # This will raise SpotifyScraperError
    client.get_track(url)
  10. How SpotifyScraper v3 works

    master

    Unlike older versions that parsed HTML, v3 uses a two-tier strategy for reliability:

    1. Primary Method: It fetches a public Spotify embed page, extracts an anonymous access token from the __NEXT_DATA__ JSON, and queries Spotify's pathfinder GraphQL API for rich, structured data.
    2. Fallback Method: If the GraphQL query fails (e.g., due to a query hash rotation), the library gracefully degrades to parsing the data directly from the embed page's own content.

    This ensures that core fields are still returned even if the primary API path changes.

  11. Manage persistent sessions with `save_session` and `load_session`

    master

    You can persist the captured sp_dc cookie to a local JSON file for password-free future use.

    Session Storage Details:

    • Permissions: On POSIX systems, session files are created with 0600 (owner-only) permissions to ensure security. If a file is found with insecure permissions (group/world-readable), load_session will refuse to load it and raise a SessionError.
    • Config Directory: The library resolves the storage path using SPOTIFYSCRAPER_CONFIG_DIR, then XDG_CONFIG_HOME, then OS defaults.
    • Atomicity: Files are written atomically to prevent world-readable windows during writes.
    • Revocation: Use clear_session() to remove the session file. This call is idempotent.

    Error Handling:

    • SessionError is raised if the session file is missing, malformed, or insecurely permissioned.
  12. How the agent autonomy ladder works

    master

    The agent's autonomy is managed via an 'autonomy ladder' where capabilities are increased incrementally based on proven performance. Autonomy is treated as a dial rather than a binary switch. Each level (rung) defines what the agent is permitted to do and what the human's role becomes. Moving up a rung requires meeting specific evidence-based criteria (the 'entry gate').

    Current Rung: 0 — Observe

    • Agent capability: Read breakage issues, reproduce on the anonymous path, and comment a diagnosis.
    • Human role: Performs the actual fix.
    • Climb criteria: Diagnoses must be correct across approximately 5 real cycles.