使用 selenium 的 Python 并行执行

Posted

技术标签:

【中文标题】使用 selenium 的 Python 并行执行【英文标题】:Python parallel execution with selenium 【发布时间】:2017-08-01 15:48:14 【问题描述】:

我对使用 selenium 在 python 中并行执行感到困惑。似乎有几种方法可以解决它,但有些方法似乎已经过时了。

    有一个名为python-wd-parallel 的python 模块,它似乎有一些功能可以做到这一点,但它是从2013 年开始的,现在这还有用吗?我还找到了this example。

    concurrent.futures,这似乎更新了很多,但实现起来并不容易。 谁有在 selenium 中并行执行的工作示例?

    也有只使用线程和执行器来完成工作,但我觉得这会慢一些,因为它没有使用所有的内核并且仍然以串行形式运行。

使用 selenium 进行并行执行的最新方法是什么?

【问题讨论】:

【参考方案1】:

使用joblib's Parallel 模块来做到这一点,它是一个很好的并行执行库。

假设我们有一个名为 urls 的 url 列表,我们希望并行截取每个 URL 的屏幕截图

首先让我们导入必要的库

from selenium import webdriver
from joblib import Parallel, delayed

现在让我们定义一个将屏幕截图作为base64的函数

def take_screenshot(url):
    phantom = webdriver.PhantomJS('/path/to/phantomjs')
    phantom.get(url)
    screenshot = phantom.get_screenshot_as_base64()
    phantom.close()

    return screenshot

现在要并行执行,您要做的是

screenshots = Parallel(n_jobs=-1)(delayed(take_screenshot)(url) for url in urls)

当这一行完成执行时,您将在screenshots 中拥有来自所有已运行进程的所有数据。

关于并行的解释

Parallel(n_jobs=-1) 表示使用所有可以使用的资源 delayed(function)(input)joblib 为您尝试并行运行的函数创建输入的方式

更多信息可以在joblib 文档中找到

【讨论】:

是否有任何直接的方法可以为一个 n_jobs 重用一个 webdriver.PhantomJS 实例,而不是每次迭代都关闭和打开? 您为什么要这样做?尝试从多个进程访问一个 webdriver 实例似乎是个坏主意——我相信这会损害并行化。无论如何,如果您决定继续这样做,则必须使 webdriver 可序列化 非常感谢。我的理由是为每个进程设置一个驱动程序实例(而不是一个驱动程序实例用于多个进程),因为在“如何加速硒”列表中,“重用驱动程序实例”这一行项几乎排在首位 为了不重新创建实例,我会将urls 列表切割成大小均匀的子列表,然后将它们发送到进程,这样就可以生成进程(以及创建 webdriver 实例)每个进程只会发生一次 我认为这取决于。这完全是生成进程和在它们之间传输数据的开销的函数,这与并行化的好处相悖 - 但大多数情况下,如果操作不是很短,您将从使用并行实现中受益【参考方案2】:
    Python Parallel Wd 接缝从它的 github 上消失了(最后一次提交是 9 年前)。它还为硒实现了obsolete protocol。我还没有测试过,我不推荐。

Selenium 性能提升 (concurrent.futures)

简答

threadsprocesses 都会为您的硒代码提供相当大的加速

下面给出了简短的例子。 selenium 工作由返回页面标题的selenium_title 函数完成。这不处理每个线程/进程执行期间发生的异常。对于那种外观长答案 - 处理异常

    线程池concurrent.futures.ThreadPoolExecutor
from selenium import webdriver  
from concurrent import futures

def selenium_title(url):  
  wdriver = webdriver.Chrome() # chrome webdriver
  wdriver.get(url)  
  title = wdriver.title  
  wdriver.quit()
  return title

links = ["https://www.amazon.com", "https://www.google.com"]

with futures.ThreadPoolExecutor() as executor: # default/optimized number of threads
  titles = list(executor.map(selenium_title, links))
    进程池工人concurrent.futures.ProcessPoolExecutor。只需要将上面代码中的ThreadPoolExecuter 替换为ProcessPoolExecutor。它们都派生自Executor 基类。此外,您必须保护 main,如下所示。
if __name__ == '__main__':
 with futures.ProcessPoolExecutor() as executor: # default/optimized number of processes
   titles = list(executor.map(selenium_title, links))

长答案

为什么Threads 与 Python GIL 一起工作?

由于 Python GIL,即使是严格的 Python 也对线程有限制,即使线程将被上下文切换。 Selenium 的实现细节将带来性能提升。 Selenium 通过发送诸如POSTGET (HTTP requests) 之类的命令来工作。这些被发送到浏览器驱动程序服务器。因此,您可能已经知道 I/O 绑定任务 (HTTP requests) 会释放 GIL,从而提高性能。

处理异常

我们可以对上面的例子做一些小的修改来处理产生的线程上的Exceptions。我们不使用executor.map,而是使用executor.submit。这将返回包装在 Future 实例上的标题。

要访问返回的标题,我们可以使用future_titles[index].result,其中索引大小为len(links),或者简单地使用for,如下所示。

with futures.ThreadPoolExecutor() as executor:
  future_titles = [ executor.submit(selenium_title, link) for link in links ]
  for future_title, link in zip(future_titles, links): 
    try:        
      title = future_title.result() # can use `timeout` to wait max seconds for each thread               
    except Exception as exc: # this thread migh have had an exception
      print('url :0 generated an exception: :1'.format(link, exc))

请注意,除了迭代 future_titles 之外,我们还会迭代 links,所以如果某个线程中的 Exception 我们知道哪个 url(link) 对此负责。

futures.Future 类很酷,因为它们让您可以控制从每个线程接收到的结果。比如它是否正确完成或有异常等等,更多关于here。

另外值得一提的是futures.as_completed 更好,如果您不关心线程返回项目的顺序。但由于控制异常的语法有点难看,我在这里省略了它。

性能增益和线程

首先为什么我一直使用线程来加速我的 selenium 代码:​​

在 I/O 绑定任务上,我使用 selenium 的经验表明,在使用进程池 (Process) 或线程 (Threads) 之间存在minimal or no diference。 Here 对于 I/O 绑定任务上的 Python 线程与进程也得出了类似的结论。 我们也知道进程使用自己的内存空间。这意味着更多的内存消耗。此外,进程的生成速度也比线程慢一些。

【讨论】:

以上是关于使用 selenium 的 Python 并行执行的主要内容,如果未能解决你的问题,请参考以下文章

使用 Robot 框架实现 Selenium 网格

使用 selenium TestNG 和 selenium 进行并行测试

selenium+python自动化90-unittest多线程执行用例

在并行执行时,第二个会话将覆盖Selenium测试

Python+Selenium笔记:配置selenium Grid

在 chrome 和 msedge 浏览器上并行运行 selenium python 脚本