"Failed to decode response from marionette" Python/Firefox 无头抓取脚本中的消息

"Failed to decode response from marionette" message in Python/Firefox headless scraping script

美好的一天,我在此处和 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...') 都不再有效。

错误消息说:

Message: failed to decode response from marionette

任何进一步的 selenium 请求页面 return 错误:

Message: tried to run command without establishing a connection


显示一些代码:

当我创建驱动程序时:

    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。因为服务器是32bit的服务器,我只能用chromedriver v0.33,只支持Chrome v60-62。目前 Chrome 是 v65,在 DigitalOcean 上我似乎没有简单的方法可以恢复到旧版本 - 这就是我坚持使用 Firefox 的原因。

我仍然不知道为什么会这样,但我可能已经找到了解决方法。我在一些文档中读到可能存在竞争条件(关于什么,我不确定,因为不应该有两个项目竞争相同的资源)。

我更改了抓取代码来执行此操作:

import time

browserObj.get(url)

time.sleep(3)

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

我选择 3 秒没有具体原因,但自从添加此延迟后,我的任何要抓取的 URL 列表都没有出现 Message: failed to decode response from marionette 错误。


更新:2018 年 10 月

这在六个月后仍然是一个问题。 Firefox、Geckodriver、Selenium 和 PyVirtualDisplay 都已更新到最新版本。此错误无规律地自发重复出现:有时有效,有时无效。

解决此问题的方法是将我服务器上的 RAM 从 1 GB 增加到 2 GB。自增加以来,没有发生过此类故障。

对于在 Docker 容器中使用 运行 selenium webdriver 时遇到此问题的任何其他人,increasing the container size to 2gb fixes this issue

如果 OP 通过将他们的服务器 RAM 升级到 2Gb 解决了他们的问题,我想这也会影响物理机器,但这可能是巧合。

这背后可能真正的问题是 DOM 尚未加载,您正在下一页触发搜索。这就是 sleep(3) 在大多数情况下都有效的原因。正确的解决方法是使用 wait class.

这是一个使用 Nextcloud 等待函数的示例测试用例。它来自我的 docker-selenium-firefox-python 图片:https://hub.docker.com/r/nowsci/selenium

注意 wait class 是如何围绕任何 clickget 调用调用的。基本上,这样做是利用 selenium 在页面加载时更改 HTML 标记的 ID 这一事实。 wait 函数检查新 ID 是否与旧 ID 不同,如果不同,则 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.headless = True
        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()

如果您使用 Docker,请结合@myol 的回答。

问题是你没有关闭驱动。我犯了同样的错误。在 Linux 中的 htop,我注意到我在 firefox 未关闭进程中占用了我电脑的所有 26 GB。

我希望这可以节省其他一些可怜的灵魂我刚刚花在这上面的时间。

Download an old version of firefox(具体来说,对我来说是 v66),然后将 selenium 指向那里:

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

试试这个,对于 Ubuntu 16.04

  1. 安装firefox
sudo apt update
sudo apt install firefox
  1. 检查 firefox 是否安装正确
which firefox

会 return /usr/bin/firefox

  1. 转到 geckodriver 版本页面。找到适用于您的平台的最新版本的驱动程序并下载。例如:
wget https://github.com/mozilla/geckodriver/releases/download/v0.24.0/geckodriver-v0.24.0-linux64.tar.gz
  1. 解压文件:
tar -xvzf geckodriver*
  1. 使其可执行:
chmod +x geckodriver
  1. 将其移至 $PATH 并授予 root 访问权限
sudo mv geckodriver /usr/bin/
cd /usr/bin
sudo chown root:root geckodriver
  1. 安装selenium
pip3 install selenium
  1. firefoxgeckodriver 添加到 $PATH
sudo vim ~/.bashrc

添加两行:

export PATH=$PATH:"/usr/bin/firefox"
export PATH=$PATH:"/usr/bin/geckodriver"
  1. 重启您的实例
sudo reboot

这个错误信息...

Message: failed to decode response from marionette

...暗示 GeckoDriverMarionette 之间的通信是 interrupted/broken.


出现此问题的部分原因及解决方法如下:

  • 在讨论中 Crash during command execution results in "Internal Server Error: Failed to decode response from marionette" @whimboo mentions, while executing your tests 可能会强制 Firefox 的父进程崩溃并出现错误:

    DEBUG   <- 500 Internal Server Error {"value":{"error":"unknown error","message":"Failed to decode response from marionette","stacktrace":...}...}
    
    • 分析:当前消息有些误导,Geckdriver 需要以更好的方式处理这种情况并报告应用程序意外退出。此问题仍未解决。
  • 讨论中Failed to decode response from marionette with Firefox >= 65 @rafagonc mentioned, this issue can occur when using GeckoDriver / or in docker environment, due to presence of Zombie process that hangs even after invoking driver.quit(). At times, when you open many browsing instances one after another, your system may run out of memory or out of PIDs. See:

    • 正如@andreastt 提到的解决方案,以下配置应该可以解决 Docker 的内存不足问题:

      --memory 1024mb --shm-size 2g
      

Steps: Configure SHM size in the docker container

  • 同样,在本地主机上执行测试时,建议保持以下(最低)配置:

    --memory 1024mb
    

其他注意事项

由于您使用的二进制文件版本不兼容,也可能会出现此问题。

解决方案:

  • JDK升级到最近的水平JDK 8u341
  • Selenium 升级到当前水平 Version 3.141.59
  • GeckoDriver 升级到 GeckoDriver v0.26.0 级别。
  • Firefox 版本升级到 Firefox v72.0 级别。
  • 以非 root 用户身份执行 Test

GeckoDriver, Selenium and Firefox Browser compatibility chart


tl;博士

[e10s] Crash in libyuv::ARGBSetRow_X86


参考

您可以在以下位置找到相关的详细讨论:

  • Browsing context has been discarded using GeckoDriver Firefox through Selenium

此错误可能是由于多次启动浏览器而没有正确关闭浏览器造成的。

如果是 OP 的 Python 代码 driver.quit() 应该在成功或失败 运行 之后调用。必须捕获所有异常。

无论您的系统有多少内存,如果浏览器未正确关闭,随后的 运行s 最终将无法启动另一个浏览器。在那种情况下,增加更多内存只会推迟那一刻。

  1. 要检查孤立的浏览器进程,请在 Linux 上使用 ps aux 命令(或在 Windows 上的 Powershell 中)。
  2. 要终止此类进程,请使用 killall firefox 命令或在使用 Firefox 扩展支持版本时使用 killall firefox-esr。这可能需要 sudo。

参考文献: