24

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"在这两种情况下都看到了消息。

如何避免关闭 Firefox 窗口KeyboardInterrupt

4

2 回答 2

7

我有一个解决方案,但它非常难看。

当按下 Ctrl+C 时,python 会收到一个中断信号 (SIGINT),该信号会在整个进程树中传播。Python 还会生成一个 KeyboardInterrupt,因此您可以尝试处理与您的进程的逻辑绑定的东西,但与子进程耦合的逻辑不会受到影响。

要影响将哪些信号传递给您的子进程,您必须在通过subprocess.Popen.

有多种选择,这个取自另一个答案

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
)

问题是,您不是委托给 seleniumPopen的那个人。关于 SO有各种讨论。从我收集到的其他尝试影响信号屏蔽的解决方案中,如果在调用.Popen

还要记住,在 python 文档中,关于 preexec_fn 的使用有一个很大的警告,所以请自行决定使用它。

“幸运的是”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)

start的代码来自 selenium,添加的行突出显示。这是一个粗略的黑客,它可能会咬你。祝你好运:D

于 2018-03-16T14:36:44.707 回答
3

我受到@einsweniger回答的启发,非常感谢!这段代码对我有用:

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更改时更新代码。

于 2020-06-17T13:30:21.767 回答