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.
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.
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.
Essayez ceci, pour buntu 16.04:
firefox
Sudo apt update
Sudo apt install firefox
firefox
est bien installéwhich firefox
Reviendra /usr/bin/firefox
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
tar -xvzf geckodriver*
chmod +x geckodriver
$PATH
et donnez à root
l'accèsSudo mv geckodriver /usr/bin/
cd /usr/bin
Sudo chown root:root geckodriver
Selenium
pip3 install Selenium
firefox
et geckodriver
à $PATH
Sudo vim ~/.bashrc
Ajoutez les deux lignes:
export PATH=$PATH:"/usr/bin/firefox"
export PATH=$PATH:"/usr/bin/geckodriver"
Sudo reboot
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'
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.