Пул соединений заполнен, отбрасывает соединение с ThreadPoolExecutor и несколькими безголовыми браузерами через Selenium и Python - PullRequest
0 голосов
/ 06 декабря 2018

Я пишу некоторое программное обеспечение для автоматизации, используя selenium==3.141.0, python 3.6.7, chromedriver 2.44.

Большая часть логики в порядке для выполнения одним экземпляром браузера, но для некоторой части янужно запустить 10-20 экземпляров, чтобы иметь приличную скорость выполнения.

Как только дело доходит до части, которая выполняется ThreadPoolExecutor, взаимодействия браузера начинают выдавать эту ошибку:

WARNING|05/Dec/2018 17:33:11|connectionpool|_put_conn|274|Connection pool is full, discarding connection: 127.0.0.1
WARNING|05/Dec/2018 17:33:11|connectionpool|urlopen|662|Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProtocolError('Connection aborted.', RemoteDisconnected('Remote end closed connection without response',))': /session/119df5b95710793a0421c13ec3a83847/url
WARNING|05/Dec/2018 17:33:11|connectionpool|urlopen|662|Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7fcee7ada048>: Failed to establish a new connection: [Errno 111] Connection refused',)': /session/119df5b95710793a0421c13ec3a83847/url

настройка браузера:

def init_chromedriver(cls):
    try:
        chrome_options = webdriver.ChromeOptions()
        chrome_options.add_argument('--headless')
        chrome_options.add_argument(f"user-agent={Utils.get_random_browser_agent()}")
        prefs = {"profile.managed_default_content_settings.images": 2}
        chrome_options.add_experimental_option("prefs", prefs)

        driver = webdriver.Chrome(driver_paths['chrome'],
                                       chrome_options=chrome_options,
                                       service_args=['--verbose', f'--log-path={bundle_dir}/selenium/chromedriver.log'])
        driver.implicitly_wait(10)

        return driver
    except Exception as e:
        logger.error(e)

соответствующий код:

ProfileParser создает экземпляр веб-драйвера и выполняет несколько взаимодействий страниц.Я полагаю, что сами взаимодействия не имеют отношения, потому что все работает без ThreadPoolExecutor.Тем не менее, вкратце:

class ProfileParser(object):
    def __init__(self, acc):
        self.driver = Utils.init_chromedriver()
    def __exit__(self, exc_type, exc_val, exc_tb):
        Utils.shutdown_chromedriver(self.driver)
        self.driver = None

    collect_user_info(post_url)
           self.driver.get(post_url)
           profile_url = self.driver.find_element_by_xpath('xpath_here')]').get_attribute('href')

Во время работы в ThreadPoolExecutor вышеуказанная ошибка появляется в этой точке self.driver.find_element_by_xpath или в self.driver.get

, это работает:

with ProfileParser(acc) as pparser:
        pparser.collect_user_info(posts[0])

эти опции не работают: (connectionpool errors)

futures = []
#one worker, one future
with ThreadPoolExecutor(max_workers=1) as executor:
        with ProfileParser(acc) as pparser:
            futures.append(executor.submit(pparser.collect_user_info, posts[0]))

#10 workers, multiple futures
with ThreadPoolExecutor(max_workers=10) as executor:
    for p in posts:
        with ProfileParser(acc) as pparser:
            futures.append(executor.submit(pparser.collect_user_info, p))

ОБНОВЛЕНИЕ:

Я нашел временное решение (которое не лишает законной силы этот первоначальный вопрос) - создать экземпляр webdriver вне ProfileParser класса.Не знаю, почему это работает, но начальная - нет.Я полагаю, причина в какой-то языковой специфике?Спасибо за ответы, однако, похоже, что проблема не в пределе ThreadPoolExecutor max_workers - как вы видите в одном из вариантов, я пытался отправить один экземпляр, и он все еще не работает.

текущее решение:

futures = []
with ThreadPoolExecutor(max_workers=10) as executor:
    for p in posts:
        driver = Utils.init_chromedriver()
        futures.append({
            'future': executor.submit(collect_user_info, driver, acc, p),
            'driver': driver
        })

for f in futures:
    f['future'].done()
    Utils.shutdown_chromedriver(f['driver'])

Ответы [ 2 ]

0 голосов
/ 06 декабря 2018

Это сообщение об ошибке ...

WARNING|05/Dec/2018 17:33:11|connectionpool|_put_conn|274|Connection pool is full, discarding connection: 127.0.0.1
WARNING|05/Dec/2018 17:33:11|connectionpool|urlopen|662|Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProtocolError('Connection aborted.', RemoteDisconnected('Remote end closed connection without response',))': /session/119df5b95710793a0421c13ec3a83847/url
WARNING|05/Dec/2018 17:33:11|connectionpool|urlopen|662|Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7fcee7ada048>: Failed to establish a new connection: [Errno 111] Connection refused',)': /session/119df5b95710793a0421c13ec3a83847/url

... кажется, проблема в пуле соединений urllib3, которая вызвала эти ПРЕДУПРЕЖДЕНИЕ при выполнении def _put_conn(self, conn)метод в connectionpool.py .

def _put_conn(self, conn):
    """
    Put a connection back into the pool.

    :param conn:
        Connection object for the current host and port as returned by
        :meth:`._new_conn` or :meth:`._get_conn`.

    If the pool is already full, the connection is closed and discarded
    because we exceeded maxsize. If connections are discarded frequently,
    then maxsize should be increased.

    If the pool is closed, then the connection will be closed and discarded.
    """
    try:
        self.pool.put(conn, block=False)
        return  # Everything is dandy, done.
    except AttributeError:
        # self.pool is None.
        pass
    except queue.Full:
        # This should never happen if self.block == True
        log.warning(
            "Connection pool is full, discarding connection: %s",
            self.host)

    # Connection never got put back into the pool, close it.
    if conn:
        conn.close()

ThreadPoolExecutor

ThreadPoolExecutor является Исполнителем подкласс, который использует пул потоков для асинхронного выполнения вызовов.Взаимные блокировки могут возникать, когда вызываемый объект, связанный с Future, ожидает результатов другого Future.

class concurrent.futures.ThreadPoolExecutor(max_workers=None, thread_name_prefix='', initializer=None, initargs=())
  • Подкласс Executor, который использует пул не более max_workers потоков для асинхронного выполнения вызовов.
  • инициализатор - это необязательный вызываемый объект, который вызывается в начале каждого рабочего потока;initargs - это набор аргументов, передаваемых инициализатору.Если инициализатор вызовет исключение, все ожидающие в настоящий момент задания вызовут BrokenThreadPool, а также любую попытку отправить больше заданий в пул.
  • Начиная с версии 3.5: если max_workers равен None или не задан, он будет по умолчаниюк числу процессоров на машине, умноженному на 5, при условии, что ThreadPoolExecutor часто используется для перекрытия операций ввода-вывода вместо работы ЦП, а число рабочих должно быть больше, чем число рабочих для ProcessPoolExecutor.
  • Начиная с версии 3.6: был добавлен аргумент thread_name_prefix, чтобы позволить пользователям управлять потоками. Имена потоков для рабочих потоков, созданных пулом для упрощения отладки.
  • Начиная с версии 3.7: добавлены аргументы инициализатора и initargs.

По вашему вопросу, когда вы пытаетесь запустить 10-20 экземпляров, размер пула соединений по умолчанию из 10 кажется недостаточным в вашем случае, которыйжестко закодировано в adapters.py .

Более того, @EdLeafe в обсуждении Ошибка при получении: пул соединений переполнен, отбрасывание соединения упоминает:

Это похоже на код запроса, None объектынормальные.Если _get_conn() получает Нет из пула, он просто создает новое соединение.Однако кажется странным, что он должен начинаться со всех этих объектов None, и что _put_conn () недостаточно умен, чтобы заменить None на соединение.

Однако объединение Добавление пулаПараметр size для конструктора клиента исправил эту проблему.

Решение

Увеличение размера пула соединений по умолчанию из 10 , который ранее был жестко задан в adapters.py и теперь настраиваемый решит вашу проблему.


Обновление

Согласно вашему обновлению комментария ... отправьте один экземпляр, и результат будет тем же ... .согласно @ meferguson84 в рамках обсуждения Ошибка при получении: пул соединений переполнен, соединение отбрасывается :

Я вошел в код до точки, где он монтирует адаптер, чтобы просто поиграть с нимразмер пула и посмотреть, если это имеет значение.Я обнаружил, что очередь заполнена объектами NoneType, а фактическое соединение для загрузки является последним элементом в списке.Список длиной 10 пунктов (что имеет смысл).Что не имеет смысла, так это то, что параметр unfinished_tasks для пула равен 11. Как это может быть, если в самой очереди только 11 элементов?Кроме того, нормально ли, чтобы очередь была заполнена объектами NoneType, поскольку используемое нами соединение является последним элементом в списке?

Это звучит как возможная причина в вашем usecase .Это может показаться излишним, но вы все равно можете выполнить несколько специальных шагов следующим образом:

0 голосов
/ 06 декабря 2018

пожалуйста, посмотрите свою ошибку

ProtocolError('Connection aborted.', 
  RemoteDisconnected('Remote end closed connection without response',))

'NewConnectionError('<urllib3.connection.HTTPConnection object at >: 
   Failed to establish a new connection: [Errno 111] Connection refused',)':

Ошибка возникает из-за того, что вы выполняете многократное соединение слишком быстро, возможно, сервер отключен или сервер заблокировал ваш запрос.

...