"""Spotify monthly listeners parser.""" import json import requests from requests.exceptions import HTTPError from bs4 import BeautifulSoup class ParsingError(Exception): def __init__(self, details): self.details = f'Failed to parse Spotify response: {details}' def __str__(self): return self.details def handler(event: dict, context: dict) -> dict: """Lambda entry point.""" try: status_code = 200 spotify_id = event.get('pathParameters').get('artist_id') if not spotify_id: raise ValueError('Error, empty spotify artist_id') listeners = get_spotify_monthly_listeners(spotify_id) response_str = {'monthly_listeners': listeners} except ValueError as e: status_code = 400 response_str = str(e) except ParsingError as e: status_code = 500 response_str = str(e) except HTTPError as e: status_code = e.response.status_code response_str = str(e) return { 'statusCode': status_code, 'headers': {'content-type': 'application/json'}, 'body': json.dumps({'response': response_str}) } def get_spotify_monthly_listeners(spotify_id): """Get spotify monthly listeners. Listeners HTML block example:

100

Monthly Listeners
Args: spotify_id (str): the id of the model. Raises: HTTPError: Spotify returned an error (usually 404). ParsingError: Failed to parse the spotify artist page. """ listeners_count = None url = f'https://open.spotify.com/artist/{spotify_id}' response = requests.get(url) response.raise_for_status() if response.ok: soup = BeautifulSoup(response.text) listeners_label = soup.find( 'span', {'class': 'insights__column__label'}, text='Monthly Listeners') if not listeners_label: # TODO: we need to send an alert to Sentry if the parsing fails. raise ParsingError( 'Monthly Listeners' ' element was not found.') listeners_span = listeners_label.parent.find( 'h3', {'class': 'insights__column__number'}) if not listeners_span: # TODO: we need to send an alert to Sentry if the parsing fails. raise ParsingError( '

100

element was ' 'not found.') listeners_count = int(listeners_span.text.replace(',', '')) return listeners_count