web-dev-qa-db-fra.com

Message "Impossible de décoder la réponse de la marionnette" dans le script de raclage sans tête Python / Firefox

Bonjour, j'ai fait un certain nombre de recherches ici et sur google et je n'ai pas encore trouvé de solution pour résoudre ce problème.

Le scénario est le suivant:

J'ai un script Python (2.7) qui parcourt un certain nombre d'URL (p. Ex., Pensez aux pages Amazon, aux critiques de grattage). Chaque page a la même disposition HTML, grattant simplement des informations différentes. J'utilise Selenium avec un navigateur sans tête car ces pages ont du javascript qui doit s'exécuter pour saisir les informations.

J'exécute ce script sur ma machine locale (OSX 10.10). Firefox est la dernière version 59. Selenium est la version 3.11.0 et utilise geckodriver v0.20.

Ce script localement n'a aucun problème, il peut parcourir toutes les URL et gratter les pages sans problème.

Maintenant, quand je mets le script sur mon serveur, la seule différence est qu'il s'agit d'Ubuntu 16.04 (32 bits). J'utilise le geckodriver approprié (toujours v0.20) mais tout le reste est le même (Python 2.7, Selenium 3.11). Il semble planter au hasard le navigateur sans tête, puis toutes les browserObjt.get('url...') ne fonctionnent plus.

Les messages d'erreur disent:

Message: échec du décodage de la réponse de la marionnette

Toute autre demande de pages Selenium renvoie l'erreur:

Message: a tenté d'exécuter la commande sans établir de connexion


Pour afficher du code:

Lorsque je crée le pilote:

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

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

driver est passé à la fonction du script en tant que paramètre browserObj qui est ensuite utilisé pour appeler des pages spécifiques, puis une fois chargé, il est passé à BeautifulSoup pour l'analyse:

browserObj.get(url)

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

L'erreur peut pointer vers la ligne BeautifulSoup qui plante le navigateur.

Quelle est la cause probable de ce problème et que puis-je faire pour résoudre le problème?


Edit: Ajout d'une trace de pile qui pointe vers la même chose:

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

Remarque: Ce script fonctionnait auparavant avec Chrome. Étant donné que le serveur est un serveur 32 bits, je ne peux utiliser que chromedriver v0.33, qui ne prend en charge que Chrome v60-62. Actuellement Chrome est v65 et sur DigitalOcean) Je ne semble pas avoir un moyen facile de revenir à une ancienne version - c'est pourquoi je suis coincé avec Firefox.

12
Reily Bourne

Je ne sais toujours pas pourquoi cela se produit, mais j'ai peut-être trouvé un moyen de contourner le problème. J'ai lu dans certains documents qu'il peut y avoir une condition de concurrence (sur quoi, je ne suis pas sûr car il ne devrait pas y avoir deux éléments en compétition pour les mêmes ressources).

J'ai changé le code de raclage pour ce faire:

import time

browserObj.get(url)

time.sleep(3)

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

Aucune raison précise pour laquelle j'ai choisi 3 secondes mais depuis l'ajout de ce délai je n'ai pas eu le Message: failed to decode response from marionette erreur de l'une de mes listes d'URL à gratter.


Mise à jour: octobre 2018

Cela continue d'être un problème plus de six mois plus tard. Firefox, Geckodriver, Selenium et PyVirtualDisplay ont tous été mis à jour vers leurs dernières versions. Cette erreur a continué à se reproduire spontanément sans motif: parfois fonctionnant et parfois non.

Ce qui a résolu ce problème augmente RAM sur mon serveur de 1 Go à 2 Go. Depuis l'augmentation, il n'y a eu aucune défaillance de ce type.

7
Reily Bourne

Pour toute autre personne rencontrant ce problème lors de l'exécution du pilote Web Selenium dans un conteneur Docker, augmentation de la taille du conteneur à 2 Go corrige ce problème .

Je suppose que cela affecte également les machines physiques si l'OP a résolu leur problème en mettant à niveau leur serveur RAM à 2 Go, mais cela pourrait être une coïncidence.

7
myol

Essayez ceci, pour buntu 16.04:

  1. Installez firefox
Sudo apt update
Sudo apt install firefox
  1. Vérifiez que firefox est bien installé
which firefox

Reviendra /usr/bin/firefox

  1. Accédez à la page geckodriver releases. Trouvez la dernière version du pilote pour votre plate-forme et téléchargez-la. Par exemple:
wget https://github.com/mozilla/geckodriver/releases/download/v0.24.0/geckodriver-v0.24.0-linux64.tar.gz
  1. Extrayez le fichier avec:
tar -xvzf geckodriver*
  1. Rendez-le exécutable:
chmod +x geckodriver
  1. Déplacez-le vers $PATH et donnez à root l'accès
Sudo mv geckodriver /usr/bin/
cd /usr/bin
Sudo chown root:root geckodriver
  1. Installez Selenium
pip3 install Selenium
  1. Ajoutez firefox et geckodriver à $PATH
Sudo vim ~/.bashrc

Ajoutez les deux lignes:

export PATH=$PATH:"/usr/bin/firefox"
export PATH=$PATH:"/usr/bin/geckodriver"
  1. Reboot votre instance
Sudo reboot
0
sashaboulouds

J'espère que cela fera gagner à une autre pauvre âme les heures que je viens de consacrer à cela.

Téléchargez une ancienne version de firefox (spécifiquement, v66 pour moi), et pointez Selenium là:

firefox_binary='/home/user/Downloads/old_firefox/firefox/firefox'
0
jason m

Le vrai problème probable derrière cela est que le DOM n'a pas encore été chargé et que vous lancez des recherches sur la page suivante. C'est pourquoi la sleep(3) fonctionne dans la plupart des cas. La solution appropriée consiste à utiliser une classe d'attente.

Ceci est un exemple de cas de test utilisant une fonction d'attente pour Nextcloud. C'est à partir de mon image docker-Selenium-firefox-python: https://hub.docker.com/r/nowsci/Selenium

Remarquez comment la classe wait est appelée autour des appels click ou get. Fondamentalement, cela profite du fait que Selenium modifie l'ID de la balise HTML au chargement de la page. La fonction d'attente vérifie si le nouvel ID est différent de l'ancien, et quand c'est le cas, le DOM s'est chargé.

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

Combinez cela avec la réponse de @myol si vous utilisez Docker.

0
Fmstrat