Сообщение "Не удалось декодировать ответ от марионетки" в сценарии сглаживания Python/Firefox

Добрый день, я сделал несколько поисков здесь и в Google, но все же нашел решение, которое решает эту проблему.

Сценарий:

У меня есть сценарий Python (2.7), который проходит через несколько URL-адресов (например, подумайте о страницах Amazon, просмотрите обзоры). Каждая страница имеет тот же макет HTML, просто очищая другую информацию. Я использую Selenium с безгласным браузером, так как на этих страницах есть javascript, который нужно выполнить, чтобы захватить информацию.

Я запускаю этот скрипт на своей локальной машине (OSX 10.10). Firefox - последняя версия v59. Selenium - версия версии 3.11.0 и с использованием geckodriver v0.20.

В этом скрипте нет проблем, он может запускать все URL-адреса и очищать страницы без проблем.

Теперь, когда я помещаю скрипт на свой сервер, единственное отличие - это Ubuntu 16.04 (32 бит). Я использую соответствующий geckodriver (все еще v0.20), но все остальное одно и то же (Python 2.7, Selenium 3.11). Кажется, что он случайно разбивает браузер без браузера, а затем все browserObjt.get('url...') больше не работает.

В сообщениях об ошибках говорится:

Сообщение: не удалось декодировать ответ от марионетки

Любые дальнейшие запросы селена на страницы возвращают ошибку:

Сообщение: попытался выполнить команду без установления соединения


Чтобы показать код:

Когда я создаю драйвер:

    options = Options()
    options.set_headless(headless=True)

    driver = webdriver.Firefox(
        firefox_options=options,
        executable_path=config.GECKODRIVER
    )

driver передается функции сценария в качестве параметра browserObj который затем используется для вызова определенных страниц, а затем после загрузки он передается в BeautifulSoup для синтаксического анализа:

browserObj.get(url)

soup = BeautifulSoup(browserObj.page_source, 'lxml')

Ошибка может указывать на строку BeautifulSoup, которая разбивает браузер.

Что может вызвать это, и что я могу сделать для решения проблемы?


Изменение: добавление трассировки стека, указывающее на одно и то же:

Traceback (most recent call last):
  File "main.py", line 164, in <module>
    getLeague
  File "/home/ps/dataparsing/XXX/yyy.py", line 48, in BBB
    soup = BeautifulSoup(browserObj.page_source, 'lxml')
  File "/home/ps/AAA/projenv/local/lib/python2.7/site-packages/selenium/webdriver/remote/webdriver.py", line 670, in page_source
    return self.execute(Command.GET_PAGE_SOURCE)['value']
  File "/home/ps/AAA/projenv/local/lib/python2.7/site-packages/selenium/webdriver/remote/webdriver.py", line 312, in execute
    self.error_handler.check_response(response)
  File "/home/ps/AAA/projenv/local/lib/python2.7/site-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response
    raise exception_class(message, screen, stacktrace)
WebDriverException: Message: Failed to decode response from marionette

Примечание. Этот скрипт использовался для работы с Chrome. Поскольку сервер является 32-битным сервером, я могу использовать только chromedriver v0.33, который поддерживает только Chrome v60-62. В настоящее время Chrome - это v65 и на DigitalOcean. Кажется, у меня нет простого способа вернуться к старой версии, поэтому я застрял в Firefox.

Ответ 1

Я до сих пор не знаю, почему это происходит, но я, возможно, нашел обходной путь. Я читал в некоторых документах, что может быть условие гонки (в чем, я не уверен, так как не должно быть двух предметов, конкурирующих за одни и те же ресурсы).

Я изменил код очистки, чтобы сделать это:

import time

browserObj.get(url)

time.sleep(3)

soup = BeautifulSoup(browserObj.page_source, 'lxml')

Никакой конкретной причины, по которой я выбрал 3 секунды, но после добавления этой задержки у меня не было Message: failed to decode response from marionette ошибки Message: failed to decode response from marionette из любого моего списка URL для очистки.


Обновление: октябрь 2018

Это продолжает оставаться проблемой более шести месяцев спустя. Firefox, Geckodriver, Selenium и PyVirtualDisplay были обновлены до последних версий. Эта ошибка повторялась спонтанно, без паттернов: иногда работала, а иногда нет.

Что исправило эту проблему, так это увеличение оперативной памяти на моем сервере с 1 до 2 ГБ. С момента увеличения не было сбоев такого рода.

Ответ 2

Для всех, кто столкнулся с этой проблемой при запуске веб-драйвера selenium в контейнере Docker, устранение этой проблемы возможно при увеличении размера контейнера до 2 ГБ.

Я полагаю, что это влияет и на физические машины, если операционная система исправила их проблему, увеличив оперативную память сервера до 2 Гб, но это может быть совпадением.

Ответ 3

Вероятная реальная проблема заключается в том, что DOM еще не загружен, и вы запускаете поиск на следующей странице. Вот почему sleep(3) работает в большинстве случаев. Правильное решение заключается в использовании класса ожидания.

Это пример теста с использованием функции ожидания для Nextcloud. Это из моего образа docker-selenium-firefox-python: https://hub.docker.com/r/nowsci/selenium

Обратите внимание, как вызывается класс wait окружающий любой click или get вызовов. По сути, для этого используется тот факт, что селен изменяет идентификатор тега HTML при загрузке страницы. Функция ожидания проверяет, отличается ли новый идентификатор от старого, и, если это так, загружен DOM.

import time
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.common.keys import Keys

class wait(object):

    def __init__(self, browser):
        self.browser = browser

    def __enter__(self):
        self.old_page = self.browser.find_element_by_tag_name('html')

    def page_has_loaded(self):
        new_page = self.browser.find_element_by_tag_name('html')
        return new_page.id != self.old_page.id

    def __exit__(self, *_):
        start_time = time.time()
        while time.time() < start_time + 5:
            if self.page_has_loaded():
                return True
            else:
                time.sleep(0.1)
        raise Exception('Timeout waiting for page load.')

def test():
    try:
        opts = Options()
        opts.set_headless()
        assert opts.headless  # Operating in headless mode
        browser = Firefox(options=opts)
    except Exception as e:
        print("  -=- FAIL -=-: Browser setup - ", e)
        return

    # Test title
    try:
        with wait(browser):
            browser.get('https://nextcloud.mydomain.com/index.php/login')
        assert 'Nextcloud' in browser.title
    except Exception as e:
        print("  -=- FAIL -=-: Initial load - ", e)
        return
    else:
        print("  Success: Initial load")

    try:
        # Enter user
        elem = browser.find_element_by_id('user')
        elem.send_keys("MYUSER")

        # Enter password
        elem = browser.find_element_by_id('password')
        elem.send_keys("MYPASSWORD")

        # Submit form
        elem = browser.find_element_by_id('submit')
        with wait(browser):
            elem.click()

        # Check title for success
        assert 'Files' in browser.title
    except Exception as e:
        print("  -=- FAIL -=-: Login - ", e)
        return
    else:
        print("  Success: Login")

    print("  Finished.")

print("Testing nextcloud...")
test()

Объедините это с ответом от @myol, если вы используете Docker.

Ответ 4

Надеюсь, это спасет еще одну бедную душу от часов, которые я потратил на это

Загрузите старую версию Firefox (в частности, v66 для меня) и укажите Selen там:

firefox_binary='/home/user/Downloads/old_firefox/firefox/firefox'

Ответ 5

Проблема в том, что вы не закрываете драйвер. Я сделал ту же ошибку. Что касается Linux, я заметил, что занимаю все 26 ГБ моего компьютера с закрытым процессом Firefox.