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/:
metalarchives.py— full API tourbrowse.py— catalog walks (country / genre / letter / labels / reviews / upcoming / RIP)portuguese_heavy_metal_pre2000.py— resumable lyrics-corpus crawlmetallvm-rest.py— FastAPI server exposing every endpoint over HTTP
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
- Getting Started — install + first commands
- API Reference — every public class + method
- Advanced Usage — splits, lineups over time, pagination, caching, lyrics download
- Developer Guide — adding endpoints, capturing fixtures, running tests
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)
| File | Size | Uploaded | |
|---|---|---|---|
| pymetal-1.0.1.tar.gz | 39.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|