fix: OG scrape fallback — parse JSON-LD first, fix artist field index
JSON-LD byArtist gives the correct artist list. As secondary fallback, OG desc format is 'Artist · Song · Year' not 'Song · Artist · Album', so use desc_parts[0] not desc_parts[1] for artist. Also handle both attribute orderings in meta tags. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
d785a4c09a
commit
98ad8ed579
1 file changed
+35
-9
@@ -84,22 +84,48 @@ def resolve(sp_type, sp_id):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
eprint(f'[spotapi] song_info({arg!r}) failed: {e}')
|
eprint(f'[spotapi] song_info({arg!r}) failed: {e}')
|
||||||
|
|
||||||
# Fallback: scrape Open Graph tags from the public Spotify track page
|
# Fallback: scrape the public Spotify track page
|
||||||
if not tr:
|
if not tr:
|
||||||
eprint(f'[spotapi] falling back to OG scrape for track {sp_id}')
|
eprint(f'[spotapi] falling back to page scrape for track {sp_id}')
|
||||||
try:
|
try:
|
||||||
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
|
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
|
||||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||||
page = resp.read().decode('utf-8', errors='replace')
|
page = resp.read().decode('utf-8', errors='replace')
|
||||||
og = {m.group(1): _html.unescape(m.group(2))
|
|
||||||
for m in re.finditer(r'<meta\s+property="og:([^"]+)"\s+content="([^"]*)"', page)}
|
track_name, artist_name, album_name, art_url = '', '', '', ''
|
||||||
# og:description format: "Song · Artist · Album · Year"
|
|
||||||
|
# 1) JSON-LD structured data — most reliable, always has byArtist
|
||||||
|
ld_m = re.search(r'<script[^>]+type=["\']application/ld\+json["\'][^>]*>(.*?)</script>', page, re.DOTALL | re.IGNORECASE)
|
||||||
|
if ld_m:
|
||||||
|
try:
|
||||||
|
ld = json.loads(ld_m.group(1))
|
||||||
|
track_name = ld.get('name', '')
|
||||||
|
by_artist = ld.get('byArtist', [])
|
||||||
|
if isinstance(by_artist, list):
|
||||||
|
artist_name = ', '.join(a.get('name', '') for a in by_artist if a.get('name'))
|
||||||
|
elif isinstance(by_artist, dict):
|
||||||
|
artist_name = by_artist.get('name', '')
|
||||||
|
in_album = ld.get('inAlbum') or {}
|
||||||
|
album_name = in_album.get('name', '') if isinstance(in_album, dict) else ''
|
||||||
|
eprint(f'[spotapi] JSON-LD: name={track_name!r} artist={artist_name!r} album={album_name!r}')
|
||||||
|
except Exception as e:
|
||||||
|
eprint(f'[spotapi] JSON-LD parse failed: {e}')
|
||||||
|
|
||||||
|
# 2) OG tags — artist is desc_parts[0], then type ("Song"), then year
|
||||||
|
if not track_name:
|
||||||
|
og = {}
|
||||||
|
for m in re.finditer(r'<meta\s+(?:property|name)=["\']og:([^"\']+)["\']\s+content=["\']([^"\']*)["\']', page):
|
||||||
|
og[m.group(1)] = _html.unescape(m.group(2))
|
||||||
|
for m in re.finditer(r'<meta\s+content=["\']([^"\']*)["\'][^>]+(?:property|name)=["\']og:([^"\']+)["\']', page):
|
||||||
|
og[m.group(2)] = _html.unescape(m.group(1))
|
||||||
desc_parts = [p.strip() for p in og.get('description', '').split('·')]
|
desc_parts = [p.strip() for p in og.get('description', '').split('·')]
|
||||||
track_name = og.get('title', '')
|
track_name = og.get('title', '')
|
||||||
artist_name = desc_parts[1] if len(desc_parts) > 1 else ''
|
# Real format: "Artist · Song · Year · N songs." — artist is index 0
|
||||||
album_name = desc_parts[2] if len(desc_parts) > 2 else ''
|
artist_name = desc_parts[0] if desc_parts else ''
|
||||||
|
album_name = ''
|
||||||
art_url = og.get('image', '')
|
art_url = og.get('image', '')
|
||||||
eprint(f'[spotapi] OG scrape: name={track_name!r} artist={artist_name!r} album={album_name!r}')
|
eprint(f'[spotapi] OG scrape: name={track_name!r} artist={artist_name!r}')
|
||||||
|
|
||||||
if track_name:
|
if track_name:
|
||||||
tr = {
|
tr = {
|
||||||
'trackName': track_name,
|
'trackName': track_name,
|
||||||
@@ -112,7 +138,7 @@ def resolve(sp_type, sp_id):
|
|||||||
'durationMs': None,
|
'durationMs': None,
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
eprint(f'[spotapi] OG scrape failed: {e}')
|
eprint(f'[spotapi] page scrape failed: {e}')
|
||||||
|
|
||||||
if not tr:
|
if not tr:
|
||||||
raise Exception(f'Track resolution failed: song_info returned GenericError and OG scrape found no title')
|
raise Exception(f'Track resolution failed: song_info returned GenericError and OG scrape found no title')
|
||||||
|
|||||||
Reference in new issue
Block a user