├── requirements.txt ├── tox.ini ├── upload-to-pypi ├── .pre-commit-config.yaml ├── .travis.yml ├── .gitignore ├── setup.py ├── tests ├── test_repr.py ├── test_misc.py ├── test_dates.py ├── test_historical_charts.py ├── test_year_end_charts.py ├── test_current_charts.py ├── 2006-08-05-traditional-jazz-albums.json ├── 1979-08-04-hot-100.json └── 2014-08-02-artist-100.json ├── LICENSE ├── examples └── top-singles-playlist │ ├── README.md │ └── run.py ├── CHANGELOG.md ├── README.md └── billboard.py /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4>=4.4.1 2 | requests>=2.2.1 3 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py34 3 | [testenv] 4 | deps= 5 | nose 6 | six 7 | commands=nosetests 8 | -------------------------------------------------------------------------------- /upload-to-pypi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf dist/ 4 | python3.6 setup.py bdist_wheel --universal 5 | twine upload dist/* 6 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: stable 4 | hooks: 5 | - id: black 6 | language_version: python3.6 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | install: "pip install -r requirements.txt" 6 | script: nosetests 7 | branches: 8 | only: 9 | - master 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | with open("README.md") as f: 6 | long_description = f.read() 7 | 8 | setup( 9 | name="billboard.py", 10 | version="7.1.0", # Don't forget to update CHANGELOG.md! 11 | description="Python API for downloading Billboard charts", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | author="Allen Guo", 15 | author_email="guoguo12@gmail.com", 16 | url="https://github.com/guoguo12/billboard-charts", 17 | py_modules=["billboard"], 18 | license="MIT License", 19 | install_requires=["beautifulsoup4 >= 4.4.1", "requests >= 2.2.1"], 20 | ) 21 | -------------------------------------------------------------------------------- /tests/test_repr.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import billboard 3 | import six 4 | 5 | 6 | class ReprTest(unittest.TestCase): 7 | """Checks that the string representations of charts and entries are correct. 8 | """ 9 | 10 | @classmethod 11 | def setUpClass(cls): 12 | cls.chart = billboard.ChartData("hot-100", date="2010-01-02") 13 | 14 | def testReprChart(self): 15 | self.assertEqual( 16 | repr(self.chart), "billboard.ChartData('hot-100', date='2010-01-02')" 17 | ) 18 | 19 | def testReprEntry(self): 20 | self.assertEqual( 21 | repr(self.chart[0]), 22 | "billboard.ChartEntry(title={!r}, artist={!r})".format( 23 | six.text_type("TiK ToK"), six.text_type("Ke$ha") 24 | ), 25 | ) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Allen Guo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /tests/test_misc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import billboard 4 | import unittest 5 | from nose.tools import raises 6 | from requests.exceptions import ConnectionError 7 | import six 8 | 9 | 10 | class MiscTest(unittest.TestCase): 11 | @raises(ConnectionError) 12 | def testTimeout(self): 13 | """Checks that using a very small timeout prevents connection.""" 14 | billboard.ChartData("hot-100", timeout=1e-9) 15 | 16 | @raises(billboard.BillboardNotFoundException) 17 | def testNonExistentChart(self): 18 | """Checks that requesting a non-existent chart fails.""" 19 | billboard.ChartData("does-not-exist") 20 | 21 | def testUnicode(self): 22 | """Checks that the Billboard website does not use Unicode characters.""" 23 | chart = billboard.ChartData("hot-100", date="2018-01-27") 24 | self.assertEqual( 25 | chart[97].title, six.text_type("El Bano") 26 | ) # With Unicode this should be "El Baño" 27 | 28 | def testDifficultTitleCasing(self): 29 | """Checks that a difficult chart title receives proper casing.""" 30 | chart = billboard.ChartData("greatest-r-b-hip-hop-songs") 31 | self.assertEqual(chart.title, "Greatest of All Time Hot R&B/Hip-Hop Songs") 32 | -------------------------------------------------------------------------------- /tests/test_dates.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import unittest 3 | import billboard 4 | from nose.tools import raises 5 | 6 | 7 | class DateTest(unittest.TestCase): 8 | def testDateRounding(self): 9 | """Checks that the Billboard website is rounding dates correctly: it should 10 | round up to the nearest date on which a chart was published. 11 | 12 | We used to check that requesting the Hot 100 chart for 1958-01-01 would 13 | return the first Hot 100 chart (1958-08-04), but the test was flaky. 14 | """ 15 | chart = billboard.ChartData("hot-100", date="2019-12-31") 16 | self.assertEqual(chart.date, "2020-01-04") 17 | 18 | def testDatetimeDate(self): 19 | """Checks that ChartData correctly handles datetime objects as the 20 | date parameter. 21 | """ 22 | chart = billboard.ChartData("hot-100", datetime.date(2016, 7, 9)) 23 | self.assertEqual(len(chart), 100) 24 | self.assertEqual(chart.date, "2016-07-09") 25 | 26 | @raises(ValueError) 27 | def testUnsupportedDateFormat(self): 28 | """Checks that using an unsupported date format raises an exception.""" 29 | billboard.ChartData("hot-100", date="07-30-1996") 30 | 31 | @raises(ValueError) 32 | def testEmptyStringDate(self): 33 | """ 34 | Checks that passing an empty string as the date raises an exception. 35 | """ 36 | billboard.ChartData("hot-100", date="") 37 | 38 | @raises(ValueError) 39 | def testInvalidDate(self): 40 | """Checks that passing a correctly formatted but invalid date raises an exception.""" 41 | billboard.ChartData("hot-100", date="2018-99-99") 42 | -------------------------------------------------------------------------------- /examples/top-singles-playlist/README.md: -------------------------------------------------------------------------------- 1 | # Example: Top Singles Playlist 2 | 3 | **Note: This example no longer works, as of [#22](https://github.com/guoguo12/billboard-charts/pull/22).** 4 | 5 | In this example, we'll create a Spotify playlist containing the past 100 songs that have reached the top spot on the Hot 100. We'll be using billboard.py and [Spotipy](https://spotipy.readthedocs.org/), which is a wrapper around the [Spotify Web API](https://developer.spotify.com/web-api/). 6 | 7 | To view the example, see `run.py`. 8 | 9 | ## Overview 10 | 11 | [This blog post](http://aguo.us/writings/spotify-billboard.html) provides a good overview of how this example works. 12 | 13 | The actual `run.py` features several minor improvements over the version described in the blog post: 14 | 15 | * More error-handling. 16 | * More variables have been made constants. 17 | * billboard.py requests are throttled. 18 | 19 | ## Instructions 20 | 21 | To run this example, create a [Spotify Developer](https://developer.spotify.com) account. (You'll need a regular Spotify account to do so.) Register an app with the Spotify API, then fill out the first three constants at the top of `run.py`: 22 | 23 | ```python 24 | SPOTIFY_USERNAME = 'YOUR_SPOTIFY_USERNAME' 25 | SPOTIFY_CLIENT_ID = 'YOUR_CLIENT_ID' 26 | SPOTIFY_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' 27 | ``` 28 | 29 | Make any necessary adjustments to the other constants before proceeding. 30 | 31 | The first time you run the script, you'll have to authorize the app you made to create playlists on your behalf. Follow the instructions in your terminal. 32 | 33 | If you have any questions, or if you find a bug, feel free to create a new issue. 34 | -------------------------------------------------------------------------------- /examples/top-singles-playlist/run.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | 4 | import billboard 5 | import spotipy 6 | import spotipy.util 7 | 8 | SPOTIFY_USERNAME = 'YOUR_SPOTIFY_USERNAME' 9 | SPOTIFY_CLIENT_ID = 'YOUR_CLIENT_ID' 10 | SPOTIFY_CLIENT_SECRET = 'YOUR_CLIENT_SECRET' 11 | SPOTIFY_REDIRECT_URI = 'https://github.com/guoguo12/billboard-charts' 12 | SPOTIFY_SCOPE = 'playlist-modify-public' 13 | 14 | PLAYLIST_NAME = 'Best of the Hot 100' 15 | TRACK_COUNT = 100 16 | CHART = 'hot-100' 17 | START_DATE = None # None for default (latest chart) 18 | THROTTLE_TIME = 0.50 # Seconds 19 | 20 | 21 | def unique_top_tracks_generator(): 22 | seen_tracks = set() 23 | chart = billboard.ChartData(CHART, date=START_DATE) 24 | 25 | while len(seen_tracks) < TRACK_COUNT: 26 | top_track = chart[0] 27 | duplicate = top_track.spotifyLink in seen_tracks 28 | 29 | if not duplicate: 30 | seen_tracks.add(top_track.spotifyLink) 31 | yield top_track 32 | 33 | if not chart.previousDate: 34 | break 35 | time.sleep(THROTTLE_TIME) 36 | chart = billboard.ChartData(CHART, date=chart.previousDate) 37 | 38 | raise StopIteration 39 | 40 | 41 | def main(): 42 | token = spotipy.util.prompt_for_user_token( 43 | SPOTIFY_USERNAME, 44 | scope=SPOTIFY_SCOPE, 45 | client_id=SPOTIFY_CLIENT_ID, 46 | client_secret=SPOTIFY_CLIENT_SECRET, 47 | redirect_uri=SPOTIFY_REDIRECT_URI) 48 | if not token: 49 | sys.exit('Authorization failed') 50 | sp = spotipy.Spotify(auth=token) 51 | 52 | playlist = sp.user_playlist_create(SPOTIFY_USERNAME, PLAYLIST_NAME) 53 | playlist_id = playlist[u'id'] 54 | 55 | for track in unique_top_tracks_generator(): 56 | print track 57 | url = track.spotifyLink 58 | try: 59 | sp.user_playlist_add_tracks(SPOTIFY_USERNAME, playlist_id, [url]) 60 | except Exception as e: 61 | print e 62 | 63 | 64 | if __name__ == '__main__': 65 | main() 66 | -------------------------------------------------------------------------------- /tests/test_historical_charts.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import json 3 | import os 4 | import six 5 | import unittest 6 | import billboard 7 | 8 | 9 | @six.add_metaclass(abc.ABCMeta) 10 | class Base: 11 | @classmethod 12 | @abc.abstractmethod 13 | def setUpClass(cls): 14 | pass 15 | 16 | def testCorrectnessVersusReference(self): 17 | testDir = os.path.dirname(os.path.realpath(__file__)) 18 | referencePath = os.path.join(testDir, self.referenceFile) 19 | with open(referencePath) as reference: 20 | reference = json.loads(reference.read()) 21 | 22 | self.assertEqual(self.chart.name, reference["name"]) 23 | self.assertEqual(self.chart.title, reference["title"]) 24 | self.assertEqual(self.chart.date, reference["date"]) 25 | 26 | self.assertEqual(len(self.chart.entries), len(reference["entries"])) 27 | for chartEntry, referenceEntry in zip(self.chart.entries, reference["entries"]): 28 | # We intentionally don't check image here, since image URLs might 29 | # change a lot 30 | self.assertEqual(chartEntry.title, referenceEntry["title"]) 31 | self.assertEqual(chartEntry.artist, referenceEntry["artist"]) 32 | self.assertEqual(chartEntry.peakPos, referenceEntry["peakPos"]) 33 | self.assertEqual(chartEntry.lastPos, referenceEntry["lastPos"]) 34 | self.assertEqual(chartEntry.weeks, referenceEntry["weeks"]) 35 | self.assertEqual(chartEntry.isNew, referenceEntry["isNew"]) 36 | self.assertEqual(chartEntry.rank, referenceEntry["rank"]) 37 | 38 | 39 | class TestHistoricalHot100(Base, unittest.TestCase): 40 | @classmethod 41 | def setUpClass(cls): 42 | cls.chart = billboard.ChartData("hot-100", date="1979-08-04") 43 | cls.referenceFile = "1979-08-04-hot-100.json" 44 | 45 | 46 | class TestHistoricalTraditionalJazzAlbums(Base, unittest.TestCase): 47 | @classmethod 48 | def setUpClass(cls): 49 | cls.chart = billboard.ChartData("traditional-jazz-albums", date="2006-08-05") 50 | cls.referenceFile = "2006-08-05-traditional-jazz-albums.json" 51 | 52 | 53 | class TestHistoricalArtist100(Base, unittest.TestCase): 54 | @classmethod 55 | def setUpClass(cls): 56 | cls.chart = billboard.ChartData("artist-100", date="2014-08-02") 57 | cls.referenceFile = "2014-08-02-artist-100.json" 58 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ## 7.1.0 – 2024-08-12 10 | ### Added 11 | - Add support for TikTok charts (#95). 12 | 13 | ## 7.0.2 – 2024-07-13 14 | ### Added 15 | - Add description for pypi.org. 16 | 17 | ## 7.0.1 – 2024-03-03 18 | ### Fixed 19 | - Fix image parsing (#70). 20 | 21 | ## 7.0.0 – 2021-11-20 22 | ### Deprecated 23 | - Deprecate `previousDate` and `nextDate`. 24 | ### Removed 25 | - Remove `charts` function for listing all charts. 26 | ### Fixed 27 | - Fix artist chart and year-end chart parsing. 28 | 29 | ## 6.3.0 – 2021-11-20 30 | ### Fixed 31 | - Fix support for basic charts (the Billboard.com UI changed). 32 | 33 | ## 6.2.1 – 2020-11-02 34 | ### Added 35 | - Add support for year-end charts (#69, #33). 36 | - Add warning for unsupported years. 37 | 38 | ## 6.1.2 – 2020-09-15 39 | ### Fixed 40 | - Fix parsing of `artist` for entries that are missing artists (#71). 41 | 42 | ## 6.1.1 – 2020-09-15 43 | ### Fixed 44 | - Fix parsing of `previousDate`. 45 | 46 | ## 6.1.0 – 2020-03-21 47 | ### Changed 48 | - Increased the default `max_retries` from 3 to 5. 49 | 50 | ## 6.0.3 – 2020-02-26 51 | ### Fixed 52 | - Fix parsing of chart titles. 53 | 54 | ## 6.0.2 – 2020-02-01 55 | ### Fixed 56 | - Fix parsing of entry stats for certain old-style charts. 57 | 58 | ## 6.0.1 – 2020-01-21 59 | ### Fixed 60 | - Fix `peakPos` description. 61 | 62 | ## 6.0.0 – 2020-01-06 63 | ### Added 64 | - Respect Retry-After headers when retrying (#65). 65 | ### Changed 66 | - Switch to HTTPS for chart requests. 67 | 68 | ## 5.4.0 – 2019-12-27 69 | ### Added 70 | - Connection retry logic (`max_retries`). 71 | 72 | ## 5.3.0 – 2019-09-29 73 | ### Added 74 | - Partial support for new Billboard.com UI used for some (but not all) charts. 75 | The `image` attribute is always set to `None` for such charts. 76 | 77 | ## 5.2.3 – 2019-09-06 78 | ### Fixed 79 | - Fix `lastPos` again in response to UI change. 80 | - Raise when HTTP request for `charts()` fails. 81 | 82 | ## 5.2.2 – 2019-08-27 83 | ### Fixed 84 | - Stop sending HTTP header that was causing all requests to fail with HTTP 403. 85 | 86 | ## 5.2.1 – 2019-08-11 87 | ### Fixed 88 | - Fix bug in which `lastPos` was set to the position two weeks prior instead of last week's position. 89 | 90 | ## 5.2.0 – 2019-08-09 91 | ### Added (since the last release, 5.1.1) 92 | - This changelog file. 93 | - The `charts` function for listing all charts (#40). 94 | - Validation for dates passed to `ChartData` (#40). 95 | -------------------------------------------------------------------------------- /tests/test_year_end_charts.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import json 3 | import unittest 4 | import warnings 5 | 6 | import billboard 7 | import six 8 | from billboard import UnsupportedYearWarning 9 | 10 | 11 | @six.add_metaclass(abc.ABCMeta) 12 | class Base: 13 | @classmethod 14 | @abc.abstractmethod 15 | def setUpClass(cls): 16 | pass 17 | 18 | def testYear(self): 19 | self.assertIsNotNone(self.chart.year) 20 | 21 | def testNextYear(self): 22 | next_year = str(int(self.chart.year) + 1) 23 | self.assertEqual(self.chart.nextYear, next_year) 24 | 25 | def testPreviousYear(self): 26 | previous_year = str(int(self.chart.year) - 1) 27 | self.assertEqual(self.chart.previousYear, previous_year) 28 | 29 | def testTitle(self): 30 | self.assertEqual(self.chart.title, self.expectedTitle) 31 | 32 | def testRanks(self): 33 | ranks = list(entry.rank for entry in self.chart) 34 | self.assertEqual(ranks, sorted(ranks)) 35 | 36 | def testEntriesValidity(self, skipTitleCheck=False): 37 | self.assertEqual(len(self.chart), self.expectedNumEntries) 38 | for entry in self.chart: 39 | if not skipTitleCheck: 40 | self.assertGreater(len(entry.title), 0) 41 | self.assertGreater(len(entry.artist), 0) 42 | 43 | def testJson(self): 44 | self.assertTrue(json.loads(self.chart.json())) 45 | for entry in self.chart: 46 | self.assertTrue(json.loads(entry.json())) 47 | 48 | 49 | class TestHot100Songs2019(Base, unittest.TestCase): 50 | @classmethod 51 | def setUpClass(cls): 52 | cls.chart = billboard.ChartData("hot-100-songs", year="2019") 53 | cls.expectedTitle = "Hot 100 Songs - Year-End" 54 | cls.expectedNumEntries = 100 55 | 56 | 57 | class TestHotCountrySongs1970(Base, unittest.TestCase): 58 | @classmethod 59 | def setUpClass(cls): 60 | name = "hot-country-songs" 61 | year = 1970 62 | warnings.filterwarnings(action="always", category=UnsupportedYearWarning) 63 | with warnings.catch_warnings(record=True) as w: 64 | cls.chart = billboard.ChartData(name, year=year) 65 | cls.warning = w[0] if w else None 66 | 67 | cls.expectedTitle = "Hot Country Songs - Year-End" 68 | cls.expectedNumEntries = 100 # Just shows the latest chart 69 | 70 | def testUnsupportedYearWarning(self): 71 | self.assertEquals(self.warning.category, UnsupportedYearWarning) 72 | 73 | def testNextYear(self): 74 | self.assertIsNone(self.chart.nextYear) 75 | 76 | def testPreviousYear(self): 77 | self.assertIsNone(self.chart.previousYear) 78 | 79 | 80 | class TestJazzAlbumsImprints2006(Base, unittest.TestCase): 81 | @classmethod 82 | def setUpClass(cls): 83 | cls.chart = billboard.ChartData("jazz-imprints", year="2006") 84 | cls.expectedTitle = "Jazz Albums Imprints - Year-End" 85 | cls.expectedNumEntries = 9 86 | 87 | def testEntriesValidity(self): 88 | super(TestJazzAlbumsImprints2006, self).testEntriesValidity(skipTitleCheck=True) 89 | for entry in self.chart: 90 | self.assertEqual(entry.title, "") # This chart has no titles 91 | 92 | def testPreviousYear(self): 93 | self.assertIsNone(self.chart.previousYear) 94 | -------------------------------------------------------------------------------- /tests/test_current_charts.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import json 3 | import six 4 | import unittest 5 | import billboard 6 | 7 | 8 | @six.add_metaclass(abc.ABCMeta) 9 | class Base: 10 | @classmethod 11 | @abc.abstractmethod 12 | def setUpClass(cls): 13 | pass 14 | 15 | def testDate(self): 16 | self.assertIsNotNone(self.chart.date) 17 | 18 | def testTitle(self): 19 | self.assertEqual(self.chart.title, self.expectedTitle) 20 | 21 | def testRanks(self): 22 | ranks = list(entry.rank for entry in self.chart) 23 | self.assertEqual(ranks, list(range(1, self.expectedNumEntries + 1))) 24 | 25 | def testEntriesValidity(self, skipTitleCheck=False): 26 | self.assertEqual(len(self.chart), self.expectedNumEntries) 27 | for entry in self.chart: 28 | if not skipTitleCheck: 29 | self.assertGreater(len(entry.title), 0) 30 | self.assertGreater(len(entry.artist), 0) 31 | self.assertGreater(len(entry.image), 0) 32 | self.assertTrue(1 <= entry.peakPos <= self.expectedNumEntries) 33 | self.assertTrue(0 <= entry.lastPos <= self.expectedNumEntries) 34 | self.assertGreaterEqual(entry.weeks, 1) 35 | self.assertIsInstance(entry.isNew, bool) 36 | 37 | def testEntriesConsistency(self): 38 | for entry in self.chart: 39 | if entry.isNew: 40 | self.assertEqual(0, entry.lastPos) 41 | 42 | def testJson(self): 43 | self.assertTrue(json.loads(self.chart.json())) 44 | for entry in self.chart: 45 | self.assertTrue(json.loads(entry.json())) 46 | 47 | 48 | class TestCurrentHot100(Base, unittest.TestCase): 49 | @classmethod 50 | def setUpClass(cls): 51 | cls.chart = billboard.ChartData("hot-100") 52 | cls.expectedTitle = "Billboard Hot 100™" 53 | cls.expectedNumEntries = 100 54 | 55 | 56 | class TestCurrentTraditionalJazzAlbums(Base, unittest.TestCase): 57 | @classmethod 58 | def setUpClass(cls): 59 | cls.chart = billboard.ChartData("traditional-jazz-albums") 60 | cls.expectedTitle = "Traditional Jazz Albums" 61 | cls.expectedNumEntries = 15 62 | 63 | 64 | class TestCurrentGreatestHot100Singles(Base, unittest.TestCase): 65 | """The Greatest Hot 100 Singles chart is special in that there are no past 66 | charts. 67 | """ 68 | 69 | @classmethod 70 | def setUpClass(cls): 71 | cls.chart = billboard.ChartData("greatest-hot-100-singles") 72 | cls.expectedTitle = "Greatest of All Time Hot 100 Songs" 73 | cls.expectedNumEntries = 100 74 | 75 | def testDate(self): 76 | # The date is in fact None 77 | pass 78 | 79 | def testEntriesValidity(self): 80 | for entry in self.chart: 81 | self.assertIsNone(entry.peakPos) 82 | self.assertIsNone(entry.lastPos) 83 | self.assertIsNone(entry.weeks) 84 | 85 | 86 | class TestCurrentArtist100(Base, unittest.TestCase): 87 | """The Artist 100 chart is special in that it does not have titles. 88 | """ 89 | 90 | @classmethod 91 | def setUpClass(cls): 92 | cls.chart = billboard.ChartData("artist-100") 93 | cls.expectedTitle = "Billboard Artist 100" 94 | cls.expectedNumEntries = 100 95 | 96 | def testEntriesValidity(self): 97 | super(TestCurrentArtist100, self).testEntriesValidity(skipTitleCheck=True) 98 | for entry in self.chart: 99 | self.assertEqual(entry.title, "") # This chart has no titles 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | billboard.py 2 | ============ 3 | 4 | **billboard.py** is a Python API for accessing music charts from [Billboard.com](http://www.billboard.com/charts/). 5 | 6 | Installation 7 | ------------ 8 | 9 | Install with pip: 10 | 11 | ``` 12 | pip install billboard.py 13 | ``` 14 | 15 | Or clone this repo and run `python setup.py install`. 16 | 17 | Quickstart 18 | ---------- 19 | 20 | To download a *Billboard* chart, we use the `ChartData()` constructor. 21 | 22 | Let's fetch the current [Hot 100](http://www.billboard.com/charts/hot-100) chart. 23 | 24 | ```Python 25 | >>> import billboard 26 | >>> chart = billboard.ChartData('hot-100') 27 | >>> chart.title 28 | 'The Hot 100' 29 | ``` 30 | 31 | Now we can look at the chart entries, which are of type `ChartEntry` and have attributes like `artist` and `title`: 32 | 33 | ```Python 34 | >>> song = chart[0] # Get no. 1 song on chart 35 | >>> song.title 36 | 'Nice For What' 37 | >>> song.artist 38 | 'Drake' 39 | >>> song.weeks # Number of weeks on chart 40 | 2 41 | ``` 42 | 43 | We can also `print` the entire chart: 44 | 45 | ``` 46 | >>> print(chart) 47 | hot-100 chart from 2018-04-28 48 | ----------------------------- 49 | 1. 'Nice For What' by Drake 50 | 2. 'God's Plan' by Drake 51 | 3. 'Meant To Be' by Bebe Rexha & Florida Georgia Line 52 | 4. 'Psycho' by Post Malone Featuring Ty Dolla $ign 53 | 5. 'The Middle' by Zedd, Maren Morris & Grey 54 | # ... 55 | ``` 56 | 57 | Guide 58 | ----- 59 | 60 | ### What charts exist? 61 | 62 | [This page](https://www.billboard.com/charts) shows all charts grouped by category. 63 | 64 | Year-end charts are [here](https://www.billboard.com/charts/year-end). 65 | 66 | ### Downloading a chart 67 | 68 | Use the `ChartData` constructor to download a chart: 69 | 70 | ```Python 71 | ChartData(name, date=None, year=None, fetch=True, timeout=25) 72 | ``` 73 | 74 | The arguments are: 75 | 76 | * `name` – The chart name, e.g. `'hot-100'` or `'pop-songs'`. 77 | * `date` – The chart date as a string, in YYYY-MM-DD format. By default, the latest chart is fetched. 78 | * `year` – The chart year, if requesting a year-end chart. Must be a string in YYYY format. Cannot supply both `date` and `year`. 79 | * `fetch` – A boolean indicating whether to fetch the chart data from Billboard.com immediately (at instantiation time). If `False`, the chart data can be populated at a later time using the `fetchEntries()` method. 80 | * `max_retries` – The max number of times to retry when requesting data (default: 5). 81 | * `timeout` – The number of seconds to wait for a server response. If `None`, no timeout is applied. 82 | 83 | For example, to download the [Alternative Songs year-end chart for 2006](https://www.billboard.com/charts/year-end/2006/alternative-songs): 84 | 85 | ```python 86 | >>> chart = billboard.ChartData('alternative-songs', year=2006) 87 | ``` 88 | 89 | ### Accessing chart entries 90 | 91 | If `chart` is a `ChartData` instance, we can ask for its `entries` attribute to get the chart entries (see below) as a list. 92 | 93 | For convenience, `chart[x]` is equivalent to `chart.entries[x]`, and `ChartData` instances are iterable. 94 | 95 | ### Chart entry attributes 96 | 97 | A chart entry (typically a single track) is of type `ChartEntry`. A `ChartEntry` instance has the following attributes: 98 | 99 | * `title` – The title of the track. 100 | * `artist` – The name of the artist, as formatted on Billboard.com. 101 | * `image` – The URL of the image for the track. 102 | * `peakPos` – The track's peak position on the chart as of the chart date, as an int (or `None` if the chart does not include this information). 103 | * `lastPos` – The track's position on the previous week's chart, as an int (or `None` if the chart does not include this information). This value is 0 if the track was not on the previous week's chart. 104 | * `weeks` – The number of weeks the track has been or was on the chart, including future dates (up until the present time). 105 | * `rank` – The track's current position on the chart. 106 | * `isNew` – Whether the track is new to the chart. 107 | 108 | ### More resources 109 | 110 | For additional documentation, look at the file `billboard.py`, or use Python's interactive `help` feature. 111 | 112 | Think you found a bug? Create an issue [here](https://github.com/guoguo12/billboard-charts/issues). 113 | 114 | Contributing 115 | ------------ 116 | 117 | Pull requests are welcome! Please adhere to the following style guidelines: 118 | 119 | * We use [Black](https://github.com/psf/black) for formatting. 120 | * If you have [pre-commit](https://pre-commit.com/) installed, run `pre-commit install` to install a pre-commit hook that runs Black. 121 | * Variable names should be in `mixedCase`. 122 | 123 | ### Running tests 124 | 125 | To run the test suite locally, install [nose](https://nose.readthedocs.org/en/latest/) and run 126 | 127 | ``` 128 | nosetests 129 | ``` 130 | 131 | To run the test suite locally on both Python 2.7 and 3.4, install [tox](https://tox.readthedocs.org/en/latest/) and run 132 | 133 | ``` 134 | tox 135 | ``` 136 | 137 | Made with billboard.py 138 | ------------ 139 | Projects and articles that use billboard.py: 140 | 141 | * ["What Makes Music Pop?"](https://cs1951a2016millionsong.wordpress.com/2016/05/14/final-report/) by Zach Loery 142 | * ["How Has Hip Hop Changed Over the Years?"](https://rohankshir.github.io/2016/02/28/topic-modeling-on-hiphop/) by Rohan Kshirsagar 143 | * ["Spotify and billboard.py"](http://aguo.us/writings/spotify-billboard.html) by Allen Guo 144 | * [chart_success.py](https://github.com/3ngthrust/calculate-chart-success-2/) by 3ngthrust 145 | * ["Top Billboard Streaks"](https://twitter.com/polygraphing/status/748543281345224704) and ["Drake's Hot-100 Streak"](https://twitter.com/polygraphing/status/748987711541940224) by James Wenzel @ Polygraph 146 | * ["Determining the 'Lifecycle' of Each Music Genre"](http://thedataface.com/genre-lifecycles/) by Jack Beckwith @ The Data Face 147 | * ["Splunking the Billboard Hot 100 with Help from the Spotify API"](https://www.function1.com/2017/09/splunking-the-billboard-hot-100-with-help-from-the-spotify-api) by Karthik Subramanian 148 | * ["Predicting Movement on 70s & 80s Billboard R&B Charts"](https://afriedman412.github.io/Predicting-Movement-On-70s-&-80s-Billboard-R&B-Charts/) by Andy Friedman 149 | * ["Billboard Trends"](https://tom-johnson.net/2018/08/12/billboard-trends/) by Tom Johnson 150 | * ["Billboard Charts Alexa Skill"](https://www.amazon.com/Cameron-Ezell-Billboard-Charts/dp/B07K5SX95L) by Cameron Ezell 151 | * ["In the Mix: What's the Secret Formula Behind the Hottest Tracks?"](https://medium.com/swlh/in-the-mix-8f22db7a69d2) by Jason Kibozi-Yocka 152 | * ["MUHSIC: An Open Dataset with Temporal Musical Success Information"](https://sol.sbc.org.br/index.php/dsw/article/view/17415) by Gabriel P. Oliveira et al. 153 | 154 | Have an addition? Make a pull request! 155 | 156 | Dependencies 157 | ------------ 158 | * [Beautiful Soup 4](http://www.crummy.com/software/BeautifulSoup/) 159 | * [Requests](http://requests.readthedocs.org/en/latest/) 160 | 161 | License 162 | ------- 163 | 164 | * This project is licensed under the MIT License. 165 | * The *Billboard* charts are owned by Prometheus Global Media LLC. See Billboard.com's [Terms of Use](http://www.billboard.com/terms-of-use) for more information. 166 | -------------------------------------------------------------------------------- /tests/2006-08-05-traditional-jazz-albums.json: -------------------------------------------------------------------------------- 1 | { 2 | "_max_retries": 5, 3 | "_timeout": 25, 4 | "date": "2006-08-05", 5 | "entries": [ 6 | { 7 | "artist": "Michael Buble", 8 | "image": "https://charts-static.billboard.com/img/2005/02/michael-buble-000-its-time-36m-53x53.jpg", 9 | "isNew": false, 10 | "lastPos": 1, 11 | "peakPos": 1, 12 | "rank": 1, 13 | "title": "It's Time", 14 | "weeks": 76 15 | }, 16 | { 17 | "artist": "Elvis Costello & Allen Toussaint", 18 | "image": "https://charts-static.billboard.com/img/2006/06/elvis-costello-q1t-the-river-in-reverse-ran-53x53.jpg", 19 | "isNew": false, 20 | "lastPos": 2, 21 | "peakPos": 2, 22 | "rank": 2, 23 | "title": "The River In Reverse", 24 | "weeks": 7 25 | }, 26 | { 27 | "artist": "Katie Melua", 28 | "image": "https://charts-static.billboard.com/img/2006/06/katie-melua-vfb-piece-by-piece-czi-53x53.jpg", 29 | "isNew": false, 30 | "lastPos": 3, 31 | "peakPos": 3, 32 | "rank": 3, 33 | "title": "Piece By Piece", 34 | "weeks": 7 35 | }, 36 | { 37 | "artist": "Nat King Cole", 38 | "image": "https://charts-static.billboard.com/img/2006/05/nat-king-cole-wkp-53x53.jpg", 39 | "isNew": false, 40 | "lastPos": 4, 41 | "peakPos": 2, 42 | "rank": 4, 43 | "title": "The Very Best Of Nat King Cole", 44 | "weeks": 12 45 | }, 46 | { 47 | "artist": "Diana Ross", 48 | "image": "https://charts-static.billboard.com/img/2006/06/diana-ross-l90-blue-i2l-53x53.jpg", 49 | "isNew": false, 50 | "lastPos": 5, 51 | "peakPos": 2, 52 | "rank": 5, 53 | "title": "Blue", 54 | "weeks": 6 55 | }, 56 | { 57 | "artist": "Chris Botti", 58 | "image": "https://charts-static.billboard.com/img/2005/11/chris-botti-4k2-to-love-again-the-duets-27t-53x53.jpg", 59 | "isNew": false, 60 | "lastPos": 6, 61 | "peakPos": 1, 62 | "rank": 6, 63 | "title": "To Love Again: The Duets", 64 | "weeks": 40 65 | }, 66 | { 67 | "artist": "Michael Buble", 68 | "image": "https://www.billboard.com/assets/1591809055/images/charts/bb-placeholder-new.jpg?70be56cb038813ba561f", 69 | "isNew": false, 70 | "lastPos": 8, 71 | "peakPos": 2, 72 | "rank": 7, 73 | "title": "Caught In The Act", 74 | "weeks": 35 75 | }, 76 | { 77 | "artist": "Thelonious Monk With John Coltrane", 78 | "image": "https://charts-static.billboard.com/img/2006/07/thelonious-monk-0r8-the-complete-1957-riverside-recordings-72a-53x53.jpg", 79 | "isNew": false, 80 | "lastPos": 7, 81 | "peakPos": 5, 82 | "rank": 8, 83 | "title": "The Complete 1957 Riverside Recordings", 84 | "weeks": 4 85 | }, 86 | { 87 | "artist": "Chris Botti", 88 | "image": "https://charts-static.billboard.com/img/2004/10/chris-botti-4k2-when-i-fall-in-love-jj5-53x53.jpg", 89 | "isNew": false, 90 | "lastPos": 9, 91 | "peakPos": 1, 92 | "rank": 9, 93 | "title": "When I Fall In Love", 94 | "weeks": 95 95 | }, 96 | { 97 | "artist": "John Pizzarelli With The Clayton-Hamilton Jazz Orchestra", 98 | "image": "https://charts-static.billboard.com/img/2006/08/john-pizzarelli-with-the-clayton-hamilton-jazz-orchestra-000-dear-mr-sinatra-f29-53x53.jpg", 99 | "isNew": true, 100 | "lastPos": 0, 101 | "peakPos": 10, 102 | "rank": 10, 103 | "title": "Dear Mr. Sinatra", 104 | "weeks": 1 105 | }, 106 | { 107 | "artist": "Madeleine Peyroux", 108 | "image": "https://charts-static.billboard.com/img/2004/10/madeleine-peyroux-92p-careless-love-ilu-53x53.jpg", 109 | "isNew": false, 110 | "lastPos": 10, 111 | "peakPos": 2, 112 | "rank": 11, 113 | "title": "Careless Love", 114 | "weeks": 97 115 | }, 116 | { 117 | "artist": "Sophie Milman", 118 | "image": "https://charts-static.billboard.com/img/2006/04/sophie-milman-kax-sophie-milman-eos-53x53.jpg", 119 | "isNew": false, 120 | "lastPos": 0, 121 | "peakPos": 12, 122 | "rank": 12, 123 | "title": "Sophie Milman", 124 | "weeks": 4 125 | }, 126 | { 127 | "artist": "Thelonious Monk Quartet With John Coltrane", 128 | "image": "https://charts-static.billboard.com/img/2005/10/thelonious-monk-quartet-dio-at-carnegie-hall-wb9-53x53.jpg", 129 | "isNew": false, 130 | "lastPos": 12, 131 | "peakPos": 2, 132 | "rank": 13, 133 | "title": "At Carnegie Hall", 134 | "weeks": 43 135 | }, 136 | { 137 | "artist": "Cassandra Wilson", 138 | "image": "https://charts-static.billboard.com/img/2006/04/cassandra-wilson-iut-thunderbird-076-53x53.jpg", 139 | "isNew": false, 140 | "lastPos": 11, 141 | "peakPos": 2, 142 | "rank": 14, 143 | "title": "thunderbird", 144 | "weeks": 16 145 | }, 146 | { 147 | "artist": "Various Artists", 148 | "image": "https://charts-static.billboard.com/img/2006/05/various-artists-000-legends-of-jazz-with-ramsey-lewis-showcase-eed-53x53.jpg", 149 | "isNew": false, 150 | "lastPos": 17, 151 | "peakPos": 7, 152 | "rank": 15, 153 | "title": "Legends Of Jazz With Ramsey Lewis: Showcase", 154 | "weeks": 13 155 | }, 156 | { 157 | "artist": "Dr. John", 158 | "image": "https://charts-static.billboard.com/img/2006/06/dr-john-a6p-mercernary-ano-53x53.jpg", 159 | "isNew": false, 160 | "lastPos": 13, 161 | "peakPos": 5, 162 | "rank": 16, 163 | "title": "Mercernary", 164 | "weeks": 9 165 | }, 166 | { 167 | "artist": "Frank Catalano", 168 | "image": "https://charts-static.billboard.com/img/2006/05/frank-catalano-d7s-mighty-burner-pug-53x53.jpg", 169 | "isNew": false, 170 | "lastPos": 16, 171 | "peakPos": 11, 172 | "rank": 17, 173 | "title": "Mighty Burner", 174 | "weeks": 12 175 | }, 176 | { 177 | "artist": "Louis Armstrong", 178 | "image": "https://charts-static.billboard.com/img/2006/02/louis-armstrong-6ue-the-definitive-collection-23l-53x53.jpg", 179 | "isNew": false, 180 | "lastPos": 18, 181 | "peakPos": 8, 182 | "rank": 18, 183 | "title": "The Definitive Collection", 184 | "weeks": 26 185 | }, 186 | { 187 | "artist": "Dianne Reeves", 188 | "image": "https://charts-static.billboard.com/img/2005/10/dianne-reeves-hdg-good-night-and-good-luck-soundtrack-8c7-53x53.jpg", 189 | "isNew": false, 190 | "lastPos": 15, 191 | "peakPos": 4, 192 | "rank": 19, 193 | "title": "Good Night, And Good Luck. (Soundtrack)", 194 | "weeks": 41 195 | }, 196 | { 197 | "artist": "Brad Mehldau Trio", 198 | "image": "https://charts-static.billboard.com/img/2006/07/brad-mehldau-urs-house-on-hill-t6f-53x53.jpg", 199 | "isNew": false, 200 | "lastPos": 14, 201 | "peakPos": 14, 202 | "rank": 20, 203 | "title": "House On Hill", 204 | "weeks": 4 205 | }, 206 | { 207 | "artist": "Gordon Goodwin's Big Phat Band", 208 | "image": "https://charts-static.billboard.com/img/2006/07/gordon-goodwins-big-phat-band-428-the-phat-pack-ugd-53x53.jpg", 209 | "isNew": false, 210 | "lastPos": 19, 211 | "peakPos": 12, 212 | "rank": 21, 213 | "title": "The Phat Pack", 214 | "weeks": 6 215 | }, 216 | { 217 | "artist": "DeJohnette/Goldings/Scofield", 218 | "image": "https://charts-static.billboard.com/img/2006/06/dejohnette-goldings-scofield-000-trio-beyond-saudades-03p-53x53.jpg", 219 | "isNew": false, 220 | "lastPos": 20, 221 | "peakPos": 15, 222 | "rank": 22, 223 | "title": "Trio Beyond: Saudades", 224 | "weeks": 4 225 | }, 226 | { 227 | "artist": "Various Artists", 228 | "image": "https://charts-static.billboard.com/img/2005/12/various-artists-000-our-new-orleans-2005-a-benefit-album-wnw-53x53.jpg", 229 | "isNew": false, 230 | "lastPos": 23, 231 | "peakPos": 5, 232 | "rank": 23, 233 | "title": "Our New Orleans 2005: A Benefit Album", 234 | "weeks": 33 235 | }, 236 | { 237 | "artist": "Yellowjackets", 238 | "image": "https://charts-static.billboard.com/img/2006/06/yellowjackets-t0s-twenty-five-71u-53x53.jpg", 239 | "isNew": false, 240 | "lastPos": 21, 241 | "peakPos": 14, 242 | "rank": 24, 243 | "title": "Twenty Five", 244 | "weeks": 9 245 | }, 246 | { 247 | "artist": "Regina Carter", 248 | "image": "https://charts-static.billboard.com/img/2006/07/regina-carter-kjt-ill-be-seeing-you-frs-53x53.jpg", 249 | "isNew": false, 250 | "lastPos": 22, 251 | "peakPos": 18, 252 | "rank": 25, 253 | "title": "I'll Be Seeing You", 254 | "weeks": 6 255 | } 256 | ], 257 | "name": "traditional-jazz-albums", 258 | "nextDate": "2006-08-12", 259 | "previousDate": "2006-07-29", 260 | "title": "Traditional Jazz Albums" 261 | } 262 | -------------------------------------------------------------------------------- /billboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import datetime 4 | import json 5 | import re 6 | import sys 7 | import warnings 8 | 9 | from bs4 import BeautifulSoup 10 | import requests 11 | 12 | """billboard.py: Unofficial Python API for accessing music charts from Billboard.com.""" 13 | 14 | __author__ = "Allen Guo" 15 | __license__ = "MIT" 16 | __maintainer__ = "Allen Guo" 17 | __email__ = "guoguo12@gmail.com" 18 | 19 | 20 | # css selector constants 21 | _CHART_NAME_SELECTOR = 'meta[property="og:title"]' 22 | _DATE_ELEMENT_SELECTOR = "button.chart-detail-header__date-selector-button" 23 | _PREVIOUS_DATE_SELECTOR = "span.fa-chevron-left" 24 | _NEXT_DATE_SELECTOR = "span.fa-chevron-right" 25 | _ENTRY_LIST_SELECTOR = "div.chart-list-item" 26 | _ENTRY_TITLE_ATTR = "data-title" 27 | _ENTRY_ARTIST_ATTR = "data-artist" 28 | _ENTRY_IMAGE_SELECTOR = "img.chart-list-item__image" 29 | _ENTRY_RANK_ATTR = "data-rank" 30 | # On new style pages, the column headings of chart data 31 | # Used to determine if the "Award" column is present in the chart. 32 | _CHART_HEADER_CELLS = "div.o-chart-results-list-header__item span" 33 | 34 | # constants for the getMinistatsCellValue helper function 35 | _MINISTATS_CELL = "div.chart-list-item__ministats-cell" 36 | _MINISTATS_CELL_HEADING = "span.chart-list-item__ministats-cell-heading" 37 | 38 | 39 | class BillboardNotFoundException(Exception): 40 | pass 41 | 42 | 43 | class BillboardParseException(Exception): 44 | pass 45 | 46 | 47 | class UnsupportedYearWarning(UserWarning): 48 | pass 49 | 50 | 51 | class ChartEntry: 52 | """Represents an entry (typically a single track) on a chart. 53 | 54 | Attributes: 55 | title: The title of the track. 56 | artist: The name of the track artist, as formatted on Billboard.com. 57 | If there are multiple artists and/or featured artists, they will 58 | be included in this string. 59 | image: The URL of the image for the track. 60 | peakPos: The track's peak position on the chart as of the chart date, 61 | as an int (or None if the chart does not include this information). 62 | lastPos: The track's position on the previous week's chart, as an int 63 | (or None if the chart does not include this information). 64 | This value is 0 if the track was not on the previous week's chart. 65 | weeks: The number of weeks the track has been or was on the chart, 66 | including future dates (up until the present time). 67 | rank: The track's position on the chart, as an int. 68 | isNew: Whether the track is new to the chart, as a boolean. 69 | """ 70 | 71 | def __init__(self, title, artist, image, peakPos, lastPos, weeks, rank, isNew): 72 | self.title = title 73 | self.artist = artist 74 | self.image = image 75 | self.peakPos = peakPos 76 | self.lastPos = lastPos 77 | self.weeks = weeks 78 | self.rank = rank 79 | self.isNew = isNew 80 | 81 | def __repr__(self): 82 | return "{}.{}(title={!r}, artist={!r})".format( 83 | self.__class__.__module__, self.__class__.__name__, self.title, self.artist 84 | ) 85 | 86 | def __str__(self): 87 | """Returns a string of the form 'TITLE by ARTIST'.""" 88 | if self.title: 89 | s = u"'%s' by %s" % (self.title, self.artist) 90 | else: 91 | s = u"%s" % self.artist 92 | 93 | if sys.version_info.major < 3: 94 | return s.encode(getattr(sys.stdout, "encoding", "") or "utf8") 95 | else: 96 | return s 97 | 98 | def json(self): 99 | """Returns the entry as a JSON string. 100 | This is useful for caching. 101 | """ 102 | return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) 103 | 104 | 105 | class YearEndChartEntry(ChartEntry): 106 | """Represents an entry (typically a single track) on a year-end chart. 107 | 108 | Attributes: 109 | title: The title of the track. 110 | artist: The name of the track artist, as formatted on Billboard.com. 111 | If there are multiple artists and/or featured artists, they will 112 | be included in this string. 113 | image: The URL of the image for the track. 114 | rank: The track's position on the chart, as an int. 115 | year: The chart's year, as an int. 116 | """ 117 | 118 | def __init__(self, title, artist, image, rank): 119 | self.title = title 120 | self.artist = artist 121 | self.image = image 122 | self.rank = rank 123 | 124 | 125 | class ChartData: 126 | """Represents a particular Billboard chart for a particular date. 127 | 128 | Attributes: 129 | name: The chart name, as a string. 130 | title: The human-readable chart name, as a string. 131 | date: The date of the chart. 132 | previousDate: Deprecated. Is always None or an empty string. 133 | nextDate: Deprecated. Is always None or an empty string. 134 | entries: A list of ChartEntry objects, ordered by position on the chart 135 | (highest first). 136 | """ 137 | 138 | def __init__( 139 | self, name, date=None, year=None, fetch=True, max_retries=5, timeout=25 140 | ): 141 | """Constructs a new ChartData instance. 142 | 143 | Args: 144 | name: The chart name, e.g. 'hot-100' or 'pop-songs'. 145 | date: The chart date, as a string in YYYY-MM-DD format. 146 | By default, the latest chart is fetched. 147 | If the argument is not a date on which a chart was published, 148 | Billboard automatically rounds dates up to the nearest date on 149 | which a chart was published. 150 | If this argument is invalid, no exception will be raised; 151 | instead, the chart will contain no entries. Cannot supply 152 | both `date` and `year`. 153 | year: The chart year, if requesting a year-end chart. Must 154 | be a string in YYYY format. Cannot supply both `date` 155 | and `year`. 156 | fetch: A boolean indicating whether to fetch the chart data from 157 | Billboard.com immediately (at instantiation time). 158 | If False, the chart data can be populated at a later time 159 | using the fetchEntries() method. 160 | max_retries: The max number of times to retry when requesting data 161 | (default: 5). 162 | timeout: The number of seconds to wait for a server response. 163 | If None, no timeout is applied. 164 | """ 165 | self.name = name 166 | 167 | # Check if the user supplied both a date and a year (they can't) 168 | if sum(map(bool, [date, year])) >= 2: 169 | raise ValueError("Can't supply both `date` and `year`.") 170 | 171 | if date is not None: 172 | if not re.match(r"\d{4}-\d{2}-\d{2}", str(date)): 173 | raise ValueError("Date argument is not in YYYY-MM-DD format") 174 | try: 175 | datetime.datetime(*(int(x) for x in str(date).split("-"))) 176 | except: 177 | raise ValueError("Date argument is invalid") 178 | 179 | if year is not None: 180 | if not re.match(r"\d{4}", str(year)): 181 | raise ValueError("Year argument is not in YYYY format") 182 | 183 | self.date = date 184 | self.year = year 185 | self.title = "" 186 | 187 | self._max_retries = max_retries 188 | self._timeout = timeout 189 | 190 | self.entries = [] 191 | if fetch: 192 | self.fetchEntries() 193 | 194 | def __repr__(self): 195 | if self.year: 196 | return "{}.{}({!r}, year={!r})".format( 197 | self.__class__.__module__, self.__class__.__name__, self.name, self.year 198 | ) 199 | return "{}.{}({!r}, date={!r})".format( 200 | self.__class__.__module__, self.__class__.__name__, self.name, self.date 201 | ) 202 | 203 | def __str__(self): 204 | """Returns the chart as a human-readable string (typically multi-line).""" 205 | if self.year: 206 | s = "%s chart (%s)" % (self.name, self.year) 207 | elif not self.date: 208 | s = "%s chart (current)" % self.name 209 | else: 210 | s = "%s chart from %s" % (self.name, self.date) 211 | s += "\n" + "-" * len(s) 212 | for n, entry in enumerate(self.entries): 213 | s += "\n%s. %s" % (entry.rank, str(entry)) 214 | return s 215 | 216 | def __getitem__(self, key): 217 | """Returns the (key + 1)-th chart entry; i.e., chart[0] refers to the 218 | top entry on the chart. 219 | """ 220 | return self.entries[key] 221 | 222 | def __len__(self): 223 | """Returns the number of entries in the chart. 224 | A length of zero may indicated a failed/bad request. 225 | """ 226 | return len(self.entries) 227 | 228 | def json(self): 229 | """Returns the entry as a JSON string. 230 | This is useful for caching. 231 | """ 232 | return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) 233 | 234 | # TODO: As of 2021-11-20, this doesn't seem to be used anymore, since 235 | # Billboard has made their styling consistent across charts. 236 | def _parseOldStylePage(self, soup): 237 | dateElement = soup.select_one(_DATE_ELEMENT_SELECTOR) 238 | if dateElement: 239 | dateText = dateElement.text.strip() 240 | curDate = datetime.datetime.strptime(dateText, "%B %d, %Y") 241 | self.date = curDate.strftime("%Y-%m-%d") 242 | 243 | prevWeek = soup.select_one(_PREVIOUS_DATE_SELECTOR) 244 | nextWeek = soup.select_one(_NEXT_DATE_SELECTOR) 245 | if prevWeek and prevWeek.parent.get("href"): 246 | self.previousDate = prevWeek.parent.get("href").split("/")[-1] 247 | else: 248 | self.previousDate = "" 249 | if nextWeek and nextWeek.parent.get("href"): 250 | self.nextDate = nextWeek.parent.get("href").split("/")[-1] 251 | else: 252 | self.nextDate = "" 253 | 254 | for entrySoup in soup.select(_ENTRY_LIST_SELECTOR): 255 | try: 256 | title = entrySoup[_ENTRY_TITLE_ATTR].strip() 257 | except: 258 | message = "Failed to parse title" 259 | raise BillboardParseException(message) 260 | 261 | try: 262 | artist = entrySoup[_ENTRY_ARTIST_ATTR].strip() or "" 263 | except: 264 | message = "Failed to parse artist" 265 | raise BillboardParseException(message) 266 | 267 | if artist == "": 268 | title, artist = artist, title 269 | 270 | try: 271 | imageSoup = entrySoup.select_one(_ENTRY_IMAGE_SELECTOR) 272 | if imageSoup.has_attr("data-src"): 273 | image = imageSoup["data-src"] 274 | else: 275 | image = imageSoup["src"] 276 | except: 277 | message = "Failed to parse image" 278 | raise BillboardParseException(message) 279 | 280 | try: 281 | rank = int(entrySoup[_ENTRY_RANK_ATTR].strip()) 282 | except: 283 | message = "Failed to parse rank" 284 | raise BillboardParseException(message) 285 | 286 | if self.date: 287 | 288 | # "Ministats" is the name in the Billboard.com source code for 289 | # the stats under each chart entry 290 | def getMinistatsCellValue(fieldName, ifNoValue=None): 291 | try: 292 | for ministat in entrySoup.select(_MINISTATS_CELL): 293 | heading = ministat.select_one(_MINISTATS_CELL_HEADING) 294 | headingText = heading.string.strip().lower() 295 | if headingText == fieldName: 296 | value = ministat.text.split(u"\xa0")[0].strip() 297 | if value is None or value == "-": 298 | return ifNoValue 299 | else: 300 | return int(value) 301 | return ifNoValue 302 | except Exception as e: 303 | print(e) 304 | message = "Failed to parse ministats cell value: %s" % fieldName 305 | raise BillboardParseException(message) 306 | 307 | peakPos = getMinistatsCellValue("peak") 308 | lastPos = getMinistatsCellValue("last", ifNoValue=0) 309 | weeks = getMinistatsCellValue("weeks", ifNoValue=1) 310 | isNew = True if weeks == 1 else False 311 | else: 312 | peakPos = lastPos = weeks = None 313 | isNew = False 314 | 315 | entry = ChartEntry( 316 | title, artist, image, peakPos, lastPos, weeks, rank, isNew 317 | ) 318 | self.entries.append(entry) 319 | 320 | def _pageHasAwardColumn(self, soup): 321 | cols = soup.select(_CHART_HEADER_CELLS) 322 | for span in cols: 323 | if "award" in span.string.lower(): 324 | return True 325 | return False 326 | 327 | def _parseNewStylePage(self, soup): 328 | dateElement = soup.select_one("#chart-date-picker") 329 | if dateElement: 330 | self.date = dateElement["data-date"] 331 | 332 | # TODO: Fix these, if possible. These used to be exposed directly in the 333 | # HTML. There were tests for these, removed in 334 | # 4d2f4ad06719341faa0bc2df5568c6354d66fc3c. If this is ever fixed, 335 | # update the documentation above. 336 | self.previousDate = None 337 | self.nextDate = None 338 | 339 | for entrySoup in soup.select("ul.o-chart-results-list-row"): 340 | 341 | def getEntryAttr(which_li, selector): 342 | element = entrySoup.select("li")[which_li].select_one(selector) 343 | if element: 344 | return element.text.strip() 345 | return None 346 | 347 | try: 348 | title = getEntryAttr(3, "#title-of-a-story") 349 | except: 350 | message = "Failed to parse title" 351 | raise BillboardParseException(message) 352 | 353 | try: 354 | artist = getEntryAttr(3, "#title-of-a-story + span.c-label") or "" 355 | except: 356 | message = "Failed to parse artist" 357 | raise BillboardParseException(message) 358 | 359 | # For artist charts like the Artist 100 360 | if artist == "": 361 | title, artist = artist, title 362 | 363 | imageElement = entrySoup.select_one("li:nth-child(2) img") 364 | if imageElement: 365 | image = imageElement.get("data-lazy-src", None) 366 | else: 367 | image = None 368 | 369 | try: 370 | rank = int(getEntryAttr(0, "span.c-label")) 371 | except: 372 | message = "Failed to parse rank" 373 | raise BillboardParseException(message) 374 | 375 | def getMeta(attribute, which_li, ifNoValue=None): 376 | try: 377 | selected = entrySoup.select_one("ul").select("li")[which_li] 378 | 379 | if not selected: 380 | return ifNoValue 381 | 382 | value = selected.text.strip() 383 | if value == "-": 384 | return ifNoValue 385 | else: 386 | return int(value) 387 | except: 388 | message = "Failed to parse metadata value: %s" % attribute 389 | raise BillboardParseException(message) 390 | 391 | # Some pages do not show an award column in their chart data. 392 | # If missing, this changes the column number offsets. 393 | awardColumnOffset = 0 if self._pageHasAwardColumn(soup) else -1 394 | if self.date: 395 | peakPos = getMeta("peak", 4 + awardColumnOffset) 396 | lastPos = getMeta("last", 3 + awardColumnOffset, ifNoValue=0) 397 | weeks = getMeta("week", 5 + awardColumnOffset, ifNoValue=1) 398 | isNew = True if weeks == 1 else False 399 | else: 400 | peakPos = lastPos = weeks = None 401 | isNew = False 402 | 403 | entry = ChartEntry( 404 | title, artist, image, peakPos, lastPos, weeks, rank, isNew 405 | ) 406 | self.entries.append(entry) 407 | 408 | def _parseYearEndPage(self, soup): 409 | # This is for consistency with Billboard.com's former title style 410 | self.title += " - Year-End" 411 | 412 | # Determine the next and previous year-end chart 413 | years = [ 414 | int(li.text.strip()) for li in soup.select("div.a-chart-o-nav-left ul li") 415 | ] 416 | current_year = int(self.year) 417 | min_year, max_year = min(years), max(years) 418 | if current_year in years: 419 | self.previousYear = ( 420 | str(current_year - 1) if current_year > min_year else None 421 | ) 422 | self.nextYear = str(current_year + 1) if current_year < max_year else None 423 | else: 424 | # Warn the user about having requested an unsupported year. 425 | msg = """ 426 | %s is not a supported year-end chart from Billboard. 427 | Results may be incomplete, inconsistent, or missing entirely. 428 | The min and max supported years for the '%s' chart are %d and %d, respectively. 429 | """ % ( 430 | current_year, 431 | self.name, 432 | min_year, 433 | max_year, 434 | ) 435 | warnings.warn(UnsupportedYearWarning(msg)) 436 | 437 | # Assign next and previous years (can be non-null if outside by 1) 438 | if current_year in [min_year - 1, max_year + 1]: 439 | self.nextYear = min_year if current_year < min_year else None 440 | self.previousYear = max_year if current_year > max_year else None 441 | else: 442 | self.previousYear = self.nextYear = None 443 | 444 | # TODO: This is all copied from `_parseNewStylePage` above, but with 445 | # worse error-handling. They should be merged. 446 | for entrySoup in soup.select("ul.o-chart-results-list-row"): 447 | 448 | def getEntryAttr(which_li, selector): 449 | element = entrySoup.select("li")[which_li].select_one(selector) 450 | if element: 451 | return element.text.strip() 452 | return None 453 | 454 | title = getEntryAttr(3, "#title-of-a-story") 455 | artist = getEntryAttr(3, "#title-of-a-story + span.c-label") or "" 456 | if artist == "": 457 | title, artist = artist, title 458 | image = None 459 | rank = int(getEntryAttr(0, "span.c-label")) 460 | 461 | entry = YearEndChartEntry(title, artist, image, rank) 462 | self.entries.append(entry) 463 | 464 | def _parsePage(self, soup): 465 | chartTitleElement = soup.select_one(_CHART_NAME_SELECTOR) 466 | if chartTitleElement: 467 | self.title = re.sub( 468 | " Chart$", 469 | "", 470 | chartTitleElement.get("content", "").split("|")[0].strip(), 471 | ) 472 | 473 | if self.year: 474 | self._parseYearEndPage(soup) 475 | elif soup.select("table"): 476 | self._parseOldStylePage(soup) 477 | else: 478 | self._parseNewStylePage(soup) 479 | 480 | def fetchEntries(self): 481 | """GETs the corresponding chart data from Billboard.com, then parses 482 | the data using BeautifulSoup. 483 | """ 484 | if not self.date: 485 | if not self.year: 486 | # Fetch latest chart 487 | url = "https://www.billboard.com/charts/%s" % (self.name) 488 | else: 489 | url = "https://www.billboard.com/charts/year-end/%s/%s" % ( 490 | self.year, 491 | self.name, 492 | ) 493 | else: 494 | url = "https://www.billboard.com/charts/%s/%s" % (self.name, self.date) 495 | 496 | session = _get_session_with_retries(max_retries=self._max_retries) 497 | req = session.get(url, timeout=self._timeout) 498 | if req.status_code == 404: 499 | message = "Chart not found (perhaps the name is misspelled?)" 500 | raise BillboardNotFoundException(message) 501 | req.raise_for_status() 502 | 503 | soup = BeautifulSoup(req.text, "html.parser") 504 | self._parsePage(soup) 505 | 506 | 507 | def _get_session_with_retries(max_retries): 508 | session = requests.Session() 509 | session.mount( 510 | "https://www.billboard.com", 511 | requests.adapters.HTTPAdapter(max_retries=max_retries), 512 | ) 513 | return session 514 | -------------------------------------------------------------------------------- /tests/1979-08-04-hot-100.json: -------------------------------------------------------------------------------- 1 | { 2 | "_max_retries": 5, 3 | "_timeout": 25, 4 | "date": "1979-08-04", 5 | "entries": [ 6 | { 7 | "artist": "Donna Summer", 8 | "image": null, 9 | "isNew": false, 10 | "lastPos": 1, 11 | "peakPos": 1, 12 | "rank": 1, 13 | "title": "Bad Girls", 14 | "weeks": 11 15 | }, 16 | { 17 | "artist": "Chic", 18 | "image": null, 19 | "isNew": false, 20 | "lastPos": 3, 21 | "peakPos": 2, 22 | "rank": 2, 23 | "title": "Good Times", 24 | "weeks": 8 25 | }, 26 | { 27 | "artist": "Anita Ward", 28 | "image": null, 29 | "isNew": false, 30 | "lastPos": 2, 31 | "peakPos": 1, 32 | "rank": 3, 33 | "title": "Ring My Bell", 34 | "weeks": 13 35 | }, 36 | { 37 | "artist": "Barbra Streisand", 38 | "image": null, 39 | "isNew": false, 40 | "lastPos": 10, 41 | "peakPos": 4, 42 | "rank": 4, 43 | "title": "The Main Event/Fight", 44 | "weeks": 8 45 | }, 46 | { 47 | "artist": "John Stewart", 48 | "image": null, 49 | "isNew": false, 50 | "lastPos": 6, 51 | "peakPos": 5, 52 | "rank": 5, 53 | "title": "Gold", 54 | "weeks": 12 55 | }, 56 | { 57 | "artist": "The Knack", 58 | "image": null, 59 | "isNew": false, 60 | "lastPos": 18, 61 | "peakPos": 6, 62 | "rank": 6, 63 | "title": "My Sharona", 64 | "weeks": 7 65 | }, 66 | { 67 | "artist": "David Naughton", 68 | "image": null, 69 | "isNew": false, 70 | "lastPos": 5, 71 | "peakPos": 5, 72 | "rank": 7, 73 | "title": "Makin' It", 74 | "weeks": 19 75 | }, 76 | { 77 | "artist": "Dr. Hook", 78 | "image": null, 79 | "isNew": false, 80 | "lastPos": 9, 81 | "peakPos": 8, 82 | "rank": 8, 83 | "title": "When You're In Love With A Beautiful Woman", 84 | "weeks": 17 85 | }, 86 | { 87 | "artist": "Donna Summer", 88 | "image": null, 89 | "isNew": false, 90 | "lastPos": 4, 91 | "peakPos": 1, 92 | "rank": 9, 93 | "title": "Hot Stuff", 94 | "weeks": 16 95 | }, 96 | { 97 | "artist": "Cheap Trick", 98 | "image": null, 99 | "isNew": false, 100 | "lastPos": 7, 101 | "peakPos": 7, 102 | "rank": 10, 103 | "title": "I Want You To Want Me", 104 | "weeks": 15 105 | }, 106 | { 107 | "artist": "Raydio", 108 | "image": null, 109 | "isNew": false, 110 | "lastPos": 12, 111 | "peakPos": 11, 112 | "rank": 11, 113 | "title": "You Can't Change That", 114 | "weeks": 15 115 | }, 116 | { 117 | "artist": "Elton John", 118 | "image": null, 119 | "isNew": false, 120 | "lastPos": 16, 121 | "peakPos": 12, 122 | "rank": 12, 123 | "title": "Mama Can't Buy You Love", 124 | "weeks": 9 125 | }, 126 | { 127 | "artist": "KISS", 128 | "image": null, 129 | "isNew": false, 130 | "lastPos": 15, 131 | "peakPos": 13, 132 | "rank": 13, 133 | "title": "I Was Made For Lovin' You", 134 | "weeks": 11 135 | }, 136 | { 137 | "artist": "McFadden & Whitehead", 138 | "image": null, 139 | "isNew": false, 140 | "lastPos": 13, 141 | "peakPos": 13, 142 | "rank": 14, 143 | "title": "Ain't No Stoppin' Us Now", 144 | "weeks": 15 145 | }, 146 | { 147 | "artist": "Electric Light Orchestra", 148 | "image": null, 149 | "isNew": false, 150 | "lastPos": 8, 151 | "peakPos": 8, 152 | "rank": 15, 153 | "title": "Shine A Little Love", 154 | "weeks": 12 155 | }, 156 | { 157 | "artist": "Robert John", 158 | "image": null, 159 | "isNew": false, 160 | "lastPos": 23, 161 | "peakPos": 16, 162 | "rank": 16, 163 | "title": "Sad Eyes", 164 | "weeks": 12 165 | }, 166 | { 167 | "artist": "Maxine Nightingale", 168 | "image": null, 169 | "isNew": false, 170 | "lastPos": 22, 171 | "peakPos": 17, 172 | "rank": 17, 173 | "title": "Lead Me On", 174 | "weeks": 11 175 | }, 176 | { 177 | "artist": "Earth, Wind & Fire With The Emotions", 178 | "image": null, 179 | "isNew": false, 180 | "lastPos": 11, 181 | "peakPos": 6, 182 | "rank": 18, 183 | "title": "Boogie Wonderland", 184 | "weeks": 13 185 | }, 186 | { 187 | "artist": "Atlanta Rhythm Section", 188 | "image": null, 189 | "isNew": false, 190 | "lastPos": 19, 191 | "peakPos": 19, 192 | "rank": 19, 193 | "title": "Do It Or Die", 194 | "weeks": 11 195 | }, 196 | { 197 | "artist": "Wings", 198 | "image": null, 199 | "isNew": false, 200 | "lastPos": 20, 201 | "peakPos": 20, 202 | "rank": 20, 203 | "title": "Getting Closer", 204 | "weeks": 8 205 | }, 206 | { 207 | "artist": "Peter Frampton", 208 | "image": null, 209 | "isNew": false, 210 | "lastPos": 14, 211 | "peakPos": 14, 212 | "rank": 21, 213 | "title": "I Can't Stand It No More", 214 | "weeks": 11 215 | }, 216 | { 217 | "artist": "Earth, Wind & Fire", 218 | "image": null, 219 | "isNew": false, 220 | "lastPos": 31, 221 | "peakPos": 22, 222 | "rank": 22, 223 | "title": "After The Love Has Gone", 224 | "weeks": 5 225 | }, 226 | { 227 | "artist": "Kansas", 228 | "image": null, 229 | "isNew": false, 230 | "lastPos": 24, 231 | "peakPos": 23, 232 | "rank": 23, 233 | "title": "People Of The South Wind", 234 | "weeks": 10 235 | }, 236 | { 237 | "artist": "Blondie", 238 | "image": null, 239 | "isNew": false, 240 | "lastPos": 26, 241 | "peakPos": 24, 242 | "rank": 24, 243 | "title": "One Way Or Another", 244 | "weeks": 10 245 | }, 246 | { 247 | "artist": "Joe Jackson", 248 | "image": null, 249 | "isNew": false, 250 | "lastPos": 27, 251 | "peakPos": 25, 252 | "rank": 25, 253 | "title": "Is She Really Going Out With Him?", 254 | "weeks": 9 255 | }, 256 | { 257 | "artist": "The Charlie Daniels Band", 258 | "image": null, 259 | "isNew": false, 260 | "lastPos": 33, 261 | "peakPos": 26, 262 | "rank": 26, 263 | "title": "The Devil Went Down To Georgia", 264 | "weeks": 7 265 | }, 266 | { 267 | "artist": "Eddie Rabbitt", 268 | "image": null, 269 | "isNew": false, 270 | "lastPos": 30, 271 | "peakPos": 27, 272 | "rank": 27, 273 | "title": "Suspicions", 274 | "weeks": 9 275 | }, 276 | { 277 | "artist": "Kenny Rogers", 278 | "image": null, 279 | "isNew": false, 280 | "lastPos": 17, 281 | "peakPos": 5, 282 | "rank": 28, 283 | "title": "She Believes In Me", 284 | "weeks": 15 285 | }, 286 | { 287 | "artist": "Dionne Warwick", 288 | "image": null, 289 | "isNew": false, 290 | "lastPos": 35, 291 | "peakPos": 29, 292 | "rank": 29, 293 | "title": "I'll Never Love This Way Again", 294 | "weeks": 7 295 | }, 296 | { 297 | "artist": "ABBA", 298 | "image": null, 299 | "isNew": false, 300 | "lastPos": 21, 301 | "peakPos": 19, 302 | "rank": 30, 303 | "title": "Does Your Mother Know", 304 | "weeks": 12 305 | }, 306 | { 307 | "artist": "Anne Murray", 308 | "image": null, 309 | "isNew": false, 310 | "lastPos": 25, 311 | "peakPos": 25, 312 | "rank": 31, 313 | "title": "Shadows In The Moonlight", 314 | "weeks": 11 315 | }, 316 | { 317 | "artist": "Supertramp", 318 | "image": null, 319 | "isNew": false, 320 | "lastPos": 45, 321 | "peakPos": 32, 322 | "rank": 32, 323 | "title": "Goodbye Stranger", 324 | "weeks": 5 325 | }, 326 | { 327 | "artist": "Little River Band", 328 | "image": null, 329 | "isNew": false, 330 | "lastPos": 44, 331 | "peakPos": 33, 332 | "rank": 33, 333 | "title": "Lonesome Loser", 334 | "weeks": 3 335 | }, 336 | { 337 | "artist": "Spyro Gyra", 338 | "image": null, 339 | "isNew": false, 340 | "lastPos": 36, 341 | "peakPos": 34, 342 | "rank": 34, 343 | "title": "Morning Dance", 344 | "weeks": 8 345 | }, 346 | { 347 | "artist": "The Cars", 348 | "image": null, 349 | "isNew": false, 350 | "lastPos": 37, 351 | "peakPos": 35, 352 | "rank": 35, 353 | "title": "Let's Go", 354 | "weeks": 6 355 | }, 356 | { 357 | "artist": "Bonnie Pointer", 358 | "image": null, 359 | "isNew": false, 360 | "lastPos": 39, 361 | "peakPos": 36, 362 | "rank": 36, 363 | "title": "Heaven Must Have Sent You", 364 | "weeks": 8 365 | }, 366 | { 367 | "artist": "Pink Lady", 368 | "image": null, 369 | "isNew": false, 370 | "lastPos": 38, 371 | "peakPos": 37, 372 | "rank": 37, 373 | "title": "Kiss In The Dark", 374 | "weeks": 10 375 | }, 376 | { 377 | "artist": "Blackfoot", 378 | "image": null, 379 | "isNew": false, 380 | "lastPos": 42, 381 | "peakPos": 38, 382 | "rank": 38, 383 | "title": "Highway Song", 384 | "weeks": 7 385 | }, 386 | { 387 | "artist": "Night", 388 | "image": null, 389 | "isNew": false, 390 | "lastPos": 43, 391 | "peakPos": 39, 392 | "rank": 39, 393 | "title": "Hot Summer Nights", 394 | "weeks": 7 395 | }, 396 | { 397 | "artist": "Patrick Hernandez", 398 | "image": null, 399 | "isNew": false, 400 | "lastPos": 47, 401 | "peakPos": 40, 402 | "rank": 40, 403 | "title": "Born To Be Alive", 404 | "weeks": 7 405 | }, 406 | { 407 | "artist": "Electric Light Orchestra", 408 | "image": null, 409 | "isNew": true, 410 | "lastPos": 0, 411 | "peakPos": 41, 412 | "rank": 41, 413 | "title": "Don't Bring Me Down", 414 | "weeks": 1 415 | }, 416 | { 417 | "artist": "The Jones Girls", 418 | "image": null, 419 | "isNew": false, 420 | "lastPos": 46, 421 | "peakPos": 42, 422 | "rank": 42, 423 | "title": "You Gonna Make Me Love Somebody Else", 424 | "weeks": 7 425 | }, 426 | { 427 | "artist": "GQ", 428 | "image": null, 429 | "isNew": false, 430 | "lastPos": 52, 431 | "peakPos": 43, 432 | "rank": 43, 433 | "title": "I Do Love You", 434 | "weeks": 6 435 | }, 436 | { 437 | "artist": "The Marshall Tucker Band", 438 | "image": null, 439 | "isNew": false, 440 | "lastPos": 50, 441 | "peakPos": 44, 442 | "rank": 44, 443 | "title": "Last Of The Singing Cowboys", 444 | "weeks": 6 445 | }, 446 | { 447 | "artist": "Rockets", 448 | "image": null, 449 | "isNew": false, 450 | "lastPos": 57, 451 | "peakPos": 45, 452 | "rank": 45, 453 | "title": "Oh Well", 454 | "weeks": 5 455 | }, 456 | { 457 | "artist": "Peaches & Herb", 458 | "image": null, 459 | "isNew": false, 460 | "lastPos": 51, 461 | "peakPos": 46, 462 | "rank": 46, 463 | "title": "We've Got Love", 464 | "weeks": 6 465 | }, 466 | { 467 | "artist": "Maureen McGovern", 468 | "image": null, 469 | "isNew": false, 470 | "lastPos": 58, 471 | "peakPos": 47, 472 | "rank": 47, 473 | "title": "Different Worlds", 474 | "weeks": 5 475 | }, 476 | { 477 | "artist": "Sister Sledge", 478 | "image": null, 479 | "isNew": false, 480 | "lastPos": 48, 481 | "peakPos": 2, 482 | "rank": 48, 483 | "title": "We Are Family", 484 | "weeks": 15 485 | }, 486 | { 487 | "artist": "Bram Tchaikovsky", 488 | "image": null, 489 | "isNew": false, 490 | "lastPos": 56, 491 | "peakPos": 49, 492 | "rank": 49, 493 | "title": "Girl Of My Dreams", 494 | "weeks": 5 495 | }, 496 | { 497 | "artist": "Robert Palmer", 498 | "image": null, 499 | "isNew": false, 500 | "lastPos": 64, 501 | "peakPos": 50, 502 | "rank": 50, 503 | "title": "Bad Case Of Loving You (Doctor, Doctor)", 504 | "weeks": 3 505 | }, 506 | { 507 | "artist": "James Taylor", 508 | "image": null, 509 | "isNew": false, 510 | "lastPos": 28, 511 | "peakPos": 28, 512 | "rank": 51, 513 | "title": "Up On The Roof", 514 | "weeks": 10 515 | }, 516 | { 517 | "artist": "Diana Ross", 518 | "image": null, 519 | "isNew": false, 520 | "lastPos": 61, 521 | "peakPos": 52, 522 | "rank": 52, 523 | "title": "The Boss", 524 | "weeks": 4 525 | }, 526 | { 527 | "artist": "Triumph", 528 | "image": null, 529 | "isNew": false, 530 | "lastPos": 55, 531 | "peakPos": 53, 532 | "rank": 53, 533 | "title": "Hold On", 534 | "weeks": 8 535 | }, 536 | { 537 | "artist": "Tony Orlando", 538 | "image": null, 539 | "isNew": false, 540 | "lastPos": 63, 541 | "peakPos": 54, 542 | "rank": 54, 543 | "title": "Sweets For My Sweet", 544 | "weeks": 5 545 | }, 546 | { 547 | "artist": "Herman Brood", 548 | "image": null, 549 | "isNew": false, 550 | "lastPos": 67, 551 | "peakPos": 55, 552 | "rank": 55, 553 | "title": "Saturdaynight", 554 | "weeks": 4 555 | }, 556 | { 557 | "artist": "Wet Willie", 558 | "image": null, 559 | "isNew": false, 560 | "lastPos": 29, 561 | "peakPos": 29, 562 | "rank": 56, 563 | "title": "Weekend", 564 | "weeks": 11 565 | }, 566 | { 567 | "artist": "Sniff 'n' the Tears", 568 | "image": null, 569 | "isNew": false, 570 | "lastPos": 72, 571 | "peakPos": 57, 572 | "rank": 57, 573 | "title": "Driver's Seat", 574 | "weeks": 3 575 | }, 576 | { 577 | "artist": "Hot Chocolate", 578 | "image": null, 579 | "isNew": false, 580 | "lastPos": 65, 581 | "peakPos": 58, 582 | "rank": 58, 583 | "title": "Going Through The Motions", 584 | "weeks": 4 585 | }, 586 | { 587 | "artist": "Five Special", 588 | "image": null, 589 | "isNew": false, 590 | "lastPos": 84, 591 | "peakPos": 59, 592 | "rank": 59, 593 | "title": "Why Leave Us Alone", 594 | "weeks": 2 595 | }, 596 | { 597 | "artist": "Hotel", 598 | "image": null, 599 | "isNew": false, 600 | "lastPos": 70, 601 | "peakPos": 60, 602 | "rank": 60, 603 | "title": "You've Got Another Thing Coming", 604 | "weeks": 4 605 | }, 606 | { 607 | "artist": "Poco", 608 | "image": null, 609 | "isNew": false, 610 | "lastPos": 32, 611 | "peakPos": 20, 612 | "rank": 61, 613 | "title": "Heart Of The Night", 614 | "weeks": 13 615 | }, 616 | { 617 | "artist": "Teddy Pendergrass", 618 | "image": null, 619 | "isNew": false, 620 | "lastPos": 73, 621 | "peakPos": 62, 622 | "rank": 62, 623 | "title": "Turn Off The Lights", 624 | "weeks": 3 625 | }, 626 | { 627 | "artist": "Rickie Lee Jones", 628 | "image": null, 629 | "isNew": false, 630 | "lastPos": 74, 631 | "peakPos": 63, 632 | "rank": 63, 633 | "title": "Young Blood", 634 | "weeks": 2 635 | }, 636 | { 637 | "artist": "Blackjack", 638 | "image": null, 639 | "isNew": false, 640 | "lastPos": 75, 641 | "peakPos": 64, 642 | "rank": 64, 643 | "title": "Love Me Tonight", 644 | "weeks": 3 645 | }, 646 | { 647 | "artist": "Journey", 648 | "image": null, 649 | "isNew": false, 650 | "lastPos": 76, 651 | "peakPos": 65, 652 | "rank": 65, 653 | "title": "Lovin', Touchin', Squeezin'", 654 | "weeks": 3 655 | }, 656 | { 657 | "artist": "Funky Communication Committee", 658 | "image": null, 659 | "isNew": false, 660 | "lastPos": 68, 661 | "peakPos": 66, 662 | "rank": 66, 663 | "title": "Baby I Want You", 664 | "weeks": 4 665 | }, 666 | { 667 | "artist": "Dire Straits", 668 | "image": null, 669 | "isNew": false, 670 | "lastPos": 78, 671 | "peakPos": 67, 672 | "rank": 67, 673 | "title": "Lady Writer", 674 | "weeks": 2 675 | }, 676 | { 677 | "artist": "Bonnie Boyer", 678 | "image": null, 679 | "isNew": false, 680 | "lastPos": 80, 681 | "peakPos": 68, 682 | "rank": 68, 683 | "title": "Got To Give In To Love", 684 | "weeks": 2 685 | }, 686 | { 687 | "artist": "Olivia Newton-John", 688 | "image": null, 689 | "isNew": false, 690 | "lastPos": 79, 691 | "peakPos": 69, 692 | "rank": 69, 693 | "title": "Totally Hot", 694 | "weeks": 2 695 | }, 696 | { 697 | "artist": "Lobo", 698 | "image": null, 699 | "isNew": false, 700 | "lastPos": 81, 701 | "peakPos": 70, 702 | "rank": 70, 703 | "title": "Where Were You When I Was Falling In Love", 704 | "weeks": 2 705 | }, 706 | { 707 | "artist": "Oak", 708 | "image": null, 709 | "isNew": false, 710 | "lastPos": 77, 711 | "peakPos": 71, 712 | "rank": 71, 713 | "title": "This Is Love", 714 | "weeks": 3 715 | }, 716 | { 717 | "artist": "Nick Lowe", 718 | "image": null, 719 | "isNew": false, 720 | "lastPos": 82, 721 | "peakPos": 72, 722 | "rank": 72, 723 | "title": "Cruel To Be Kind", 724 | "weeks": 2 725 | }, 726 | { 727 | "artist": "Herb Alpert", 728 | "image": null, 729 | "isNew": false, 730 | "lastPos": 83, 731 | "peakPos": 73, 732 | "rank": 73, 733 | "title": "Rise", 734 | "weeks": 2 735 | }, 736 | { 737 | "artist": "Stephanie Mills", 738 | "image": null, 739 | "isNew": false, 740 | "lastPos": 86, 741 | "peakPos": 74, 742 | "rank": 74, 743 | "title": "What Cha Gonna Do With My Lovin'", 744 | "weeks": 3 745 | }, 746 | { 747 | "artist": "Gerry Rafferty", 748 | "image": null, 749 | "isNew": false, 750 | "lastPos": 34, 751 | "peakPos": 17, 752 | "rank": 75, 753 | "title": "Days Gone Down (Still Got The Light In Your Eyes)", 754 | "weeks": 10 755 | }, 756 | { 757 | "artist": "Rickie Lee Jones", 758 | "image": null, 759 | "isNew": false, 760 | "lastPos": 40, 761 | "peakPos": 4, 762 | "rank": 76, 763 | "title": "Chuck E.'s In Love", 764 | "weeks": 15 765 | }, 766 | { 767 | "artist": "Michael Jackson", 768 | "image": null, 769 | "isNew": false, 770 | "lastPos": 87, 771 | "peakPos": 77, 772 | "rank": 77, 773 | "title": "Don't Stop 'til You Get Enough", 774 | "weeks": 2 775 | }, 776 | { 777 | "artist": "Beckmeier Brothers", 778 | "image": null, 779 | "isNew": false, 780 | "lastPos": 88, 781 | "peakPos": 78, 782 | "rank": 78, 783 | "title": "Rock And Roll Dancin'", 784 | "weeks": 2 785 | }, 786 | { 787 | "artist": "Billy Thorpe", 788 | "image": null, 789 | "isNew": false, 790 | "lastPos": 89, 791 | "peakPos": 79, 792 | "rank": 79, 793 | "title": "Children Of The Sun", 794 | "weeks": 2 795 | }, 796 | { 797 | "artist": "Flash And The Pan", 798 | "image": null, 799 | "isNew": false, 800 | "lastPos": 85, 801 | "peakPos": 80, 802 | "rank": 80, 803 | "title": "Hey, St. Peter", 804 | "weeks": 2 805 | }, 806 | { 807 | "artist": "Bad Company", 808 | "image": null, 809 | "isNew": true, 810 | "lastPos": 0, 811 | "peakPos": 81, 812 | "rank": 81, 813 | "title": "Gone, Gone, Gone", 814 | "weeks": 1 815 | }, 816 | { 817 | "artist": "Cheap Trick", 818 | "image": null, 819 | "isNew": true, 820 | "lastPos": 0, 821 | "peakPos": 82, 822 | "rank": 82, 823 | "title": "Ain't That A Shame", 824 | "weeks": 1 825 | }, 826 | { 827 | "artist": "Switch", 828 | "image": null, 829 | "isNew": false, 830 | "lastPos": 93, 831 | "peakPos": 83, 832 | "rank": 83, 833 | "title": "Best Beat In Town", 834 | "weeks": 3 835 | }, 836 | { 837 | "artist": "Supertramp", 838 | "image": null, 839 | "isNew": false, 840 | "lastPos": 41, 841 | "peakPos": 6, 842 | "rank": 84, 843 | "title": "The Logical Song", 844 | "weeks": 20 845 | }, 846 | { 847 | "artist": "Maynard Ferguson", 848 | "image": null, 849 | "isNew": true, 850 | "lastPos": 0, 851 | "peakPos": 85, 852 | "rank": 85, 853 | "title": "Rocky II Disco", 854 | "weeks": 1 855 | }, 856 | { 857 | "artist": "Mass Production", 858 | "image": null, 859 | "isNew": true, 860 | "lastPos": 0, 861 | "peakPos": 86, 862 | "rank": 86, 863 | "title": "Firecracker", 864 | "weeks": 1 865 | }, 866 | { 867 | "artist": "Van Halen", 868 | "image": null, 869 | "isNew": false, 870 | "lastPos": 49, 871 | "peakPos": 15, 872 | "rank": 87, 873 | "title": "Dance The Night Away", 874 | "weeks": 15 875 | }, 876 | { 877 | "artist": "Samantha Sang", 878 | "image": null, 879 | "isNew": false, 880 | "lastPos": 90, 881 | "peakPos": 88, 882 | "rank": 88, 883 | "title": "In The Midnight Hour", 884 | "weeks": 2 885 | }, 886 | { 887 | "artist": "Michael Johnson", 888 | "image": null, 889 | "isNew": true, 890 | "lastPos": 0, 891 | "peakPos": 89, 892 | "rank": 89, 893 | "title": "This Night Won't Last Forever", 894 | "weeks": 1 895 | }, 896 | { 897 | "artist": "Edwin Starr", 898 | "image": null, 899 | "isNew": true, 900 | "lastPos": 0, 901 | "peakPos": 90, 902 | "rank": 90, 903 | "title": "H.a.p.p.y. Radio", 904 | "weeks": 1 905 | }, 906 | { 907 | "artist": "Jennifer Warnes", 908 | "image": null, 909 | "isNew": false, 910 | "lastPos": 91, 911 | "peakPos": 70, 912 | "rank": 91, 913 | "title": "I Know A Heartache When I See One", 914 | "weeks": 6 915 | }, 916 | { 917 | "artist": "Peaches & Herb", 918 | "image": null, 919 | "isNew": false, 920 | "lastPos": 92, 921 | "peakPos": 1, 922 | "rank": 92, 923 | "title": "Reunited", 924 | "weeks": 21 925 | }, 926 | { 927 | "artist": "Randy VanWarmer", 928 | "image": null, 929 | "isNew": false, 930 | "lastPos": 53, 931 | "peakPos": 4, 932 | "rank": 93, 933 | "title": "Just When I Needed You Most", 934 | "weeks": 20 935 | }, 936 | { 937 | "artist": "The Who", 938 | "image": null, 939 | "isNew": false, 940 | "lastPos": 54, 941 | "peakPos": 54, 942 | "rank": 94, 943 | "title": "Long Live Rock", 944 | "weeks": 6 945 | }, 946 | { 947 | "artist": "Rex Smith", 948 | "image": null, 949 | "isNew": false, 950 | "lastPos": 62, 951 | "peakPos": 10, 952 | "rank": 95, 953 | "title": "You Take My Breath Away", 954 | "weeks": 16 955 | }, 956 | { 957 | "artist": "The Doobie Brothers", 958 | "image": null, 959 | "isNew": false, 960 | "lastPos": 59, 961 | "peakPos": 14, 962 | "rank": 96, 963 | "title": "Minute By Minute", 964 | "weeks": 14 965 | }, 966 | { 967 | "artist": "Jay Ferguson", 968 | "image": null, 969 | "isNew": false, 970 | "lastPos": 60, 971 | "peakPos": 31, 972 | "rank": 97, 973 | "title": "Shakedown Cruise", 974 | "weeks": 14 975 | }, 976 | { 977 | "artist": "Bee Gees", 978 | "image": null, 979 | "isNew": false, 980 | "lastPos": 66, 981 | "peakPos": 1, 982 | "rank": 98, 983 | "title": "Love You Inside Out", 984 | "weeks": 16 985 | }, 986 | { 987 | "artist": "Bellamy Brothers", 988 | "image": null, 989 | "isNew": false, 990 | "lastPos": 69, 991 | "peakPos": 39, 992 | "rank": 99, 993 | "title": "If I Said You Have A Beautiful Body Would You Hold It Against Me", 994 | "weeks": 11 995 | }, 996 | { 997 | "artist": "Dolly Parton", 998 | "image": null, 999 | "isNew": false, 1000 | "lastPos": 71, 1001 | "peakPos": 59, 1002 | "rank": 100, 1003 | "title": "You're The Only One", 1004 | "weeks": 6 1005 | } 1006 | ], 1007 | "name": "hot-100", 1008 | "nextDate": "1979-08-11", 1009 | "previousDate": "1979-07-28", 1010 | "title": "Billboard Hot 100™" 1011 | } 1012 | -------------------------------------------------------------------------------- /tests/2014-08-02-artist-100.json: -------------------------------------------------------------------------------- 1 | { 2 | "_max_retries": 5, 3 | "_timeout": 25, 4 | "date": "2014-08-02", 5 | "entries": [ 6 | { 7 | "artist": "Sam Smith", 8 | "image": "https://charts-static.billboard.com/img/2014/07/sam-smith-9q2-53x53.jpg", 9 | "isNew": false, 10 | "lastPos": 1, 11 | "peakPos": 1, 12 | "rank": 1, 13 | "title": "", 14 | "weeks": 3 15 | }, 16 | { 17 | "artist": "Iggy Azalea", 18 | "image": "https://charts-static.billboard.com/img/2014/05/iggy-azalea-68u-53x53.jpg", 19 | "isNew": false, 20 | "lastPos": 2, 21 | "peakPos": 2, 22 | "rank": 2, 23 | "title": "", 24 | "weeks": 3 25 | }, 26 | { 27 | "artist": "\"Weird Al\" Yankovic", 28 | "image": "https://www.billboard.com/assets/1591809055/images/charts/bb-placeholder-new.jpg?6fa1a4cbdddcf38db268", 29 | "isNew": true, 30 | "lastPos": 0, 31 | "peakPos": 3, 32 | "rank": 3, 33 | "title": "", 34 | "weeks": 1 35 | }, 36 | { 37 | "artist": "MAGIC!", 38 | "image": "https://charts-static.billboard.com/img/2014/07/magic-1-yin-53x53.jpg", 39 | "isNew": false, 40 | "lastPos": 3, 41 | "peakPos": 3, 42 | "rank": 4, 43 | "title": "", 44 | "weeks": 3 45 | }, 46 | { 47 | "artist": "Jason Mraz", 48 | "image": "https://charts-static.billboard.com/img/1840/12/jason-mraz-5w0-53x53.jpg", 49 | "isNew": true, 50 | "lastPos": 0, 51 | "peakPos": 5, 52 | "rank": 5, 53 | "title": "", 54 | "weeks": 1 55 | }, 56 | { 57 | "artist": "5 Seconds Of Summer", 58 | "image": "https://charts-static.billboard.com/img/2013/11/5-seconds-of-summer-deq-53x53.jpg", 59 | "isNew": false, 60 | "lastPos": 25, 61 | "peakPos": 6, 62 | "rank": 6, 63 | "title": "", 64 | "weeks": 3 65 | }, 66 | { 67 | "artist": "Ariana Grande", 68 | "image": "https://charts-static.billboard.com/img/2011/02/ariana-grande-ypy-artist-chart-xj0-53x53.jpg", 69 | "isNew": false, 70 | "lastPos": 6, 71 | "peakPos": 6, 72 | "rank": 7, 73 | "title": "", 74 | "weeks": 3 75 | }, 76 | { 77 | "artist": "Florida Georgia Line", 78 | "image": "https://charts-static.billboard.com/img/2012/12/florida-georgia-line-wbf-53x53.jpg", 79 | "isNew": false, 80 | "lastPos": 4, 81 | "peakPos": 4, 82 | "rank": 8, 83 | "title": "", 84 | "weeks": 3 85 | }, 86 | { 87 | "artist": "Katy Perry", 88 | "image": "https://charts-static.billboard.com/img/2008/12/katy-perry-l8p-53x53.jpg", 89 | "isNew": false, 90 | "lastPos": 9, 91 | "peakPos": 7, 92 | "rank": 9, 93 | "title": "", 94 | "weeks": 3 95 | }, 96 | { 97 | "artist": "Jason Derulo", 98 | "image": "https://charts-static.billboard.com/img/2009/12/jason-derulo-3xq-53x53.jpg", 99 | "isNew": false, 100 | "lastPos": 12, 101 | "peakPos": 9, 102 | "rank": 10, 103 | "title": "", 104 | "weeks": 3 105 | }, 106 | { 107 | "artist": "OneRepublic", 108 | "image": "https://charts-static.billboard.com/img/2017/08/onerepublic-53x53.jpg", 109 | "isNew": false, 110 | "lastPos": 10, 111 | "peakPos": 10, 112 | "rank": 11, 113 | "title": "", 114 | "weeks": 3 115 | }, 116 | { 117 | "artist": "Luke Bryan", 118 | "image": "https://charts-static.billboard.com/img/1840/12/luke-bryan-3d2-53x53.jpg", 119 | "isNew": false, 120 | "lastPos": 11, 121 | "peakPos": 10, 122 | "rank": 12, 123 | "title": "", 124 | "weeks": 3 125 | }, 126 | { 127 | "artist": "Ed Sheeran", 128 | "image": "https://charts-static.billboard.com/img/2012/11/ed-sheeran-buv-artist-chart-914-53x53.jpg", 129 | "isNew": false, 130 | "lastPos": 7, 131 | "peakPos": 5, 132 | "rank": 13, 133 | "title": "", 134 | "weeks": 3 135 | }, 136 | { 137 | "artist": "Maroon 5", 138 | "image": "https://charts-static.billboard.com/img/1840/12/maroon-5-9st-53x53.jpg", 139 | "isNew": false, 140 | "lastPos": 13, 141 | "peakPos": 13, 142 | "rank": 14, 143 | "title": "", 144 | "weeks": 3 145 | }, 146 | { 147 | "artist": "Nico & Vinz", 148 | "image": "https://charts-static.billboard.com/img/2014/07/nico-vinz-0zf-53x53.jpg", 149 | "isNew": false, 150 | "lastPos": 16, 151 | "peakPos": 12, 152 | "rank": 15, 153 | "title": "", 154 | "weeks": 3 155 | }, 156 | { 157 | "artist": "Pharrell Williams", 158 | "image": "https://charts-static.billboard.com/img/2006/12/pharrell-williams-rbx-53x53.jpg", 159 | "isNew": false, 160 | "lastPos": 14, 161 | "peakPos": 11, 162 | "rank": 16, 163 | "title": "", 164 | "weeks": 3 165 | }, 166 | { 167 | "artist": "Charli XCX", 168 | "image": "https://charts-static.billboard.com/img/2013/12/charli-xcx-juq-53x53.jpg", 169 | "isNew": false, 170 | "lastPos": 17, 171 | "peakPos": 15, 172 | "rank": 17, 173 | "title": "", 174 | "weeks": 3 175 | }, 176 | { 177 | "artist": "John Legend", 178 | "image": "https://charts-static.billboard.com/img/2006/12/john-legend-xtz-53x53.jpg", 179 | "isNew": false, 180 | "lastPos": 15, 181 | "peakPos": 15, 182 | "rank": 18, 183 | "title": "", 184 | "weeks": 3 185 | }, 186 | { 187 | "artist": "Rise Against", 188 | "image": "https://charts-static.billboard.com/img/2008/12/rise-against-26z-53x53.jpg", 189 | "isNew": true, 190 | "lastPos": 0, 191 | "peakPos": 19, 192 | "rank": 19, 193 | "title": "", 194 | "weeks": 1 195 | }, 196 | { 197 | "artist": "Trey Songz", 198 | "image": "https://charts-static.billboard.com/img/2017/06/trey-songz-53x53.jpg", 199 | "isNew": false, 200 | "lastPos": 8, 201 | "peakPos": 1, 202 | "rank": 20, 203 | "title": "", 204 | "weeks": 3 205 | }, 206 | { 207 | "artist": "Kidz Bop Kids", 208 | "image": "https://charts-static.billboard.com/img/2006/12/kidz-bop-kids-hda-53x53.jpg", 209 | "isNew": true, 210 | "lastPos": 0, 211 | "peakPos": 21, 212 | "rank": 21, 213 | "title": "", 214 | "weeks": 1 215 | }, 216 | { 217 | "artist": "Sia", 218 | "image": "https://charts-static.billboard.com/img/2012/12/sia-5kn-53x53.jpg", 219 | "isNew": false, 220 | "lastPos": 5, 221 | "peakPos": 5, 222 | "rank": 22, 223 | "title": "", 224 | "weeks": 3 225 | }, 226 | { 227 | "artist": "Drake", 228 | "image": "https://charts-static.billboard.com/img/2009/12/drake-hq6-artist-chart-vz1-53x53.jpg", 229 | "isNew": false, 230 | "lastPos": 33, 231 | "peakPos": 23, 232 | "rank": 23, 233 | "title": "", 234 | "weeks": 3 235 | }, 236 | { 237 | "artist": "Lorde", 238 | "image": "https://charts-static.billboard.com/img/2013/03/lorde-gw0-53x53.jpg", 239 | "isNew": false, 240 | "lastPos": 21, 241 | "peakPos": 21, 242 | "rank": 24, 243 | "title": "", 244 | "weeks": 3 245 | }, 246 | { 247 | "artist": "Eminem", 248 | "image": "https://charts-static.billboard.com/img/2017/02/eminem-53x53.jpg", 249 | "isNew": false, 250 | "lastPos": 23, 251 | "peakPos": 20, 252 | "rank": 25, 253 | "title": "", 254 | "weeks": 3 255 | }, 256 | { 257 | "artist": "Bruno Mars", 258 | "image": "https://charts-static.billboard.com/img/2010/12/bruno-mars-va7-53x53.jpg", 259 | "isNew": false, 260 | "lastPos": 27, 261 | "peakPos": 26, 262 | "rank": 26, 263 | "title": "", 264 | "weeks": 3 265 | }, 266 | { 267 | "artist": "Blake Shelton", 268 | "image": "https://charts-static.billboard.com/img/2017/02/blake-shelton-53x53.jpg", 269 | "isNew": false, 270 | "lastPos": 39, 271 | "peakPos": 27, 272 | "rank": 27, 273 | "title": "", 274 | "weeks": 3 275 | }, 276 | { 277 | "artist": "Calvin Harris", 278 | "image": "https://charts-static.billboard.com/img/2012/12/calvin-harris-s9n-53x53.jpg", 279 | "isNew": false, 280 | "lastPos": 18, 281 | "peakPos": 18, 282 | "rank": 28, 283 | "title": "", 284 | "weeks": 3 285 | }, 286 | { 287 | "artist": "Beyonce", 288 | "image": "https://charts-static.billboard.com/img/2006/12/beyonce-000-artist-chart-cci-53x53.jpg", 289 | "isNew": false, 290 | "lastPos": 28, 291 | "peakPos": 26, 292 | "rank": 29, 293 | "title": "", 294 | "weeks": 3 295 | }, 296 | { 297 | "artist": "Justin Timberlake", 298 | "image": "https://charts-static.billboard.com/img/2006/12/justin-timberlake-bcj-artist-chart-3lf-53x53.jpg", 299 | "isNew": false, 300 | "lastPos": 20, 301 | "peakPos": 20, 302 | "rank": 30, 303 | "title": "", 304 | "weeks": 3 305 | }, 306 | { 307 | "artist": "Imagine Dragons", 308 | "image": "https://charts-static.billboard.com/img/1840/12/imagine-dragons-hy6-53x53.jpg", 309 | "isNew": false, 310 | "lastPos": 24, 311 | "peakPos": 22, 312 | "rank": 31, 313 | "title": "", 314 | "weeks": 3 315 | }, 316 | { 317 | "artist": "Coldplay", 318 | "image": "https://charts-static.billboard.com/img/2017/08/coldplay-53x53.jpg", 319 | "isNew": false, 320 | "lastPos": 26, 321 | "peakPos": 26, 322 | "rank": 32, 323 | "title": "", 324 | "weeks": 3 325 | }, 326 | { 327 | "artist": "Miranda Lambert", 328 | "image": "https://charts-static.billboard.com/img/2016/12/miranda-lambert-53x53.jpg", 329 | "isNew": false, 330 | "lastPos": 19, 331 | "peakPos": 18, 332 | "rank": 33, 333 | "title": "", 334 | "weeks": 3 335 | }, 336 | { 337 | "artist": "Lana Del Rey", 338 | "image": "https://charts-static.billboard.com/img/2011/10/lana-del-rey-902-53x53.jpg", 339 | "isNew": false, 340 | "lastPos": 22, 341 | "peakPos": 16, 342 | "rank": 34, 343 | "title": "", 344 | "weeks": 3 345 | }, 346 | { 347 | "artist": "Shakira", 348 | "image": "https://charts-static.billboard.com/img/2017/06/shakira-53x53.jpg", 349 | "isNew": false, 350 | "lastPos": 36, 351 | "peakPos": 35, 352 | "rank": 35, 353 | "title": "", 354 | "weeks": 3 355 | }, 356 | { 357 | "artist": "Disclosure", 358 | "image": "https://charts-static.billboard.com/img/2013/12/disclosure-g9t-53x53.jpg", 359 | "isNew": false, 360 | "lastPos": 40, 361 | "peakPos": 36, 362 | "rank": 36, 363 | "title": "", 364 | "weeks": 3 365 | }, 366 | { 367 | "artist": "Michael Jackson", 368 | "image": "https://charts-static.billboard.com/img/1840/12/michael-jackson-9to-53x53.jpg", 369 | "isNew": false, 370 | "lastPos": 30, 371 | "peakPos": 30, 372 | "rank": 37, 373 | "title": "", 374 | "weeks": 3 375 | }, 376 | { 377 | "artist": "Brantley Gilbert", 378 | "image": "https://charts-static.billboard.com/img/2017/07/brantley-gilbert-53x53.jpg", 379 | "isNew": false, 380 | "lastPos": 29, 381 | "peakPos": 28, 382 | "rank": 38, 383 | "title": "", 384 | "weeks": 3 385 | }, 386 | { 387 | "artist": "One Direction", 388 | "image": "https://charts-static.billboard.com/img/2011/11/one-direction-0i5-53x53.jpg", 389 | "isNew": false, 390 | "lastPos": 32, 391 | "peakPos": 32, 392 | "rank": 39, 393 | "title": "", 394 | "weeks": 3 395 | }, 396 | { 397 | "artist": "Justin Bieber", 398 | "image": "https://charts-static.billboard.com/img/2009/07/justin-bieber-4oh-53x53.jpg", 399 | "isNew": false, 400 | "lastPos": 34, 401 | "peakPos": 34, 402 | "rank": 40, 403 | "title": "", 404 | "weeks": 3 405 | }, 406 | { 407 | "artist": "Chris Brown", 408 | "image": "https://charts-static.billboard.com/img/2006/12/chris-brown-cte-53x53.jpg", 409 | "isNew": false, 410 | "lastPos": 41, 411 | "peakPos": 37, 412 | "rank": 41, 413 | "title": "", 414 | "weeks": 3 415 | }, 416 | { 417 | "artist": "KONGOS", 418 | "image": "https://charts-static.billboard.com/img/2014/07/kongos-um1-53x53.jpg", 419 | "isNew": false, 420 | "lastPos": 50, 421 | "peakPos": 42, 422 | "rank": 42, 423 | "title": "", 424 | "weeks": 3 425 | }, 426 | { 427 | "artist": "Dierks Bentley", 428 | "image": "https://charts-static.billboard.com/img/1840/12/dierks-bentley-8zd-53x53.jpg", 429 | "isNew": false, 430 | "lastPos": 37, 431 | "peakPos": 37, 432 | "rank": 43, 433 | "title": "", 434 | "weeks": 3 435 | }, 436 | { 437 | "artist": "Lady A", 438 | "image": "https://charts-static.billboard.com/img/2017/07/lady-antebellum-53x53.jpg", 439 | "isNew": false, 440 | "lastPos": 42, 441 | "peakPos": 42, 442 | "rank": 44, 443 | "title": "", 444 | "weeks": 3 445 | }, 446 | { 447 | "artist": "Becky G", 448 | "image": "https://charts-static.billboard.com/img/2014/07/becky-g-owi-53x53.jpg", 449 | "isNew": false, 450 | "lastPos": 54, 451 | "peakPos": 45, 452 | "rank": 45, 453 | "title": "", 454 | "weeks": 3 455 | }, 456 | { 457 | "artist": "Wiz Khalifa", 458 | "image": "https://charts-static.billboard.com/img/2010/12/wiz-khalifa-b1e-53x53.jpg", 459 | "isNew": false, 460 | "lastPos": 58, 461 | "peakPos": 46, 462 | "rank": 46, 463 | "title": "", 464 | "weeks": 3 465 | }, 466 | { 467 | "artist": "Enrique Iglesias", 468 | "image": "https://charts-static.billboard.com/img/2017/03/enrique-iglesias-53x53.jpg", 469 | "isNew": false, 470 | "lastPos": 55, 471 | "peakPos": 47, 472 | "rank": 47, 473 | "title": "", 474 | "weeks": 3 475 | }, 476 | { 477 | "artist": "Kenny Chesney", 478 | "image": "https://charts-static.billboard.com/img/2017/06/kenny-chesney-53x53.jpg", 479 | "isNew": false, 480 | "lastPos": 44, 481 | "peakPos": 44, 482 | "rank": 48, 483 | "title": "", 484 | "weeks": 3 485 | }, 486 | { 487 | "artist": "Paramore", 488 | "image": "https://charts-static.billboard.com/img/2008/12/paramore-teo-53x53.jpg", 489 | "isNew": false, 490 | "lastPos": 38, 491 | "peakPos": 35, 492 | "rank": 49, 493 | "title": "", 494 | "weeks": 3 495 | }, 496 | { 497 | "artist": "Jake Owen", 498 | "image": "https://charts-static.billboard.com/img/2006/12/jake-owen-17q-53x53.jpg", 499 | "isNew": false, 500 | "lastPos": 47, 501 | "peakPos": 41, 502 | "rank": 50, 503 | "title": "", 504 | "weeks": 3 505 | }, 506 | { 507 | "artist": "Bleachers", 508 | "image": "https://charts-static.billboard.com/img/2014/08/bleachers-nw9-53x53.jpg", 509 | "isNew": true, 510 | "lastPos": 0, 511 | "peakPos": 51, 512 | "rank": 51, 513 | "title": "", 514 | "weeks": 1 515 | }, 516 | { 517 | "artist": "Demi Lovato", 518 | "image": "https://charts-static.billboard.com/img/2009/12/demi-lovato-sad-artist-chart-nkm-53x53.jpg", 519 | "isNew": false, 520 | "lastPos": 49, 521 | "peakPos": 44, 522 | "rank": 52, 523 | "title": "", 524 | "weeks": 3 525 | }, 526 | { 527 | "artist": "Bastille", 528 | "image": "https://charts-static.billboard.com/img/2013/12/bastille-p0b-53x53.jpg", 529 | "isNew": false, 530 | "lastPos": 48, 531 | "peakPos": 45, 532 | "rank": 53, 533 | "title": "", 534 | "weeks": 3 535 | }, 536 | { 537 | "artist": "MKTO", 538 | "image": "https://charts-static.billboard.com/img/2014/07/mkto-0le-53x53.jpg", 539 | "isNew": false, 540 | "lastPos": 46, 541 | "peakPos": 43, 542 | "rank": 54, 543 | "title": "", 544 | "weeks": 3 545 | }, 546 | { 547 | "artist": "ScHoolboy Q", 548 | "image": "https://charts-static.billboard.com/img/2014/07/schoolboy-q-s5u-53x53.jpg", 549 | "isNew": false, 550 | "lastPos": 53, 551 | "peakPos": 52, 552 | "rank": 55, 553 | "title": "", 554 | "weeks": 3 555 | }, 556 | { 557 | "artist": "Pitbull", 558 | "image": "https://charts-static.billboard.com/img/2017/06/pitbull-53x53.jpg", 559 | "isNew": false, 560 | "lastPos": 52, 561 | "peakPos": 48, 562 | "rank": 56, 563 | "title": "", 564 | "weeks": 3 565 | }, 566 | { 567 | "artist": "Nicki Minaj", 568 | "image": "https://charts-static.billboard.com/img/2010/12/nicki-minaj-thi-53x53.jpg", 569 | "isNew": false, 570 | "lastPos": 43, 571 | "peakPos": 42, 572 | "rank": 57, 573 | "title": "", 574 | "weeks": 3 575 | }, 576 | { 577 | "artist": "Lee Brice", 578 | "image": "https://charts-static.billboard.com/img/2010/12/lee-brice-vbm-53x53.jpg", 579 | "isNew": false, 580 | "lastPos": 61, 581 | "peakPos": 58, 582 | "rank": 58, 583 | "title": "", 584 | "weeks": 3 585 | }, 586 | { 587 | "artist": "Avicii", 588 | "image": "https://charts-static.billboard.com/img/1840/12/avicii-32y-53x53.jpg", 589 | "isNew": false, 590 | "lastPos": 62, 591 | "peakPos": 50, 592 | "rank": 59, 593 | "title": "", 594 | "weeks": 3 595 | }, 596 | { 597 | "artist": "Miley Cyrus", 598 | "image": "https://charts-static.billboard.com/img/2017/06/miley-cyrus-53x53.jpg", 599 | "isNew": false, 600 | "lastPos": 35, 601 | "peakPos": 25, 602 | "rank": 60, 603 | "title": "", 604 | "weeks": 3 605 | }, 606 | { 607 | "artist": "American Authors", 608 | "image": "https://charts-static.billboard.com/img/2014/07/american-authors-o02-53x53.jpg", 609 | "isNew": false, 610 | "lastPos": 79, 611 | "peakPos": 61, 612 | "rank": 61, 613 | "title": "", 614 | "weeks": 3 615 | }, 616 | { 617 | "artist": "Eric Church", 618 | "image": "https://charts-static.billboard.com/img/2017/04/eric-church-53x53.jpg", 619 | "isNew": false, 620 | "lastPos": 57, 621 | "peakPos": 56, 622 | "rank": 62, 623 | "title": "", 624 | "weeks": 3 625 | }, 626 | { 627 | "artist": "Linkin Park", 628 | "image": "https://charts-static.billboard.com/img/2017/08/linkin-park-53x53.jpg", 629 | "isNew": false, 630 | "lastPos": 63, 631 | "peakPos": 49, 632 | "rank": 63, 633 | "title": "", 634 | "weeks": 3 635 | }, 636 | { 637 | "artist": "Usher", 638 | "image": "https://charts-static.billboard.com/img/2004/12/usher-oqp-53x53.jpg", 639 | "isNew": false, 640 | "lastPos": 65, 641 | "peakPos": 64, 642 | "rank": 64, 643 | "title": "", 644 | "weeks": 3 645 | }, 646 | { 647 | "artist": "Joe Nichols", 648 | "image": "https://charts-static.billboard.com/img/2006/12/joe-nichols-yxj-53x53.jpg", 649 | "isNew": false, 650 | "lastPos": 66, 651 | "peakPos": 65, 652 | "rank": 65, 653 | "title": "", 654 | "weeks": 3 655 | }, 656 | { 657 | "artist": "Romeo Santos", 658 | "image": "https://charts-static.billboard.com/img/2011/12/romeo-santos-rhp-53x53.jpg", 659 | "isNew": true, 660 | "lastPos": 0, 661 | "peakPos": 66, 662 | "rank": 66, 663 | "title": "", 664 | "weeks": 1 665 | }, 666 | { 667 | "artist": "Selena Gomez", 668 | "image": "https://charts-static.billboard.com/img/2017/07/selena-gomez-53x53.jpg", 669 | "isNew": false, 670 | "lastPos": 92, 671 | "peakPos": 67, 672 | "rank": 67, 673 | "title": "", 674 | "weeks": 2 675 | }, 676 | { 677 | "artist": "Lil Wayne", 678 | "image": "https://charts-static.billboard.com/img/2017/08/lil-wayne-53x53.jpg", 679 | "isNew": false, 680 | "lastPos": 59, 681 | "peakPos": 55, 682 | "rank": 68, 683 | "title": "", 684 | "weeks": 3 685 | }, 686 | { 687 | "artist": "Rihanna", 688 | "image": "https://charts-static.billboard.com/img/2006/12/rihanna-t38-artist-chart-vji-53x53.jpg", 689 | "isNew": false, 690 | "lastPos": 68, 691 | "peakPos": 68, 692 | "rank": 69, 693 | "title": "", 694 | "weeks": 3 695 | }, 696 | { 697 | "artist": "Sara Bareilles", 698 | "image": "https://charts-static.billboard.com/img/1840/12/sara-bareilles-7da-53x53.jpg", 699 | "isNew": true, 700 | "lastPos": 0, 701 | "peakPos": 70, 702 | "rank": 70, 703 | "title": "", 704 | "weeks": 1 705 | }, 706 | { 707 | "artist": "Zac Brown Band", 708 | "image": "https://charts-static.billboard.com/img/2008/06/zac-brown-band-ett.jpg", 709 | "isNew": false, 710 | "lastPos": 60, 711 | "peakPos": 59, 712 | "rank": 71, 713 | "title": "", 714 | "weeks": 3 715 | }, 716 | { 717 | "artist": "Jason Aldean", 718 | "image": "https://charts-static.billboard.com/img/2006/12/jason-aldean-sa0-53x53.jpg", 719 | "isNew": false, 720 | "lastPos": 64, 721 | "peakPos": 64, 722 | "rank": 72, 723 | "title": "", 724 | "weeks": 3 725 | }, 726 | { 727 | "artist": "Kid Ink", 728 | "image": "https://charts-static.billboard.com/img/2014/07/kid-ink-9ar-53x53.jpg", 729 | "isNew": false, 730 | "lastPos": 70, 731 | "peakPos": 70, 732 | "rank": 73, 733 | "title": "", 734 | "weeks": 3 735 | }, 736 | { 737 | "artist": "Colbie Caillat", 738 | "image": "https://charts-static.billboard.com/img/1840/12/colbie-caillat-p3m-53x53.jpg", 739 | "isNew": false, 740 | "lastPos": 94, 741 | "peakPos": 74, 742 | "rank": 74, 743 | "title": "", 744 | "weeks": 2 745 | }, 746 | { 747 | "artist": "Chris Young", 748 | "image": "https://charts-static.billboard.com/img/2017/06/chris-young-53x53.jpg", 749 | "isNew": false, 750 | "lastPos": 76, 751 | "peakPos": 75, 752 | "rank": 75, 753 | "title": "", 754 | "weeks": 3 755 | }, 756 | { 757 | "artist": "Marsha Ambrosius", 758 | "image": "https://charts-static.billboard.com/img/2011/12/marsha-ambrosius-b44-53x53.jpg", 759 | "isNew": true, 760 | "lastPos": 0, 761 | "peakPos": 76, 762 | "rank": 76, 763 | "title": "", 764 | "weeks": 1 765 | }, 766 | { 767 | "artist": "YG", 768 | "image": "https://charts-static.billboard.com/img/2014/07/yg-fg4-53x53.jpg", 769 | "isNew": false, 770 | "lastPos": 73, 771 | "peakPos": 73, 772 | "rank": 77, 773 | "title": "", 774 | "weeks": 3 775 | }, 776 | { 777 | "artist": "Meghan Trainor", 778 | "image": "https://charts-static.billboard.com/img/2014/08/meghan-trainor-vgn-53x53.jpg", 779 | "isNew": true, 780 | "lastPos": 0, 781 | "peakPos": 78, 782 | "rank": 78, 783 | "title": "", 784 | "weeks": 1 785 | }, 786 | { 787 | "artist": "Arctic Monkeys", 788 | "image": "https://charts-static.billboard.com/img/2006/12/arctic-monkeys-a3g-53x53.jpg", 789 | "isNew": false, 790 | "lastPos": 69, 791 | "peakPos": 69, 792 | "rank": 79, 793 | "title": "", 794 | "weeks": 3 795 | }, 796 | { 797 | "artist": "Childish Gambino", 798 | "image": "https://charts-static.billboard.com/img/2011/05/childish-gambino-f0k-53x53.jpg", 799 | "isNew": true, 800 | "lastPos": 0, 801 | "peakPos": 80, 802 | "rank": 80, 803 | "title": "", 804 | "weeks": 1 805 | }, 806 | { 807 | "artist": "Adele", 808 | "image": "https://charts-static.billboard.com/img/2009/12/adele-ee3-artist-chart-nd3-53x53.jpg", 809 | "isNew": false, 810 | "lastPos": 72, 811 | "peakPos": 72, 812 | "rank": 81, 813 | "title": "", 814 | "weeks": 3 815 | }, 816 | { 817 | "artist": "P!nk", 818 | "image": "https://charts-static.billboard.com/img/2017/04/pnk-53x53.jpg", 819 | "isNew": false, 820 | "lastPos": 82, 821 | "peakPos": 82, 822 | "rank": 82, 823 | "title": "", 824 | "weeks": 3 825 | }, 826 | { 827 | "artist": "Tiesto", 828 | "image": "https://charts-static.billboard.com/img/2006/12/tiesto-nm9-53x53.jpg", 829 | "isNew": false, 830 | "lastPos": 80, 831 | "peakPos": 80, 832 | "rank": 83, 833 | "title": "", 834 | "weeks": 3 835 | }, 836 | { 837 | "artist": "Snoop Dogg", 838 | "image": "https://charts-static.billboard.com/img/2017/07/snoop-dogg-53x53.jpg", 839 | "isNew": false, 840 | "lastPos": 67, 841 | "peakPos": 62, 842 | "rank": 84, 843 | "title": "", 844 | "weeks": 3 845 | }, 846 | { 847 | "artist": "Ingrid Michaelson", 848 | "image": "https://charts-static.billboard.com/img/2008/12/ingrid-michaelson-k7e-53x53.jpg", 849 | "isNew": false, 850 | "lastPos": 84, 851 | "peakPos": 84, 852 | "rank": 85, 853 | "title": "", 854 | "weeks": 3 855 | }, 856 | { 857 | "artist": "Tove Lo", 858 | "image": "https://charts-static.billboard.com/img/2014/08/tove-lo-vxl-53x53.jpg", 859 | "isNew": true, 860 | "lastPos": 0, 861 | "peakPos": 86, 862 | "rank": 86, 863 | "title": "", 864 | "weeks": 1 865 | }, 866 | { 867 | "artist": "Cole Swindell", 868 | "image": "https://charts-static.billboard.com/img/2014/07/cole-swindell-lq3-53x53.jpg", 869 | "isNew": false, 870 | "lastPos": 86, 871 | "peakPos": 86, 872 | "rank": 87, 873 | "title": "", 874 | "weeks": 3 875 | }, 876 | { 877 | "artist": "Morrissey", 878 | "image": "https://charts-static.billboard.com/img/2014/08/morrissey-1io-53x53.jpg", 879 | "isNew": true, 880 | "lastPos": 0, 881 | "peakPos": 88, 882 | "rank": 88, 883 | "title": "", 884 | "weeks": 1 885 | }, 886 | { 887 | "artist": "Clean Bandit", 888 | "image": "https://charts-static.billboard.com/img/2014/01/clean-bandit-ywv-53x53.jpg", 889 | "isNew": true, 890 | "lastPos": 0, 891 | "peakPos": 89, 892 | "rank": 89, 893 | "title": "", 894 | "weeks": 1 895 | }, 896 | { 897 | "artist": "Rixton", 898 | "image": "https://charts-static.billboard.com/img/2014/07/rixton-z8y-53x53.jpg", 899 | "isNew": false, 900 | "lastPos": 83, 901 | "peakPos": 77, 902 | "rank": 90, 903 | "title": "", 904 | "weeks": 3 905 | }, 906 | { 907 | "artist": "Crosby, Stills, Nash & Young", 908 | "image": "https://www.billboard.com/assets/1591809055/images/charts/bb-placeholder-new.jpg?6fa1a4cbdddcf38db268", 909 | "isNew": false, 910 | "lastPos": 45, 911 | "peakPos": 45, 912 | "rank": 91, 913 | "title": "", 914 | "weeks": 2 915 | }, 916 | { 917 | "artist": "Lil Jon", 918 | "image": "https://charts-static.billboard.com/img/2006/12/lil-jon-cje-53x53.jpg", 919 | "isNew": false, 920 | "lastPos": 87, 921 | "peakPos": 76, 922 | "rank": 92, 923 | "title": "", 924 | "weeks": 3 925 | }, 926 | { 927 | "artist": "Suicide Silence", 928 | "image": "https://charts-static.billboard.com/img/2012/11/suicide-silence-wm8-53x53.jpg", 929 | "isNew": true, 930 | "lastPos": 0, 931 | "peakPos": 93, 932 | "rank": 93, 933 | "title": "", 934 | "weeks": 1 935 | }, 936 | { 937 | "artist": "AJR", 938 | "image": "https://charts-static.billboard.com/img/2014/05/ajr-k86-53x53.jpg", 939 | "isNew": true, 940 | "lastPos": 0, 941 | "peakPos": 94, 942 | "rank": 94, 943 | "title": "", 944 | "weeks": 1 945 | }, 946 | { 947 | "artist": "Ellie Goulding", 948 | "image": "https://charts-static.billboard.com/img/2012/12/ellie-goulding-xqm-artist-chart-1gk-53x53.jpg", 949 | "isNew": false, 950 | "lastPos": 81, 951 | "peakPos": 81, 952 | "rank": 95, 953 | "title": "", 954 | "weeks": 3 955 | }, 956 | { 957 | "artist": "Led Zeppelin", 958 | "image": "https://charts-static.billboard.com/img/2016/12/led-zeppelin-53x53.jpg", 959 | "isNew": false, 960 | "lastPos": 77, 961 | "peakPos": 63, 962 | "rank": 96, 963 | "title": "", 964 | "weeks": 3 965 | }, 966 | { 967 | "artist": "T.I.", 968 | "image": "https://charts-static.billboard.com/img/2017/07/ti-53x53.jpg", 969 | "isNew": true, 970 | "lastPos": 0, 971 | "peakPos": 97, 972 | "rank": 97, 973 | "title": "", 974 | "weeks": 1 975 | }, 976 | { 977 | "artist": "Carrie Underwood", 978 | "image": "https://charts-static.billboard.com/img/1840/12/carrie-underwood-obd-53x53.jpg", 979 | "isNew": false, 980 | "lastPos": 97, 981 | "peakPos": 89, 982 | "rank": 98, 983 | "title": "", 984 | "weeks": 3 985 | }, 986 | { 987 | "artist": "Sam Hunt", 988 | "image": "https://charts-static.billboard.com/img/2014/08/sam-hunt-2mf-53x53.jpg", 989 | "isNew": true, 990 | "lastPos": 0, 991 | "peakPos": 99, 992 | "rank": 99, 993 | "title": "", 994 | "weeks": 1 995 | }, 996 | { 997 | "artist": "The Black Keys", 998 | "image": "https://charts-static.billboard.com/img/2010/12/the-black-keys-3bo-53x53.jpg", 999 | "isNew": false, 1000 | "lastPos": 91, 1001 | "peakPos": 88, 1002 | "rank": 100, 1003 | "title": "", 1004 | "weeks": 3 1005 | } 1006 | ], 1007 | "name": "artist-100", 1008 | "nextDate": "2014-08-09", 1009 | "previousDate": "2014-07-26", 1010 | "title": "Billboard Artist 100" 1011 | } 1012 | --------------------------------------------------------------------------------