Mensaje de error “No se pudo decodificar la respuesta de la marioneta” en el script de raspado sin cabeza de Python / Firefox

Buen día, he hecho varias búsquedas aquí y en Google y, sin embargo, he encontrado una solución que resuelva este problema.

El escenario es:

Tengo un script de Python (2.7) que recorre una cantidad de URL (p. Ej., Piense en páginas de Amazon, en revisiones de raspado). Cada página tiene el mismo diseño HTML, simplemente raspando información diferente. Uso Selenium con un navegador sin cabeza ya que estas páginas tienen javascript que debe ejecutarse para obtener la información.

Ejecuto este script en mi máquina local (OSX 10.10). Firefox es el último v59. Selenium tiene una versión de 3.11.0 y usa geckodriver v0.20.

Esta secuencia de comandos localmente no tiene problemas, puede ejecutar todas las URL y raspar las páginas sin ningún problema.

Ahora, cuando coloco el script en mi servidor, la única diferencia es que es Ubuntu 16.04 (32 bits). Uso el geckodriver apropiado (aún v0.20) pero todo lo demás es igual (Python 2.7, Selenium 3.11). Parece que se browserObjt.get('url...') aleatoriamente el navegador sin cabeza y luego todo el browserObjt.get('url...') ya no funciona.

Los mensajes de error dicen:

Mensaje: no se pudo decodificar la respuesta de la marioneta

Cualquier otra solicitud de selenium para páginas devuelve el error:

Mensaje: intentado ejecutar el comando sin establecer una conexión


Para mostrar algún código:

Cuando creo el driver:

  options = Options() options.set_headless(headless=True) driver = webdriver.Firefox( firefox_options=options, executable_path=config.GECKODRIVER ) 

driver se pasa a la función del script como un parámetro browserObj que luego se usa para llamar a páginas específicas y luego, una vez que se carga, se pasa a BeautifulSoup para su análisis:

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

El error podría estar apuntando a la línea BeautifulSoup que está bloqueando el navegador.

¿Qué causa esto y qué puedo hacer para resolver el problema?


Edición: Agregar traza de stack que apunta a lo mismo:

 Traceback (most recent call last): File "main.py", line 164, in  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 

Nota: este script solía funcionar con Chrome. Como el servidor es un servidor de 32 bits, solo puedo usar chromedriver v0.33, que solo es compatible con Chrome v60-62. Actualmente, Chrome es v65 y en DigitalOcean parece que no tengo una forma fácil de volver a una versión anterior, por lo que me quedo con Firefox.

Todavía no sé por qué sucede esto, pero es posible que haya encontrado una solución. Leí en alguna documentación que puede haber una condición de carrera (sobre qué, no estoy seguro ya que no debería haber dos elementos compitiendo por los mismos recursos).

Cambié el código de raspado para hacer esto:

 import time browserObj.get(url) time.sleep(3) soup = BeautifulSoup(browserObj.page_source, 'lxml') 

No hay ninguna razón específica por la que elegí 3 segundos, pero desde que agregué este retraso, no tuve el Message: failed to decode response from marionette pude Message: failed to decode response from marionette error de Message: failed to decode response from marionette de ninguna de mis listas de URL a eliminar.


Actualización: octubre, 2018

Esto sigue siendo un problema más de seis meses después. Firefox, Geckodriver, Selenium y PyVirtualDisplay se han actualizado a sus últimas versiones. Este error se repitió espontáneamente sin patrón: a veces funciona y otras no.

Lo que solucionó este problema es boost la RAM en mi servidor de 1 GB a 2 GB. Desde el aumento no ha habido fallas de este tipo.

Para cualquier otra persona que experimente este problema al ejecutar Selenium Webdriver en un contenedor Docker, boost el tamaño del contenedor a 2GB soluciona este problema .

Supongo que esto también afecta a las máquinas físicas si el OP solucionó su problema al actualizar la memoria RAM de su servidor a 2Gb, pero podría ser una coincidencia.

El problema real probable detrás de esto es que el DOM aún no se ha cargado y está activando búsquedas en la página siguiente. Es por eso que el sleep(3) está funcionando en la mayoría de los casos. La solución adecuada es utilizar una clase de espera.

Este es un ejemplo de caso de prueba que utiliza una función de espera para Nextcloud. Es de mi docker-selenium-firefox-python image: https://hub.docker.com/r/nowsci/selenium

Observe cómo se llama a la clase de wait alrededor de cualquier click u get llamadas. Básicamente, lo que esto hace es aprovechar el hecho de que Selenio cambia la ID de la etiqueta HTML en la carga de la página. La función de espera verifica si la nueva ID es diferente a la anterior, y cuando lo es, el DOM se ha cargado.

 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() 

Combina esto con la respuesta de @myol si estás usando Docker.