Python程序通过Selenium WebDriver驱动Firefox。代码嵌入在try
/ except
块中,如下所示:
session = selenium.webdriver.Firefox(firefox_profile)
try:
# do stuff
except (Exception, KeyboardInterrupt) as exception:
logging.info("Caught exception.")
traceback.print_exc(file=sys.stdout)
如果程序因错误而中止,则WebDriver会话未关闭,因此Firefox窗口保持打开状态。但是如果程序以KeyboardInterrupt
异常中止,则Firefox窗口关闭(我想因为WebDriver会话也被释放了),我想避免这种情况。
我知道两个异常都经过相同的处理程序,因为我在两种情况下都看到了"Caught exception"
消息。
如何避免使用KeyboardInterrupt
关闭Firefox窗口?
答案 0 :(得分:5)
我有一个解决方案,但它非常难看。
当按下Ctrl + C时,python会收到一个中断信号(SIGINT),它会在整个进程树中传播。 Python还会生成一个KeyboardInterrupt,因此您可以尝试处理绑定到您的进程逻辑的内容,但不会影响与子进程耦合的逻辑。
要影响哪些信号传递给您的子流程,您必须在通过subprocess.Popen
生成流程之前指定信号的处理方式。
有各种选项,这个选项来自another answer:
import subprocess
import signal
def preexec_function():
# Ignore the SIGINT signal by setting the handler to the standard
# signal handler SIG_IGN.
signal.signal(signal.SIGINT, signal.SIG_IGN)
my_process = subprocess.Popen(
["my_executable"],
preexec_fn = preexec_function
)
问题是,您不是那个叫Popen
的人,即delegated to selenium。 SO上有various discussions。根据我所收集的其他尝试影响信号屏蔽的解决方案,在调用Popen
之前未执行屏蔽时容易出现故障。
另请注意,有一个big fat warning regarding the use of preexec_fn in the python documentation,因此请自行决定使用。
"幸运的是" python允许在运行时覆盖函数,所以我们可以这样做:
>>> import monkey
>>> import selenium.webdriver
>>> selenium.webdriver.common.service.Service.start = monkey.start
>>> ffx = selenium.webdriver.Firefox()
>>> # pressed Ctrl+C, window stays open.
KeyboardInterrupt
>>> ffx.service.assert_process_still_running()
>>> ffx.quit()
>>> ffx.service.assert_process_still_running()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.6/site-packages/selenium/webdriver/common/service.py", line 107, in assert_process_still_running
return_code = self.process.poll()
AttributeError: 'NoneType' object has no attribute 'poll'
monkey.py ,如下所示:
import errno
import os
import platform
import subprocess
from subprocess import PIPE
import signal
import time
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.common import utils
def preexec_function():
signal.signal(signal.SIGINT, signal.SIG_IGN)
def start(self):
"""
Starts the Service.
:Exceptions:
- WebDriverException : Raised either when it can't start the service
or when it can't connect to the service
"""
try:
cmd = [self.path]
cmd.extend(self.command_line_args())
self.process = subprocess.Popen(cmd, env=self.env,
close_fds=platform.system() != 'Windows',
stdout=self.log_file,
stderr=self.log_file,
stdin=PIPE,
preexec_fn=preexec_function)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^
except TypeError:
raise
except OSError as err:
if err.errno == errno.ENOENT:
raise WebDriverException(
"'%s' executable needs to be in PATH. %s" % (
os.path.basename(self.path), self.start_error_message)
)
elif err.errno == errno.EACCES:
raise WebDriverException(
"'%s' executable may have wrong permissions. %s" % (
os.path.basename(self.path), self.start_error_message)
)
else:
raise
except Exception as e:
raise WebDriverException(
"The executable %s needs to be available in the path. %s\n%s" %
(os.path.basename(self.path), self.start_error_message, str(e)))
count = 0
while True:
self.assert_process_still_running()
if self.is_connectable():
break
count += 1
time.sleep(1)
if count == 30:
raise WebDriverException("Can not connect to the Service %s" % self.path)
code for start is from selenium,突出显示添加的行。 这是一个粗暴的黑客,它也可能咬你。祝你好运:D
答案 1 :(得分:1)
我被@einsweniger answer感染了,非常感谢!这段代码对我有用:
import subprocess, functools, os
import selenium.webdriver
def new_start(*args, **kwargs):
def preexec_function():
# signal.signal(signal.SIGINT, signal.SIG_IGN) # this one didn't worked for me
os.setpgrp()
default_Popen = subprocess.Popen
subprocess.Popen = functools.partial(subprocess.Popen, preexec_fn=preexec_function)
try:
new_start.default_start(*args, **kwargs)
finally:
subprocess.Popen = default_Popen
new_start.default_start = selenium.webdriver.common.service.Service.start
selenium.webdriver.common.service.Service.start = new_start
它比先前的答案更具侵入性,因为它不会重写完整功能的代码。但是另一方面,它会修改subprocess.Popen
函数本身,有些人可以将其称为相当丑陋的举动。
无论如何,它都能完成工作,并且Service.start
的源代码更改时,您不必更新代码。