1. The requests module

The module requests makes it very easy to send an HTTP request to a web server. Then, the results that comes from the server is stored as a special object, which organizes the response according to a certain structure specified by the HTTP protocol.

Note: Some Python installations seem to not include the module requests, if that is the case, you can easily install it with the following command.

In [ ]:
!pip install requests
In [1]:
import requests

url = "http://cs111.wellesley.edu/content/info/simple.html"

httpResponse = requests.get(url) # Send the request
type(httpResponse)               # What did we receive?
Out[1]:
requests.models.Response

Exploring the HTTP response

In [2]:
httpResponse # the object
Out[2]:
<Response [200]>
In [3]:
print(dir(httpResponse)) # show all properties or methods of the object
['__attrs__', '__bool__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_content', '_content_consumed', '_next', 'apparent_encoding', 'close', 'connection', 'content', 'cookies', 'elapsed', 'encoding', 'headers', 'history', 'is_permanent_redirect', 'is_redirect', 'iter_content', 'iter_lines', 'json', 'links', 'next', 'ok', 'raise_for_status', 'raw', 'reason', 'request', 'status_code', 'text', 'url']
In [4]:
httpResponse.status_code # show status of the HTTP response. For example, 200 means OK, 404, means Page not found.
Out[4]:
200
In [5]:
httpResponse.url # show url from which the content was received
Out[5]:
'http://cs111.wellesley.edu/content/info/simple.html'
In [6]:
httpResponse.headers # show metainformation about the page
Out[6]:
{'Date': 'Mon, 22 Nov 2021 10:33:37 GMT', 'Server': 'Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_auth_gssapi/1.5.1 mod_wsgi/3.4 Python/2.7.5 PHP/7.3.33 mod_perl/2.0.11 Perl/v5.16.3', 'Last-Modified': 'Mon, 07 Jun 2021 21:07:38 GMT', 'ETag': '"20d-5c4336e71d788"', 'Accept-Ranges': 'bytes', 'Content-Length': '525', 'Keep-Alive': 'timeout=5, max=100', 'Connection': 'Keep-Alive', 'Content-Type': 'text/html'}

There are two properties of the object that are useful to retrieve the content of the response:

In [7]:
httpResponse.content
Out[7]:
b'<!DOCTYPE html>\n<html lang="">\n<head>\n  <meta charset="UTF-8">\n  <meta name="viewport" content="width=device-width, initial-scale=1.0">\n  <title></title>\n</head>\n\n<body>\n  <h1>Welcome to CS111!</h1>\n  <h2>Learn about Web APIs</h2>\n  \n  <p>Today our topic is how to use Web APIs in our programs.</p>\n  \n  <p>Some examples of Web APIs are:</p>\n  \n  <ol>\n    <li>OpenWeatherMap API</li>\n    <li>Google Books API</li>\n    <li>Google Maps API</li>\n    <li>Twitter API</li>\n    <li>Facebook Graph API</li>\n\n  </ol>\n</body>\n</html>\n'
In [8]:
httpResponse.text
Out[8]:
'<!DOCTYPE html>\n<html lang="">\n<head>\n  <meta charset="UTF-8">\n  <meta name="viewport" content="width=device-width, initial-scale=1.0">\n  <title></title>\n</head>\n\n<body>\n  <h1>Welcome to CS111!</h1>\n  <h2>Learn about Web APIs</h2>\n  \n  <p>Today our topic is how to use Web APIs in our programs.</p>\n  \n  <p>Some examples of Web APIs are:</p>\n  \n  <ol>\n    <li>OpenWeatherMap API</li>\n    <li>Google Books API</li>\n    <li>Google Maps API</li>\n    <li>Twitter API</li>\n    <li>Facebook Graph API</li>\n\n  </ol>\n</body>\n</html>\n'

These look the same, are they the same?

In [9]:
httpResponse.content == httpResponse.text
Out[9]:
False

Let's check the types:

In [10]:
type(httpResponse.content)
Out[10]:
bytes
In [11]:
type(httpResponse.text)
Out[11]:
str

The first type is a new Python type, bytes. It exists because often the files that we will be requesting are not text files, but some other type such as PNG, JPEG, GIF, PDF, etc. We can see the difference with the example of a GIF file, which we will download from this link.

In [12]:
url2 = "http://cs111.wellesley.edu/content/info/wait_32.gif"
httpResponse2 = requests.get(url2)
httpResponse2
Out[12]:
<Response [200]>
In [13]:
httpResponse2.content
Out[13]:
b'GIF89a \x00 \x00\xa1\x01\x002\x87\xd2\x82\x82\x82\x82\x82\x82\x82\x82\x82!\xff\x0bNETSCAPE2.0\x03\x01\x00\x00\x00!\xf9\x04\t\x14\x00\x02\x00,\x00\x00\x00\x00 \x00 \x00\x00\x02i\x84\x8f(\x1b\xed\xffD\x9ag\t\x88\x9d\xa4\xd3\xe6\xbcq\n\xf3A\xa1hxe\x84\x8e\xd7\xaa\xb5\x15\t\x07\'\xaa\xd6\xb7\x98\xc3;\xd7[\xfd(\xc1\xd2\xb0C\xf3\xc9RI\xe1\x12P\xfcX\xa6\xd4\xaa\xf5\x8a\xcdj\xb7\\l\xad\x11\x05}mM\xe38\x8cy\xe9\xca\xd23[\xfcE\x9b\xdcj\x1f\xfd|\x8f\xe7u{{\xfc\x9d\xd6\'$h\xd4ehQ\x00\x00!\xf9\x04\t\x14\x00\x03\x00,\x00\x00\x00\x00\x1f\x00\x1f\x00\x00\x02X\x9c\x8fi\xc0\xed\xae"z\xb4\xc9[\xeb\x95\x99\xee\xd8=\x9f\x12B\xe3T2\'\x9a\xaeG\xaa\xba\x03\x0c\xc83l\xd39.\xeb\xf6\x0f\x0c\xfe\x02\xc4\xa2\xd1x8*\x97Lb\xb2\te>\xa3\xd4\xe2\xb4J\xbdb\xa1\xda\xad\xd4\xe0\xcd\x82\xc3\xdc1\xf9;87\xbbj\xa7\xb9\x8d|\xc3\xdd\xe9j\x01\x00!\xf9\x04\t\x14\x00\x03\x00,\x00\x00\x11\x00\x1f\x00\x0e\x00\x00\x022\x8c\x8f8\x0b\xed\xff\x92<k\xc0\xeb\xe6\xac\x18k\xc9u\xd0\x97\x84bFR\xcc\x89\xa6\x81\xc9\xba\x06|\xca\xef\xca\x026-\xeex,\xe3u|\x96\x9c.\xf8;\x15\x00\x00!\xf9\x04\t\x14\x00\x03\x00,\x00\x00\x11\x00\x0e\x00\x0e\x00\x00\x02\x0c\x84\x8f\xa9\xcb\xed\x0f\xa3\x9c\xb4\xda\xab\n\x00!\xf9\x04\x05\x14\x00\x03\x00,\x00\x00\x00\x00\x0e\x00\x0e\x00\x00\x02\x0c\x84\x8f\xa9\xcb\xed\x0f\xa3\x9c\xb4\xda\xab\n\x00!\xf9\x04\x05\x14\x00\x03\x00,\x00\x00\x00\x00\x1f\x00\x0e\x00\x00\x022\x8c\x8f8\x0b\xed\xff\x92<k\xc0\xeb\xe6\xac\x18k\xc9u\xd0\x97\x84bFR\xcc\x89\xa6\x81\xc9\xba\x06|\xca\xef\xca\x026-\xeex,\xe3u|\x96\x9c.\xf8;\x15\x00\x00!\xf9\x04\x05\x14\x00\x03\x00,\x11\x00\x00\x00\x0e\x00\x1f\x00\x00\x02!\x8c\x8f\xa9\xcb\xed\x0f\xa3\x9c\xb4\xda\xab\x86\xde\xbc\xfb\x0fz\xc0H\x96\xe6\x89\xa6\xea\xca\xb6\xee\x0b\xc7\xf2L\x9f\x05\x00!\xf9\x04\t\x14\x00\x03\x00,\x00\x00\x11\x00\x1f\x00\x0e\x00\x00\x022\x84\x8f8\x1b\xed\xff\x92<k\xc0\xeb\xe6\xac\x18k\xc9u\xd0\x97\x84bFR\xcc\x89\xa6\x80\xc9\xba\x06|\xca\xef\xca\x066-\xeex,\xe3u|\x96\x9c.\xf8;\x15\x00\x00;'

We can save this content in bytes into a file, notice the mode of writing in the file:

In [14]:
with open("ourgif.gif", 'wb') as outputf:  # notice 'wb' - write bytes
    outputf.write(httpResponse2.content)

We can then display this file here in our notebook, because it is has been saved on our own machine:

In [15]:
from IPython.display import Image
Image(filename='ourgif.gif') 
Out[15]:
<IPython.core.display.Image object>

Meanwhile, if we try to the same with the text property, things look a bit different, because this property has encoded the bytes as UNICODE characters, instead of leaving them alone as a stream of bytes:

In [16]:
httpResponse2.text
Out[16]:
'GIF89a \x00 \x00�\x01\x002�҂��������!�\x0bNETSCAPE2.0\x03\x01\x00\x00\x00!�\x04\t\x14\x00\x02\x00,\x00\x00\x00\x00 \x00 \x00\x00\x02i��(\x1b��D�g\t�����q\n�A�hxe��ת�\x15\t\x07\'�ַ��;�[�(�ҰC��RI�\x12P�X�Ԫ���j�\\l�\x11\x05}mM�8�y���3[�E��j\x1f�|��u{{���\'$h�ehQ\x00\x00!�\x04\t\x14\x00\x03\x00,\x00\x00\x00\x00\x1f\x00\x1f\x00\x00\x02X��i���"z��[땙��=�\x12B�T2\'��G��\x03\x0c�3l�9.��\x0f\x0c�\x02Ģ�x8*�Lb�\te>���J�b�ڭ��͂��1�;87�j���|���j\x01\x00!�\x04\t\x14\x00\x03\x00,\x00\x00\x11\x00\x1f\x00\x0e\x00\x00\x022��8\x0b���<k���\x18k�uЗ�bFR̉��ɺ\x06|���\x026-�x,�u|��.�;\x15\x00\x00!�\x04\t\x14\x00\x03\x00,\x00\x00\x11\x00\x0e\x00\x0e\x00\x00\x02\x0c�����\x0f���ګ\n\x00!�\x04\x05\x14\x00\x03\x00,\x00\x00\x00\x00\x0e\x00\x0e\x00\x00\x02\x0c�����\x0f���ګ\n\x00!�\x04\x05\x14\x00\x03\x00,\x00\x00\x00\x00\x1f\x00\x0e\x00\x00\x022��8\x0b���<k���\x18k�uЗ�bFR̉��ɺ\x06|���\x026-�x,�u|��.�;\x15\x00\x00!�\x04\x05\x14\x00\x03\x00,\x11\x00\x00\x00\x0e\x00\x1f\x00\x00\x02!�����\x0f���ګ�\u07bc�\x0fz�H�扦�ʶ�\x0b��L�\x05\x00!�\x04\t\x14\x00\x03\x00,\x00\x00\x11\x00\x1f\x00\x0e\x00\x00\x022��8\x1b���<k���\x18k�uЗ�bFR̉��ɺ\x06|���\x066-�x,�u|��.�;\x15\x00\x00;'
In [17]:
with open("ourgif_text.gif", 'w') as outputf:
    outputf.write(httpResponse2.text)
In [18]:
from IPython.display import Image
Image(filename='ourgif_text.gif')
Out[18]:
<IPython.core.display.Image object>

Summary: If we are retrieving mostly text-based files (such as HTML), it's better to use the property text, but if we don't know what our data are going to be, it's better to use the property content.

2. Get lyrics from the lyrics.ovh API

lyrics.ovh is a website that provides an API to access song lyrics saved in their database.

The URL for retrieving a song has this structure:

https://api.lyrics.ovh/v1/artist/title

where we need to replace artist with an artist name and title with a song title. Click on the link below to see an example.

https://api.lyrics.ovh/v1/coldplay/yellow

Below we provide a function that packages the process of sending the request and getting the response in a function, that makes it easy for us to provide the artist name and song title.

In [19]:
def get_lyrics(artist, title):
    """Create a URL to send a request to the API server.
    """
    url = f"https://api.lyrics.ovh/v1/{artist}/{title}"
    response = requests.get(url)
    
    if response.status_code == 200:
        return response.text
    else:
        print("There was an error: ", response.reason)
        return ''

Now let's try it out:

In [20]:
get_lyrics("alanis morissette", 'ironic')
Out[20]:
'{"lyrics":"Paroles de la chanson Ironic par Alanis Morissette\\r\\nAn old man turned ninety-eight\\nHe won the lottery and died the next day\\nIt\'s a black fly in your Chardonnay\\nIt\'s a death row pardon two minutes too late\\nIsn\'t it ironic... don\'t you think\\n\\nIt\'s like rain on your wedding day\\nIt\'s a free ride when you\'ve already paid\\nIt\'s a good advice that you just can\'t take\\nWho would\'ve thought.... it figures\\n\\nMr. Play It Safe was afraid to fly\\nHe packed his suitcase and kissed his kids good-bye\\nHe waited his whole damn life to take that flight\\nAnd as the plane crashed down he thought\\n\\n\\"Well isn\'t this nice....\\"\\nAnd isn\'t it ironic.... don\'t you think\\n\\nIt\'s like rain on your wedding day\\nIt\'s a free ride when you\'ve already paid\\nIt\'s a good advice that you just can\'t take\\nWho would\'ve thought.... it figures\\n\\nWell life has a funny way of sneaking up on you\\nWhen you think everything\'s okay and everything\'s going right\\nAnd life has a funny way of helping you out when\\nYou think everything\'s gone wrong and everything blows up\\nIn your face\\n\\nA traffic jam when you\'re already late\\nA no-smoking sign on your cigarette break\\n\\nIt\'s like ten thousand spoons when all you need is a knife\\nIt\'s meeting the man of my dreams\\nAnd then meeting his beautiful wife\\nAnd isn\'t it ironic... don\'t you think\\nA little too ironic... and yeah I really do think...\\n\\nIt\'s like rain on your wedding day\\nIt\'s a free ride when you\'ve already paid\\nIt\'s a good advice that you just can\'t take\\nWho would\'ve thought.... it figures\\n\\nLife as a funny way of sneaking up on you\\nLife as a funny, funny way of helping you out\\nHelping you out"}'
In [21]:
get_lyrics("beyonce", "hold up")
Out[21]:
'{"lyrics":"Hold up, they don\'t love you like I love you\\r\\nSlow down, they don\'t love you like I love you\\r\\nBack up, they don\'t love you like I love you\\r\\nStep down, they don\'t love you like I love you\\r\\nCan\'t you see there\'s no other man above you?\\r\\nWhat a wicked way to treat the girl that loves you\\n\\nHold up, they don\'t love you like I love you\\n\\nOh, down, they don\'t love you like I love you\\n\\n\\n\\nSomething don\'t feel right\\n\\nBecause it ain\'t right\\n\\nEspecially comin\' up after midnight\\n\\nI smell your secret, and I\'m not too perfect\\n\\nTo ever feel this worthless\\n\\nHow did it come down to this?\\n\\nGoing through your call list\\n\\nI don\'t wanna lose my pride, \\n\\nBut I\'mma fuck me up a bitch\\n\\nKnow that I kept it sexy, and know I kept it fun\\n\\nThere\'s something that I\'m missing, maybe my head for one\\n\\n\\n\\nWhat\'s worse, lookin\' jealous or crazy? Jealous or crazy?\\n\\nOr like being walked all over lately, walked all over lately\\n\\nI\'d rather be crazy\\n\\n\\n\\nHold up, they don\'t love you like I love you\\n\\nSlow down, they don\'t love you like I love you\\n\\nBack up, they don\'t love you like I love you\\n\\nStep down, they don\'t love you like I love you\\n\\nCan\'t you see there\'s no other man above you?\\n\\nWhat a wicked way to treat the girl that loves you\\n\\nHold up, they don\'t love you like I love you\\n\\nSlow down they don\'t love you like I love you\\n\\n\\n\\nLet\'s imagine for a moment that you never made a name for yourself\\n\\nOr mastered wealth, they had you labeled as a king\\n\\nNever made it out the cage, still out there movin\' in them streets\\n\\nNever had the baddest woman in the game up in your sheets\\n\\nWould they be down to ride?\\n\\nNo, they used to hide from you, lie to you\\n\\nBut y\'all know we were made for each other\\n\\nSo I find you and hold you down\\n\\nMe sing se\\n\\n\\n\\nHold up, they don\'t love you like I love you\\n\\nSlow down, they don\'t love you like I love you\\n\\nBack up, they don\'t love you like I love you\\n\\nStep down, they don\'t love you like I love you\\n\\nCan\'t you see there\'s no other man above you?\\n\\nWhat a wicked way to treat the girl that loves you\\n\\nHold up, they don\'t love you like I love you\\n\\nSlow down they don\'t love you like I love you\\n\\n\\n\\nHey it is such a shame\\n\\nYou let this good love go to waste\\n\\nI always keep the top tier, 5 star\\n\\nBackseat lovin\' in the car\\n\\nLike make that wood, like make that wood\\n\\nHolly like a boulevard\\n\\n\\n\\nWhat\'s worse, lookin\' jealous or crazy? Jealous and crazy?\\n\\nOr like being walked all over lately, walked all over lately\\n\\nI\'d rather be crazy\\n\\n\\n\\nHold up, they don\'t love you like I love you\\n\\nSlow down, they don\'t love you like I love you\\n\\nBack up, they don\'t love you like I love you\\n\\nStep down, they don\'t love you like I love you\\n\\nCan\'t you see there\'s no other man above you?\\n\\nWhat a wicked way to treat the girl that loves you\\n\\nHold up, they don\'t love you like I love you\\n\\nOh, down, they don\'t love you like I love you\\n\\n\\n\\nI hop up out the bed and get my swag on\\n\\nI look in the mirror, say, \\"What\'s up?\\"\\n\\nWhat\'s up, what\'s up, what\'s up\\n\\nI hop up out the bed and get my swag on\\n\\nI look in the mirror, say, \\"What\'s up?\\"\\n\\nWhat\'s up, what\'s up, what\'s up"}'
In [22]:
get_lyrics("shakira", "hips don't lie")
Out[22]:
'{"lyrics":"Ladies up in here tonight\\r\\nNo fighting, no fighting\\r\\nWe got the refugees up in here\\r\\nNo fighting, no fighting\\r\\nShakira, Shakira\\n\\n\\n\\nI never really knew that she could dance like this\\n\\nShe makes a man wants to speak Spanish\\n\\nComo se llama (si), bonita (si), mi casa (si), Shakira Shakira, su casa\\n\\nShakira, Shakira\\n\\n\\n\\nOh baby when you talk like that\\n\\nYou make a woman go mad\\n\\nSo be wise and keep on\\n\\nReading the signs of my body\\n\\n\\n\\nAnd I\'m on tonight\\n\\nYou know my hips don\'t lie\\n\\nAnd I\'m starting to feel it\'s right\\n\\nAll the attraction, the tension\\n\\nDon\'t you see baby, this is perfection\\n\\n\\n\\nHey Girl, I can see your body moving\\n\\nAnd it\'s driving me crazy\\n\\nAnd I didn\'t have the slightest idea\\n\\nUntil I saw you dancing\\n\\n\\n\\nAnd when you walk up on the dance floor\\n\\nNobody cannot ignore the way you move your body, girl\\n\\nAnd everything so unexpected - the way you right and left it\\n\\nSo you can keep on shaking it\\n\\n\\n\\nI never really knew that she could dance like this\\n\\nShe makes a man want to speak Spanish\\n\\nComo se llama (si), bonita (si), mi casa (si, Shakira Shakira), su casa\\n\\nShakira, Shakira\\n\\n\\n\\nOh baby when you talk like that\\n\\nYou make a woman go mad\\n\\nSo be wise and keep on\\n\\nReading the signs of my body\\n\\n\\n\\nAnd I\'m on tonight\\n\\nYou know my hips don\'t lie\\n\\nAnd I am starting to feel you boy\\n\\nCome on lets go, real slow\\n\\nDon\'t you see baby asi es perfecto\\n\\n\\n\\nOh I know I am on tonight my hips don\'t lie\\n\\nAnd I am starting to feel it\'s right\\n\\nAll the attraction, the tension\\n\\nDon\'t you see baby, this is perfection\\n\\nShakira, Shakira\\n\\n\\n\\nOh boy, I can see your body moving\\n\\nHalf animal, half man\\n\\nI don\'t, don\'t really know what I\'m doing\\n\\nBut you seem to have a plan\\n\\nMy will and self restraint\\n\\nHave come to fail now, fail now\\n\\nSee, I am doing what I can, but I can\'t so you know\\n\\nThat\'s a bit too hard to explain\\n\\n\\n\\nBaila en la calle de noche\\n\\nBaila en la calle de día\\n\\n\\n\\nBaila en la calle de noche\\n\\nBaila en la calle de día\\n\\n\\n\\nI never really knew that she could dance like this\\n\\nShe makes a man want to speak Spanish\\n\\nComo se llama (si), bonita (si), mi casa (si, Shakira Shakira), su casa\\n\\nShakira, Shakira\\n\\n\\n\\nOh baby when you talk like that\\n\\nYou know you got me hypnotized\\n\\nSo be wise and keep on\\n\\nReading the signs of my body\\n\\n\\n\\nSenorita, feel the conga, let me see you move like you come from Colombia\\n\\n\\n\\nMira en Barranquilla se baila así, say it!\\n\\nMira en Barranquilla se baila así\\n\\n\\n\\nYeah\\n\\nShe\'s so sexy every man\'s fantasy a refugee like me \\n\\nback with the Fugees from a 3rd world country\\n\\nI go back like when \'pac carried crates for Humpty Humpty\\n\\nI need a whole club dizzy\\n\\nWhy the CIA wanna watch us?\\n\\nColombians and Haitians\\n\\nI ain\'t guilty, it\'s a musical transaction\\n\\nNo more do we snatch ropes\\n\\nRefugees run the seas \'cause we own our own boats\\n\\n\\n\\nI\'m on tonight, my hips don\'t lie\\n\\nAnd I\'m starting to feel you boy\\n\\nCome on let\'s go, real slow\\n\\nBaby, like this is perfecto\\n\\n\\n\\nOh, you know I am on tonight and my hips don\'t lie\\n\\nAnd I am starting to feel it\'s right\\n\\nThe attraction, the tension\\n\\nBaby, like this is perfection\\n\\n\\n\\nNo fighting\\n\\nNo fighting"}'
In [23]:
get_lyrics("3 doors down", "here without you and") # I added an extra word to the title to show what happens
There was an error:  Not Found
Out[23]:
''

When a song is not found, we receive a 404 error (this is a common HTTP error to convey that the resource was not found on the server).

2.1 Using .json method to convert string to JSON format

Most often, APIs will return data formatted as JSON. Our example above is a dictionary with only one key, lyrics. Python's request module provides a method to convert the response to JSON, so that we can access the structure more easily.

Let's see an example:

In [24]:
url = "https://api.lyrics.ovh/v1/coldplay/yellow"
response = requests.get(url)
response.json()
Out[24]:
{'lyrics': 'Paroles de la chanson Yellow par Coldplay\r\n[Chris Martin]\nLook at the stars\nLook how they shine for you\nAnd everything you do\nYeah, they were all yellow\nI came along\nI wrote a song for you\nAnd all the things you do\nAnd it was called "Yellow"\nSo then I took my turn\nOh, what a thing to have done\nAnd it was all yellow\n\n[Chris, Jonny & Will]\n(Aah) Your skin, oh yeah, your skin and bones\n(Ooh) Turn into something beautiful\n\n(Aah) You know, you know I love you so\nYou know I love you so\n\n[Chris Martin]\nI swam across\nI jumped across for you\nOh, what a thing to do\n\'Cause you were all yellow\nI drew a line\nI drew a line for you\nOh, what a thing to do\nAnd it was all yellow\n\n[Chris, Jonny & Will]\n(Aah) Your skin, oh yeah, your skin and bones\n(Ooh) Turn into something beautiful\n(Aah) And you know\nFor you, I\'d bleed myself dry\n\nFor you, I\'d bleed myself dry\n\n[ Chris Martin]\nIt\'s true, look how they shine for you\nLook how they shine for you\nLook how they shine for\nLook how they shine for you\nLook how they shine for you\nLook how they shine\n\n[ Chris Martin]\nLook at the stars\nLook how they shine for you\nAnd all the things that you do'}

We can see that this is a dictionary, so we can access the key:

In [25]:
yellowSong = response.json()
print(yellowSong['lyrics'])
Paroles de la chanson Yellow par Coldplay
[Chris Martin]
Look at the stars
Look how they shine for you
And everything you do
Yeah, they were all yellow
I came along
I wrote a song for you
And all the things you do
And it was called "Yellow"
So then I took my turn
Oh, what a thing to have done
And it was all yellow

[Chris, Jonny & Will]
(Aah) Your skin, oh yeah, your skin and bones
(Ooh) Turn into something beautiful

(Aah) You know, you know I love you so
You know I love you so

[Chris Martin]
I swam across
I jumped across for you
Oh, what a thing to do
'Cause you were all yellow
I drew a line
I drew a line for you
Oh, what a thing to do
And it was all yellow

[Chris, Jonny & Will]
(Aah) Your skin, oh yeah, your skin and bones
(Ooh) Turn into something beautiful
(Aah) And you know
For you, I'd bleed myself dry

For you, I'd bleed myself dry

[ Chris Martin]
It's true, look how they shine for you
Look how they shine for you
Look how they shine for
Look how they shine for you
Look how they shine for you
Look how they shine

[ Chris Martin]
Look at the stars
Look how they shine for you
And all the things that you do

We will modify the function we wrote, so that we convert the result into JSON right away:

In [26]:
def get_lyrics_JSON(artist, title):
    """Create a URL to send a request to the API server.
    """
    url = f"https://api.lyrics.ovh/v1/{artist}/{title}"
    response = requests.get(url)
    
    if response.status_code == 200:
        return response.json()      # CHANGE: this was changed from .text to .json()
    else:
        print("There was an error: ", response.reason)
        return ''

Let's test it again:

In [28]:
abba = get_lyrics_JSON("abba", "dancing queen")
if abba:
    print(abba['lyrics'])
Paroles de la chanson Dancing Queen par Abba


(oooo ya)
You can dance, you can jive, having the time of your life ohh yaa
Ooo.. see that girl, watch that scene, dig in the Dancing Queen ahhh ahhh

Friday night and the lights are low 
Looking out for the place to go 
Where they play the right music, getting in the swing 
You come in to look for a king 
Anybody could be that guy 
Night is young and the music's high 
With a bit of rock music, everything is fine 

You're in the mood for a dance 
And when you get the chance 

You are the Dancing Queen, young and sweet, only seventeen 
Dancing Queen, feel the beat from the tambourine ohh yaaa
You can dance, you can jive, having the time of your life 
Ooo.. see that girl, watch that scene, dig in the Dancing Queen 

You're a teaser, you turn 'em on 
Leave 'em burning and then you're gone 
Looking out for another, anyone will do 
You're in the mood for a dance 
And when you get the chance 


You are the Dancing Queen, young and sweet, only seventeen 
Dancing Queen, feel the beat from the tambourine ohh yaaa
You can dance, you can jive, having the time of your life 
Ooo.. see that girl, watch that scene, dig in the Dancing Queen 

Dig in the Dancing Queen

3. Exercise 1: Write a program to request and print lyrics

Now is your turn to put everything together. Write a function, show_lyrics with the following characterics:

  1. the function doesn't have any parameter
  2. the function prompts the user to enter an artist name, and then a song from that artist
  3. it invokes the function get_lyrics_JSON with the appropriate arguments
  4. if the song is found, it prints the lyrics and goes back to asking for an artist name
  5. if the song is not found, it shows a meaningful message and goes back to prompting
  6. the function ends when the user types q (for quit)

Here is a screenshot of the first call of this function:

Advice: First write a version of the function that executes the two input calls only once. When that works, you can add the While loop.

In [29]:
# Your code here

def show_lyrics():
    """A function that retrieves lyrics in a loop.
    """
    while True:
        input1 = input("Enter an artist name or q (to quit):")
        if input1 == 'q':
            return
        else:
            artist = input1
            song = input(f"Enter a song from {artist}:")
            results = get_lyrics_JSON(artist, song)
            if results:
                print(results['lyrics'])
            else:
                print("Sorry, we couldn't find this song, try again!")
In [30]:
# Test the function
show_lyrics()
Enter an artist name or q (to quit):justin bieber
Enter a song from justin bieber:lonely
Everybody knows my name now
But something 'bout it still feels strange
Like looking in the mirror
Tryna steady yourself and seeing somebody else
And everything is not the same now
It feels like all our lives have changed

Maybe when I'm older, it'll all calm down

But it's killing me now



What if you had it all but nobody to call?

Maybe then you'd know me

'Cause I've had everything

But no one's listening

And that's just fucking lonely



I'm so lo-o-o-onely

Lo-o-o-onely



Everybody knows my past now

Like my house was always made of glass

And maybe that's the price you pay

For the money and fame at an early age

And everybody saw me sick

And it felt like no one gave a shit

They criticized the things I did

As an idiot kid



What if you had it all but nobody to call?

Maybe then you'd know me

'Cause I've had everything

But no one's listening

And that's just fucking lonely



I'm so lo-o-o-onely

Lo-o-o-onely

I'm so lo-o-o-onely

Lo-o-o-onely
Enter an artist name or q (to quit):q

4. Getting books from the Open Library API

The Internet Archive is the biggest free library of many artefacts, such as books, movies, tv segments, music, images, etc. In addition to providing access to these resources through its website, it also provides several APIs, such as the Open Library API.

To use the API, one needs to have some iformation about the entities to retrieve. For example, the ISBN of the books, or the OpenLibrary ID for authors, etc. Once that information is known, the API makes it simple to retrieve the desired data.

Here are some examples:

https://openlibrary.org/isbn/9780140328721.json

https://openlibrary.org/authors/OL34184A.json

Because there are similarities in the two URLs, below we show a function that can be used to retrieve information for both books with known ISBN and authors with known OpenLibrary ids. Notice that the function parameter is then used in generating the URL for the request.

In [31]:
def get_entity_JSON(entityPath):
    """Create a URL to send a request to the Open Library API server.
    """
                  
    url = f"https://openlibrary.org{entityPath}.json"
    response = requests.get(url)
    
    if response.status_code == 200:
        return response.json()
    else:
        print("There was an error: ", response.reason)
        return ''

Get info about a book

In [32]:
book = get_entity_JSON('/isbn/9780140328721')
book['title'], book['publish_date']
Out[32]:
('Fantastic Mr. Fox', 'October 1, 1988')

Get info about an author

In [33]:
author = get_entity_JSON('/authors/OL34184A')
author['name'], author['birth_date']
Out[33]:
('Roald Dahl', '13 September 1916')

How do we know what keys to use for books or authors?

We can lok them up.

In [34]:
book.keys()
Out[34]:
dict_keys(['publishers', 'number_of_pages', 'isbn_10', 'covers', 'key', 'authors', 'ocaid', 'contributions', 'languages', 'classifications', 'source_records', 'title', 'identifiers', 'isbn_13', 'local_id', 'publish_date', 'works', 'type', 'first_sentence', 'latest_revision', 'revision', 'created', 'last_modified'])
In [35]:
author.keys()
Out[35]:
dict_keys(['photos', 'name', 'source_records', 'remote_ids', 'links', 'personal_name', 'bio', 'birth_date', 'key', 'alternate_names', 'death_date', 'type', 'latest_revision', 'revision', 'created', 'last_modified'])

YOUR Turn: Find the author from the book

Above we used an ISBN value and an OpenLibrary ID for the author. While the ISBN info is easy to find on the Web (simply Google for book name and word ISBN), how do we find the OpenLibrary IDs? It turns out that this information is part of the dictionary of the book, in the field authors.

Given the ISBN number "0156628708" find the name of the book and its author (by using the information in the book fields).

Algorithm

  1. Call the function get_entity_json to retrieve the info for the book that has ISBN 0156628708. Print its title.
  2. Look up the key authors. Find how you can access the value you need to get the author's info (it's a bit nested in the result). Store it in a variable.
  3. Call the function get_entity_json again, now for the author, then print the author's name

No need to write a function, simply write statements that do the above things one by one.

In [36]:
# Your code here

# Step 1, get the book
book = get_entity_JSON("/isbn/0156628708")
print(book['title'])
print(book['authors'])

# Step 2, get the author key
authorID = book['authors'][0]['key']

# Step 3, get the author name
author = get_entity_JSON(authorID)
print(author['name'])
Mrs. Dalloway
[{'key': '/authors/OL19450A'}]
Virginia Woolf

5. Exercise 2: Sort these books by publication date

Write a function, sorted_by_publish_date, which takes as a parameter a list of ISBN values, retrieves the information about the books from Open Library API and returns a list of dictionaries, each of them containing the title of the book and its publication date. Because occasionally a key might be missing, always use the method get to access keys from JSON data from APIs. Finally, the list of dictionaries must be sorted by the publication date. (You will need to write a simple helper function to help with sorting.)

For the list of ISBNs given below, you should expect the following result:

[{'title': "The Russian Debutante's Handbook", 'publish_date': 'Apr 29, 2003'},
 {'title': 'All the light we cannot see', 'publish_date': '2014'},
 {'title': 'The fifth season', 'publish_date': 'Sep 08, 2016'},
 {'title': 'The idiot', 'publish_date': '2017'},
 {'title': 'Asymmetry', 'publish_date': '2018'},
 {'title': 'How to do Nothing', 'publish_date': '2019'}]

Notice that occasionally dates contain more than the year. That will affect how you write your helper function for sorted.

In [37]:
isbnList = ['9781501166761',    # Asymmetry, by Lisa Halliday
            '9781594205613',    # The Idiot by Elif Batuman
            '9781573229883',    # The Russian Debutante's Handbook by Gary Shteyngart
            '9781612197494',    # How to do nothing by Jenny Oddell
            '9781476746586',    # All the lights we cannot see by Anthony Doerr
            '9780356508191',    # The fifth season by N.K. Jemisin
           ]
In [38]:
# Your code here

def byYear(bookDct):
    """Helper function for sorted."""
    return int(bookDct['publish_date'][-4:]) # the last four characters belong to the year

def get_publication_date(listISBN):
    """For a give list of ISBN values, get title and first sentence.
    """
    results = [] # to store the results
    
    for isbnVal in isbnList:
        book = get_entity_JSON(f"/isbn/{isbnVal}")
        title = book.get('title', '')
        date = book.get('publish_date', '')
        results.append({'title': title, 
                        'publish_date': date
                       })
        
    return sorted(results, key=byYear)
In [39]:
# test the function, it may require a few seconds to get all results
get_publication_date(isbnList)
Out[39]:
[{'title': "The Russian Debutante's Handbook", 'publish_date': 'Apr 29, 2003'},
 {'title': 'All the light we cannot see', 'publish_date': '2014'},
 {'title': 'The fifth season', 'publish_date': 'Sep 08, 2016'},
 {'title': 'The idiot', 'publish_date': '2017'},
 {'title': 'Asymmetry', 'publish_date': '2018'},
 {'title': 'How to do Nothing', 'publish_date': '2019'}]

6. Exercise 3: Which of these authors is still alive?

Write the function, isBookAuthorAlive, which, given a list of book ISBNs, will print out whether the book author is alive or deceased. For example, given the list of ISBNs in the cell below, we should get the following printout:

Ursula K. Le Guin is deceased.
Neal Stephenson is alive.
Octavia E. Butler is deceased.
Connie Willis is alive.
Kurt Vonnegut is deceased.
Couldn't find author's ID for book '9781594205613'.

Occasionally, an author's information might not be available, so the code should take that into account.

Challenge
Students who want to challenge themselves can try to also generate this more detailed printout.

The author of 'The  left hand of darkness', Ursula K. Le Guin, died at the age of 89.
The author of 'Seveneves', Neal Stephenson, is alive.
The author of 'Parable of the sower', Octavia E. Butler, died at the age of 59.
The author of 'Doomsday book', Connie Willis, is alive.
The author of 'Slaughterhouse-five, or, The children's crusade', Kurt Vonnegut, died at the age of 85.
Couldn't find author's ID for book '9781594205613'.

Advice
Don't write the function right away, first make sure sure you can get the field 'death_year' for each author and write a message based on its existence. Once you have figured out the code, you can package it as a function. The reason for not writing the function right away is that you cannot access the local variables for inspection outside the body of the function.

In [40]:
isbnList = ['0802713025',    # The left hand of darkness by Ursula LeGuin
            '1469246864',    # Seveneves by Neal Stephenson
            '0941423999',    # Parable of the sower by Octavia Butler
            '0553081314',    # Doomsday book by Connie Willis
            '0385312083',    # Slaughterhouse-five by Kurt Vonnegut
            '9781594205613'  # The Idiot by Elif Batuman
           ]
In [41]:
# Solution nr. 1 (show simple message)
# Your code here

def isBookAuthorAlive(listISBN):
    """A function that prints out a message about the author of a book.
    """
    for isbnVal in listISBN:
        book = get_entity_JSON(f"/isbn/{isbnVal}")
        
        try:
            authorPath = book['authors'][0]['key']
        except:
            print(f"Couldn't find author's ID for book '{isbnVal}'.")
            authorPath = ''
            
        if authorPath:
            author = get_entity_JSON(authorPath)
            
            if author.get('death_date',''):
                print(f"{author.get('name','')} is deceased.")
            else:
                print(f"{author.get('name','')} is alive.")
In [42]:
isBookAuthorAlive(isbnList)
Ursula K. Le Guin is deceased.
Neal Stephenson is alive.
Octavia E. Butler is deceased.
Connie Willis is alive.
Kurt Vonnegut is deceased.
Couldn't find author's ID for book '9781594205613'.
In [43]:
# Solution nr. 2 (show detailed message)
# Your code here

def calculate_age(author):
    """Helper function to calculate the age of an author.
    """
    birth = author.get('birth_date', '')
    death = author.get('death_date', '')
    
    if birth and death:
        age = int(death[-4:]) - int(birth[-4:])
        return age
    return None


def isBookAuthorAlive(listISBN):
    """A function that prints out a detailed message about the author of a book.
    """
    for isbnVal in listISBN:
        book = get_entity_JSON(f"/isbn/{isbnVal}")
        title = book.get('title', '')
        
        # It's good to use try/except to avoid problems with missing keys
        try:
            authorPath = book['authors'][0]['key']
        except:
            print(f"Couldn't find author's ID for book '{isbnVal}'.")
            authorPath = ''
            
        if authorPath:
            author = get_entity_JSON(authorPath)
            
            # Generate first half of the message to print out
            toPrint1 = f"The author of '{title}', {author.get('name','')},"
            
            # Generate the second part of the message to print out
            if author.get('death_date',''):
                age = calculate_age(author)
                toPrint2 = f" died at the age of {age}."
            else:
                toPrint2 = " is alive."
                
            print(toPrint1+toPrint2)
In [44]:
isBookAuthorAlive(isbnList)
The author of 'The  left hand of darkness', Ursula K. Le Guin, died at the age of 89.
The author of 'Seveneves', Neal Stephenson, is alive.
The author of 'Parable of the sower', Octavia E. Butler, died at the age of 59.
The author of 'Doomsday book', Connie Willis, is alive.
The author of 'Slaughterhouse-five, or, The children's crusade', Kurt Vonnegut, died at the age of 85.
Couldn't find author's ID for book '9781594205613'.

This is the end of this notebook!