Параллельные Фьючерсы Не Продолжаются После Исключения

#python #multithreading

Вопрос:

Я использую параллельные фьючерсы, чтобы удалить список H1 с веб-страниц и добавить их в список под названием archive_h1_list. Проблема в том, что, как только параллельные фьючерсы попадают в исключение, они перестают добавлять список.

Когда я печатаю полученный список ниже, он останавливается после первого исключения. ['Example Domain', 'Example Domain', 'Exception Error!'] он никогда не продолжает обрабатывать последнее https://www.example.com h1 в списке после попадания в исключение.

 import concurrent.futures
from urllib.request import urlopen

from bs4 import BeautifulSoup

CONNECTIONS = 8

archive_url_list = ["https://www.example.com", "https://www.example.com", "sdfihaslkhasd", "https://www.example.com"]
archive_h1_list = []

def get_archive_h1(h1_url):
    html = urlopen(h1_url)
    bsh = BeautifulSoup(html.read(), 'lxml')
    return bsh.h1.text.strip()


def concurrent_calls():
    with concurrent.futures.ThreadPoolExecutor(max_workers=CONNECTIONS) as executor:
        f1 = executor.map(get_archive_h1, archive_url_list)
        try:
            for future in f1:
                archive_h1_list.append(future)
        except Exception:
            archive_h1_list.append("Exception Error!")
            pass
 

Ожидаемый результат должен быть:

 ['Example Domain', 'Example Domain', 'Exception Error!', 'Example Domain']
 

Ответ №1:

Это потому, что ваш for цикл находится внутри try , и когда вы ловите исключение try , блок приостанавливается, и except блок выполняется, таким образом, ваш for цикл прерывается.

Одним из способов решить эту проблему было бы переместить цикл for за пределы try блока, однако, согласно документации Executor.map :

Если вызов функции вызывает исключение, то это исключение будет вызвано, когда его значение будет извлечено из итератора.

Что делает обработку исключений довольно неприятной за пределами вашей функции.

Поэтому первое решение состоит в том, чтобы перехватывать исключения внутри get_archive_h1 :

 def get_archive_h1(h1_url):
    try:
        html = urlopen(h1_url)
        bsh = BeautifulSoup(html.read(), 'lxml')
        return bsh.h1.text.strip()
    except Exception:
       return "Exception Error!"


def concurrent_calls():
    with concurrent.futures.ThreadPoolExecutor(max_workers=CONNECTIONS) as executor:
        f1 = executor.map(get_archive_h1, archive_url_list)
        for future in f1:
            archive_h1_list.append(future)
 

Другим решением является использование другого метода исполнителя, при котором у вас будет больше контроля над вашим будущим разрешением, т. е. Executor.submit :

 def concurrent_calls():
    with concurrent.futures.ThreadPoolExecutor(max_workers=CONNECTIONS) as executor:
        futures = [executor.submit(get_archive_h1, url) for url in archive_url_list]
        for future in futures:
            try:
                archive_h1_list.append(future.result())
            except Exception:
                archive_h1_list.append("Exception Error!")
                pass
 

Комментарии:

1. Большое спасибо! В этом есть смысл. Мне нужно использовать карту, потому что мне нужно вернуть список в исходном порядке. Спасибо, что нашли время объяснить мне это . Действительно ценю это.