Python/Firefox 无头抓取脚本中的“无法解码木偶的响应”消息
Posted
技术标签:
【中文标题】Python/Firefox 无头抓取脚本中的“无法解码木偶的响应”消息【英文标题】:"Failed to decode response from marionette" message in Python/Firefox headless scraping script 【发布时间】:2018-09-18 23:14:34 【问题描述】:美好的一天,我在这里和谷歌上进行了多次搜索,但尚未找到解决此问题的解决方案。
场景是:
我有一个循环遍历多个 URL 的 Python 脚本 (2.7)(例如,想想亚马逊页面、抓取评论)。每个页面都有相同的 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...')
不再工作。
错误信息说:
消息:无法解码来自木偶的响应
对页面的任何进一步 selenium 请求都会返回错误:
消息:试图在未建立连接的情况下运行命令
显示一些代码:
当我创建驱动程序时:
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】:这个错误可能是浏览器启动次数过多而没有正确关闭造成的。
如果是 OP 的 Python 代码 driver.quit()
,则应在运行成功或失败后调用。必须捕获所有异常。
无论您的系统有多少内存,如果浏览器没有正确关闭,后续运行最终将无法启动另一个。在那种情况下,添加更多内存只会推迟那个时刻。
-
要检查孤立的浏览器进程,请在 Linux 上(或在 Windows 上的 Powershell 中)使用
ps aux
命令。
要终止此类进程,请使用 killall firefox
命令或在使用 Firefox 扩展支持版本时使用 killall firefox-esr
)。这可能需要 sudo。
参考文献:
https://www.selenium.dev/documentation/webdriver/browser/windows/#quitting-the-browser-at-the-end-of-a-session https://www.selenium.dev/selenium/docs/api/py/index.html?highlight=quit#example-1【讨论】:
【参考方案2】:这背后可能的真正问题是 DOM 尚未加载,而您正在触发下一页上的搜索。这就是为什么sleep(3)
在大多数情况下都能正常工作的原因。正确的解决方法是使用等待类。
这是一个使用 Nextcloud 的等待函数的示例测试用例。它来自我的 docker-selenium-firefox-python 图像:https://hub.docker.com/r/nowsci/selenium
注意wait
类是如何围绕任何click
或get
调用的。基本上,这样做是利用 selenium 在页面加载时更改 HTML
标记的 ID 的事实。等待函数检查新 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 的答案结合起来。
【讨论】:
【参考方案3】:此错误消息...
Message: failed to decode response from marionette
...表示 GeckoDriver 和 Marionette 之间的通信中断/中断。
此问题的一些原因和解决方案如下:
在Crash during command execution results in "Internal Server Error: Failed to decode response from marionette"@whimboo 的讨论中提到,在执行您的测试时Selenium 可能会导致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 中提到,在docker 环境中使用GeckoDriver / FirefoxDriver 或ChromeDriver 时可能会出现此问题,因为Zombie process 的存在甚至在之后挂起调用driver.quit()
。有时,当您一个接一个地打开许多浏览实例时,您的系统可能会出现内存不足或 PID 不足的情况。见:Selenium using too much RAM with Firefox
作为@andreastt 提到的解决方案,以下配置应该可以解决 Docker 的内存不足问题:
--memory 1024mb --shm-size 2g
步骤:配置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【讨论】:
【参考方案4】:在 Ubuntu 16.04 上试试这个:
-
安装
firefox
sudo apt update
sudo apt install firefox
-
检查
firefox
是否安装正确
which firefox
将返回/usr/bin/firefox
-
转到
geckodriver
发布页面。为您的平台找到最新版本的驱动程序并下载。例如:
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
并授予root
访问权限
sudo mv geckodriver /usr/bin/
cd /usr/bin
sudo chown root:root geckodriver
-
安装
selenium
pip3 install selenium
-
将
firefox
和geckodriver
添加到$PATH
sudo vim ~/.bashrc
添加两行:
export PATH=$PATH:"/usr/bin/firefox"
export PATH=$PATH:"/usr/bin/geckodriver"
-
重启您的实例
sudo reboot
【讨论】:
【参考方案5】:我希望这可以节省我刚刚花在这上面的时间。
Download an old version of firefox(特别是对我来说是 v66),并在那里指出 selenium:
firefox_binary='/home/user/Downloads/old_firefox/firefox/firefox'
【讨论】:
【参考方案6】:问题是你没有关闭驱动。我犯了同样的错误。在 Linux 中使用 Whit htop,我注意到我占用了我的 pc whit 未关闭进程的所有 26 GB。
【讨论】:
是的!这也是我的问题,我注意到有很多 geckodriver.exe 进程仍在运行,所以杀死它们修复了它。【参考方案7】:对于在 Docker 容器中运行 selenium webdriver 时遇到此问题的任何其他人,increasing the container size to 2gb 修复了this issue。
如果 OP 通过将服务器 RAM 升级到 2Gb 来解决问题,我猜这也会影响物理机,但这可能是巧合。
【讨论】:
如果有办法给你多个支持,我支持!花了几个小时弄清楚这一点!非常感谢! 谢谢,这正是我的问题。显然,default is only 64mb... 我在尝试通过window.resize_to
然后driver.screenshot_as
进行完整内容截图时遇到了这样的问题,关于我可以缩放浏览器窗口的大小,“免费内存”似乎与你所说的一样.
太棒了!
非常感谢,我会被卡很久【参考方案8】:
我仍然不知道为什么会发生这种情况,但我可能已经找到了解决方法。我在一些文档中读到可能存在竞争条件(我不确定,因为不应该有两个项目竞争相同的资源)。
我改变了抓取代码来做到这一点:
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。自增加以来,没有出现过此类失败。
【讨论】:
您的问题解决了吗?我也随机收到相同的错误。 ***.com/questions/49831841/… 可能是因为这个bug 在我发现一种在 DOM 上进行适当等待的方法之前,我被这个问题困扰了一段时间。如果您尚未解决此问题,请参阅我的答案。希望对您有所帮助! 确认将我的 Droplet 实例的 RAM 从 1GB 增加到 2GB 也解决了这个问题。以上是关于Python/Firefox 无头抓取脚本中的“无法解码木偶的响应”消息的主要内容,如果未能解决你的问题,请参考以下文章