pythonurllib

403 Forbidden error when getting cat images


I just finished programming a dog photo program, and after a few issues it works fine. I decided to make a modified version that uses a different api to give cat images instead. It took a second to figure out but after changing the format, I get this error: urllib.error.HTTPError: HTTP Error 403: Forbidden

This error is consistent and happens every single time.

Here's my entire code, since none of these answers seemed to help.

import tkinter, urllib.request, json
from PIL import Image, ImageTk

#Create the main window
main = tkinter.Tk()
main.geometry('550x600+800+300')
main.title('Dogs')

#This is the list of dog image urls
urllist = []

#This is the list of dog images
doglist = []

#This is a pointer to the image in the list, which is used as the image in the label
dognumber = 0

#Set W and H to the max width and height we want for the images
W, H = 500, 460

#Resize the image to the max width and height
def resize_image(img): 
    ratio = min(W/img.width, H/img.height)
    return img.resize((int(img.width*ratio), int(img.height*ratio)), Image.ANTIALIAS)

#Change dog number by -1 to go back a dog, then set the image to the new dog
def last_image():
    global doglist, dognumber

    #This is pretty self explanatory
    dognumber -= 1

    #If the dog number is less than 0, set it to 0 so there's no negative indexing in dog list
    if dognumber < 0:
        dognumber = 0

    #Set the image to the new dog
    l.config(image=doglist[dognumber])

def fetch_image():
    global doglist, dognumber, urllist
    try:
        #If the dog number is less than the length of the list, simply increment it by 1 and set the image to the new dog. This allows you to go back a dog, and then go forward again, instead of generating a new dog every time
        dognumber += 1
        l.config(image=doglist[dognumber])
    except IndexError:
        #Even if we are on the last dog on the list, that try script will add one to the dog number, so we need to subtract one to get the correct index
        dognumber -= 1

        #Get the actual image of the dog we want
        dogapi = urllib.request.Request(f'https://api.thecatapi.com/v1/images/search')
        dogapi.add_header("x-api-key", "blahblahblah12312")
        dogjson = urllib.request.urlopen(dogapi).read()
        dogdict = json.loads(dogjson)
        url = dogdict[0]['url']
        print(url)

        #Convert the image to a PIL image
        #error right here, stack overflow didnt help ):
        m = urllib.request.urlopen(url)
        mpi = resize_image(Image.open(m))
        tkimg = ImageTk.PhotoImage(mpi)

        #Add the new dog to the list, as well as the url 
        doglist.append(tkimg)
        urllist.append(url)

        #Increment the dog number, and set the image to the new dog
        dognumber += 1
        l.config(image=tkimg)
        l.image = tkimg

def save_dogs():
    global urllist

    #Open the file to write to
    w = open('/'.join(__file__.split("\\")[:-1]) + '/doglist.txt', 'w')

    #Write the urls to the file
    w.write('\n'.join(urllist))

#Load Label and Buttons
l = tkinter.Label(main, image=tkinter.PhotoImage(), width=W, height=H)
b = tkinter.Button(main, text='Next Dog', command=fetch_image)
b2 = tkinter.Button(main, text='Last Dog', command=last_image)
b3 = tkinter.Button(main, text='Save Dogs', command=save_dogs)

def preloaddogs():
    global doglist, dognumber, urllist
    for _ in range(5):
        #This does the same thing as fetch_image, but it doesn't increment the dog number so you dont end up on the last dog, and it sets the image shown to the first dog
        dogapi = urllib.request.urlopen(f'https://dog.ceo/api/breeds/image/random')
        dogjson = dogapi.read()
        dogdict = json.loads(dogjson)
        url = dogdict['message']
        m = urllib.request.urlopen(url)
        mpi = resize_image(Image.open(m))
        tkimg = ImageTk.PhotoImage(mpi)
        doglist.append(tkimg)
        urllist.append(url)
        url = urllist[0]
        m = urllib.request.urlopen(url)
        mpi = resize_image(Image.open(m))
        tkimg = ImageTk.PhotoImage(mpi)
        l.config(image=tkimg)
        l.image = tkimg

#Load the widgets, and then preload the first 5 dogs
l.pack()
b.place(x=350, y=500)
b2.place(x=150, y=500)
b3.pack()

#preloaddogs()

main.mainloop()

And heres the full error:

Traceback (most recent call last):
  File "C:\Users\doubl\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 1921, in __call__
    return self.func(*args)
  File "g:\My Drive\Code\Projects\Dog\cats.pyw", line 58, in fetch_image
    m = urllib.request.urlopen(url)
  File "C:\Users\doubl\AppData\Local\Programs\Python\Python310\lib\urllib\request.py", line 216, in urlopen
    return opener.open(url, data, timeout)
  File "C:\Users\doubl\AppData\Local\Programs\Python\Python310\lib\urllib\request.py", line 525, in open
    response = meth(req, response)
  File "C:\Users\doubl\AppData\Local\Programs\Python\Python310\lib\urllib\request.py", line 634, in http_response
    response = self.parent.error(
  File "C:\Users\doubl\AppData\Local\Programs\Python\Python310\lib\urllib\request.py", line 563, in error
    return self._call_chain(*args)
  File "C:\Users\doubl\AppData\Local\Programs\Python\Python310\lib\urllib\request.py", line 496, in _call_chain
    result = func(*args)
  File "C:\Users\doubl\AppData\Local\Programs\Python\Python310\lib\urllib\request.py", line 643, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 403: Forbidden

I tried adding an api key, and I still get the same error. Then tried adding it to the header, same story.


Solution

  • The "thecatapi" needs an API Key and it seems like you aren't providing one and that's the reason you're getting the 403 error

    Try adding an API Key as Request Header
    You can get one on the thecatapi website

    import requests
    from PIL import Image
    from io import BytesIO
    
    token = "token"
    
    def getRandomCat():
        url = "https://api.thecatapi.com/v1/images/search"
        response = requests.get(url, params={"x-api-key": token})
        return response.json()[0]["url"]
    
    if __name__ == '__main__':
        # get url of image
        url = getRandomCat()
        # get content from the url with the image
        response = requests.get(url)
        # convert and open Image (conversion with io)
        image = Image.open(BytesIO(response.content))