Skip to main content

pymetal

A Python client for Encyclopaedia Metallum (the Metal Archives) with a relational data model that captures what flat scrapers lose: splits, lineups changing over time, and tracks reused across releases.

Built on curl_cffi for TLS-fingerprint bypass and pydantic for typed, validated data.

Why

Most scrapers model Track = (id, title, band, album). That collapses three independent facts MA keeps separate:

  • a track may have multiple bands (split releases, collaborations);
  • a band's lineup is time-sliced — "the same band" on two tracks may mean different humans;
  • a track may appear on many releases (compilations, re-issues, singles).

pymetal models each as a first-class entity (TrackAppearance, LineupMember, ReleaseLineup) keyed by metal-archives ids so re-scrapes are idempotent.

Install

pip install -e .

Requires Python 3.10+. Pulls curl_cffi, lxml, pydantic>=2, random-user-agent.

Quick start

from pymetal import MetalArchives

ma = MetalArchives()

# Search bands with the full advanced-search filter set.
for hit in ma.search_bands(country="PT", genre="Heavy", year_from=1980, year_to=1989):
    print(hit.ma_id, hit.name, hit.country)

# Pull a release with all its tracks (per-band attribution on splits).
release, songs, appearances = ma.get_release(451600)  # Carcass — Heartwork
print(release.cover_url, release.total_length, release.label_name)
print(release.reviews_count, "reviews", release.reviews_avg_percent, "% avg")

# Lineup over time — Current / Past / Live / Last-known / Guest-Session.
for member in ma.get_lineup(14):
    print(member.status, member.artist_name, member.role, member.date_from, member.date_to)

# Lyrics by song id.
print(ma.get_lyrics_by_song_id(172090))

More examples in examples/:

Endpoints

Search (advanced-search forms)

Function What it returns
search_bands(...) Iterator[BandSearchHit] — every advanced filter (country, status, year range, themes, location, label)
search_albums(...) Iterator[AlbumSearchHit] — release type, format, label, catalog/barcode, year+month range
search_songs(...) Iterator[SongSearchHit] — full-text lyrics search; carries band_id / release_id / lyrics_id

Detail pages

Function What it returns
get_band(id) Band — name, country, genres, themes, labels, comment, photo, audit
get_lineup(id) List[LineupMember] — partitioned by status with role-date ranges
get_release(id) (Release, List[Song], List[TrackAppearance]) — splits attribute per-band
get_release_lineup(id) List[ReleaseLineup] — band / guest / staff credits
get_other_versions(id) List[Release] — re-issues, re-masters, regional editions
get_discography(id) List[Release]
get_artist(id) Artist — real name, born, R.I.P., died of, place of birth
get_label(id) Label — address, phone, styles, founding date, sub-labels, parent
get_band_recommendations(id) List[BandRecommendation] — MA's "Similar artists" tab
get_band_reviews(id) Iterator[Review] — every user review of every release by a band
get_links(id, entity_type='band') List[ExternalLink] — Bandcamp/Spotify/merch grouped by section
get_lyrics_by_song_id(id) Optional[str]
get_lyrics(...) Iterator[str] — combined search + lyrics fetch

Catalog browse + discovery

Function What it returns
browse_bands_by_country(code) Iterator[BandSearchHit] — full country listing
browse_bands_by_genre(slug) Iterator[BandSearchHit] — 23-bucket coarse taxonomy
browse_bands_by_letter(letter) Iterator[BandSearchHit] — alphabetical (A–Z, NBR, ~)
browse_labels_by_country(code) Iterator[Label]
browse_labels_by_letter(letter) Iterator[Label]
browse_reviews(year, month) Iterator[Review] — reviews posted in a given month
get_upcoming_releases() Iterator[UpcomingRelease] — scheduled future releases
get_rip_artists() Iterator[RIPArtist] — MA's deceased-artists list
list_countries() dict[code, name] — all MA-known country codes
list_genre_slugs() list[str] — the 23 genre browse slugs

All return Pydantic v2 models — .model_dump_json() round-trip works on every type.

Documentation

License

Apache 2.0

Metadata

Release files for pymetal 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pymetal 1.0.1
File Size Uploaded
pymetal-1.0.1.tar.gz 39.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pymetal 1.0.1
File Interpreter ABI Platform
pymetal-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 73.5 kB

Release files / pymetal-1.0.1.tar.gz

Download URL pymetal-1.0.1.tar.gz
Size 39.9 kB
Tags Source
SHA-256 checksum
How to use checksums
7e91bda32bed76b96530db77092b03e45ca01bdd802e32bd87893089ba81fc31
BLAKE2b-256 checksum
How to use checksums
83522b86af6f592c3110396de7bb84312ec53bcc87825e59b690df9349092be1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.12

Release files / pymetal-1.0.1-py3-none-any.whl

Download URL pymetal-1.0.1-py3-none-any.whl
Size 33.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
69d13fa0f0bda3756c46f7684cef3dbda68ffcffc380f80d98d9bf6710950e2a
BLAKE2b-256 checksum
How to use checksums
f8d8303cc1331dbfd8ec2e2389d3c616aeb766152a96e6a8ba12d4c2a1fdc6af
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.12
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page