Skip to content

Commit

Permalink
Merge pull request #10 from hp0404/0.2.3
Browse files Browse the repository at this point in the history
0.2.3
  • Loading branch information
hp0404 authored Sep 13, 2021
2 parents ffdd73b + a57a014 commit 9fb4e65
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 118 deletions.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,23 @@ That will use (or create) a SQLite database called `lastfm_dump.db` and a table

To scrape specific dates, use `--start_date` and `--end_date`:

lastfm export 244ec3b62b2501514191234eed07c75 lastfm_dump.db --user way4music --start_date 2021-08-15 --end_date 2021-09-01
lastfm export 244ec3b62b2501514191234eed07c75 lastfm_dump.db --user way4music --start_date 2021-08-21 --end_date 2021-09-01


Python-based API works like this:

from lastfm import LastFM

# specific date, ommit start_date and end_date to download all tracks
api = LastFM(
api="244ec3b62b2501514191234eed07c75d",
username="way4music",
start_date="2021-08-21",
end_date="2021-09-01"
)
data = api.fetch()
song = next(data)
print(song)
container = []
for item in data:
container.append(item)
21 changes: 14 additions & 7 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,25 @@ Python API
----------
To use Python API
::
from lastfm.export import get_users_recent_tracks
from lastfm import LastFM

# playlist is a generator containing all tracks for a given date
playlist = get_users_recent_tracks(
api="244ec3b62b2501514191234eed07c75d"
user="way4music",
start_date="2021-08-15",
# specific date, ommit start_date and end_date to download all tracks
api = LastFM(
api="244ec3b62b2501514191234eed07c75d",
username="way4music",
start_date="2021-08-21",
end_date="2021-09-01"
)
data = api.fetch()
song = next(data)
print(song)
container = []
for item in data:
container.append(item)

::

This allows you to work with a trimmed JSON directly.
This allows you to work with a trimmed response directly.


---------
Expand Down
118 changes: 118 additions & 0 deletions examples/LastFM - user's recent tracks.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "8d385b85-a9b1-4ee7-ab87-2f2ad31df4c0",
"metadata": {},
"outputs": [],
"source": [
"from lastfm import LastFM"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "9dbaabba-41d2-4006-8c7b-54bce065603c",
"metadata": {},
"outputs": [],
"source": [
"api = LastFM(\n",
" api=\"244ec3b62b2501514191234eed07c75d\",\n",
" username=\"way4music\", \n",
" start_date=\"2021-08-21\",\n",
" end_date=\"2021-09-01\"\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "19b84008-1a79-4f45-a5bd-26c870fe82c1",
"metadata": {},
"outputs": [],
"source": [
"data = api.fetch()\n",
"song = next(data)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "6bf1e7b4-6f38-4e99-ad96-60e17843f6bf",
"metadata": {},
"outputs": [],
"source": [
"container = []\n",
"for item in data:\n",
" container.append(item)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "1f89a8fb-aada-41be-bda5-05e4cd124c14",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[{'artist': 'Sara Watkins',\n",
" 'album': 'Without A Word',\n",
" 'song': 'Without A Word',\n",
" 'uts_timestamp': 1630421886,\n",
" 'datetime': '31 Aug 2021, 14:58'},\n",
" {'artist': 'Dave Rawlings Machine',\n",
" 'album': 'A Friend Of A Friend',\n",
" 'song': 'Bells Of Harlem',\n",
" 'uts_timestamp': 1630421638,\n",
" 'datetime': '31 Aug 2021, 14:53'},\n",
" {'artist': 'Courtney Marie Andrews',\n",
" 'album': 'May Your Kindness Remain',\n",
" 'song': 'May Your Kindness Remain',\n",
" 'uts_timestamp': 1630421403,\n",
" 'datetime': '31 Aug 2021, 14:50'},\n",
" {'artist': 'Lera Lynn',\n",
" 'album': 'On My Own',\n",
" 'song': 'A Light Comes Through',\n",
" 'uts_timestamp': 1630421221,\n",
" 'datetime': '31 Aug 2021, 14:47'},\n",
" {'artist': 'The Deep Dark Woods',\n",
" 'album': 'The Place I Left Behind',\n",
" 'song': 'The Place I Left Behind',\n",
" 'uts_timestamp': 1630420976,\n",
" 'datetime': '31 Aug 2021, 14:42'}]"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"container[:5]"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
105 changes: 105 additions & 0 deletions lastfm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
import datetime
import requests
from time import sleep


class APIError(Exception):
"""APIError is raised when provided key is invalid
(should be 32 alphanum characters long)."""
pass


class LastFM:
"""Base LastFM class."""

URL = "http://ws.audioscrobbler.com/2.0"
DATE_FORMAT = "%Y-%m-%d"

def __init__(
self,
api: str,
username: str,
method: str = "user.getrecenttracks",
first_page: int = 1,
limit_per_page: int = 200,
extended: int = 0,
start_date=None,
end_date=None,
):
self.api = self._validate_apikey(api)
self.username = username
self.method = method
self.first_page = first_page
self.limit_per_page = limit_per_page
self.extended = extended
self.start_date = start_date
self.end_date = end_date

self.context_created = False
self.session = None
self.total_pages = None

def __del__(self) -> None:
if self.session:
self.session.close()

@staticmethod
def _validate_apikey(api):
"""Ensure apikey is valid."""
if api.isalnum() and len(api) == 32:
return api
raise APIError("API key should be 32 alphanum char. long.")

@staticmethod
def _convert_to_timestamp(date):
"""Convert human-readable `date` - either `datetime.date` or `str` - to
Unix Timestamp."""
if isinstance(date, datetime.date):
return int(date.timestamp())
return int(datetime.datetime.strptime(date, LastFM.DATE_FORMAT).timestamp())

@staticmethod
def process_response(page):
"""Yield specific k:v items of each song within page."""
playlist = page["recenttracks"]["track"]
for song in playlist:
date = song.get("date", "")
yield {
"artist": song["artist"]["#text"],
"album": song["album"]["#text"],
"song": song["name"],
"uts_timestamp": int(date["uts"]) if date else "",
"datetime": date["#text"] if date else "",
}

def ensure_context_created(self):
"""Make sure session, params, and request total number of pages exist."""
if self.context_created:
return
self.session = requests.Session()
self.params = {
"method": self.method,
"user": self.username,
"api_key": self.api,
"page": self.first_page,
"limit": self.limit_per_page,
"extended": self.extended,
"format": "json",
}
if self.start_date is not None:
self.params["from"] = self._convert_to_timestamp(self.start_date)
if self.end_date is not None:
self.params["to"] = self._convert_to_timestamp(self.end_date)
response = self.session.get(LastFM.URL, params=self.params).json()
self.total_pages = int(response["recenttracks"]["@attr"]["totalPages"])
self.context_created = True

def fetch(self):
"""Fetch user's track history given the parametrs."""
self.ensure_context_created()
while self.params["page"] <= self.total_pages:
response = self.session.get(LastFM.URL, params=self.params).json()
yield from self.process_response(response)
self.params["page"] += 1
sleep(1)
20 changes: 13 additions & 7 deletions lastfm/cli.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
import click
from sqlite_utils import Database
from lastfm.export import get_users_recent_tracks, DATE_FORMAT
from lastfm import LastFM


formats = [DATE_FORMAT]
formats = [LastFM.DATE_FORMAT]


@click.group()
Expand Down Expand Up @@ -45,12 +45,18 @@ def export_playlist(
database = Database(database)

table = database.table(table)
data = get_users_recent_tracks(
api=api, user=user, first_page=first_page, limit_per_page=limit_per_page,
extended=extended, start_date=start_date, end_date=end_date
api = LastFM(
api=api, username=user, first_page=first_page,
limit_per_page=limit_per_page, extended=extended,
start_date=start_date, end_date=end_date
)
for item in data:
table.upsert(item, pk="uts_timestamp")
api.ensure_context_created()
data = api.fetch()
with click.progressbar(length=api.total_pages, label="Fetching data") as bar:
for idx, item in enumerate(data):
table.upsert(item, pk="uts_timestamp")
bar.pos = int(idx / api.total_pages * 100)
bar.update(0)


if __name__ == "__main__":
Expand Down
Loading

0 comments on commit 9fb4e65

Please sign in to comment.