Can You Retry/Loop inside a Try/Except?

Viewed 1383

I'm trying to understand if it's possible to set a loop inside of a Try/Except call, or if I'd need to restructure to use functions. Long story short, after spending a few hours learning Python and BeautifulSoup, I managed to frankenstein some code together to scrape a list of URLs, pull that data out to CSV (and now update it to a MySQL db). The code is now working as planned, except that I occasionally run into a 10054, either because my VPN hiccups, or possibly the source host server is occasionally bouncing me (I have a 30 second delay in my loop but it still kicks me on occasion).

I get the general idea of Try/Except structure, but I'm not quite sure how I would (or if I could) loop inside it to try again. My base code to grab the URL, clean it and parse the table I need looks like this:

for url in contents:
    print('Processing record', (num+1), 'of', len(contents))
    if url:
        print('Retrieving data from ', url[0])
        html = requests.get(url[0]).text
        soup = BeautifulSoup(html, 'html.parser')
        for span in soup('span'):
            span.decompose()
       trs = soup.select('div#collapseOne tr')
        if trs:
            print('Processing')
            for t in trs:
                for header, value in zip(t.select('td')[0], t.select('td:nth-child(2)')):
                    if num == 0:
                        headers.append(' '.join(header.split()))    
                    values.append(re.sub(' +', ' ', value.get_text(' ', strip=True)))  

After that is just processing the data to CSV and running an update sql statement.

What I'd like to do is if the HTML request call fails is wait 30 seconds, try the request again, then process, or if the retry fails X number of times, go ahead and exit the script (assuming at that point I have a full connection failure).

Is it possible to do something like that in line, or would I need to make the request statement into a function and set up a loop to call it? Have to admit I'm not familiar with how Python works with function returns yet.

2 Answers

You can add an inner loop for the retries and put your try/except block in that. Here is a sketch of what it would look like. You could put all of this into a function and put that function call in its own try/except block to catch other errors that cause the loop to exit.

Looking at requests exception hierarchy, Timeout covers multiple recoverable exceptions and is a good start for everything you may want to catch. Other things like SSLError aren't going to get better just because you retry, so skip them. You can go through the list to see what is reasonable for you.

import itertools

# requests exceptions at 
# https://requests.readthedocs.io/en/master/_modules/requests/exceptions/
    
for url in contents:
    print('Processing record', (num+1), 'of', len(contents))
    if url:
        print('Retrieving data from ', url[0])
        retry_count = itertools.count()
        # loop for retries
        while True:
            try:
                # get with timeout and convert http errors to exceptions
                resp = requests.get(url[0], timeout=10)
                resp.raise_for_status()
            # the things you want to recover from
            except requests.Timeout as e:
                if next(retry_count) <= 5:
                    print("timeout, wait and retry:", e)
                    time.sleep(30)
                    continue
                else:
                    print("timeout, exiting")
                    raise # reraise exception to exit
            except Exception as e:
                print("unrecoverable error", e)
                raise
            break

        html = resp.text
        etc…

I've done a little example by myself to graphic this, and yes, you can put loops inside try/except blocks.

from sys import exit


def example_func():
    try:
        while True:
            num = input("> ")
            try:
                int(num)
                if num == "10":
                    print("Let's go!")
                else:
                    print("Not 10")
            except ValueError:
                exit(0)
    except:
        exit(0)


example_func()

This is a fairly simple program that takes input and if it's 10, then it says "Let's go!", otherwise it tells you it's not 10 (if it's not a valid value, it just kicks you out).

Notice that inside the while loop I put a try/except block, taking into account the necessary indentations. You can take this program as a model and use it on your favor.

Related