如何使用Selenium重新连接到由webdriver打开的浏览器?

22
由于未知原因,我的浏览器打开远程服务器的测试页面非常缓慢。因此,我在思考如果我退出脚本后重新连接到浏览器,但不执行 webdriver.quit() ,那么浏览器将保持打开状态。这可能是一种HOOK或webdriver句柄。我查看了selenium API文档,但没有找到任何函数。我使用Chrome 62,x64,Windows 7,selenium 3.8.0。无论问题是否能够解决,我都将非常感激。

1
你的代码在哪里? - Alan
你尝试过使用选项'user-data-dir'重复使用Chrome现有的用户目录,而不是每次使用WebDriver创建新目录吗?这样做可以避免下载,直接使用缓存中的内容。 - Grasshopper
@Grasshopper 这是什么黑魔法?我使用了这个Chromeoption。它减少了很多加载时间。我会查找一下这个。非常感谢你。 - imbaiye
它是缓存帮助页面加载如此快的原因。 - imbaiye
7个回答

14

不行,在退出脚本后你无法重新连接到之前的Web浏览会话。即使你能够从以前的浏览上下文中提取会话IDCookies和其他会话属性,你也无法将这些属性作为HOOK传递给WebDriver

更干净的方法是调用webdriver.quit()然后开启一个新的浏览上下文


深入探讨

关于重新连接WebDriver到已经运行的浏览上下文,曾有过很多讨论和尝试。在讨论中允许WebDriver附加到运行的浏览器中,Simon Stewart [创建WebDriver] 明确指出:

  • 重新连接到现有的浏览上下文是一种特定于浏览器的功能,因此无法以通用方式实现。
  • 使用,可以在操作系统中遍历打开的窗口并找到正确的IE进程进行附加。
  • 需要以特定的方式和配置启动,这意味着仅附加到运行中的实例在技术上不可能。

简述

webdriver.firefox.useExisting未实现。


1
你完全理解并解决了我的困惑。我会整理一下。非常感谢。 - imbaiye

12

是的,这实际上很容易做到。

selenium <-> webdriver会话由连接URL和session_id表示,您只需重新连接到现有会话即可。

免责声明 - 该方法使用selenium内部属性(在某种程度上为“私有”),这些属性可能会在新版本中更改;最好不要将其用于生产代码;最好不要针对远程SE(您的hub或像BrowserStack / Sauce Labs这样的提供商)使用,因为末尾解释的一个警告/资源耗尽问题。

当初始化webdriver实例时,您需要获取上述属性,示例:

from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://www.google.com/')

# now Google is opened, the browser is fully functional; print the two properties
# command_executor._url (it's "private", not for a direct usage), and session_id

print(f'driver.command_executor._url: {driver.command_executor._url}')
print(f'driver.session_id: {driver.session_id}')

现在知道了这两个属性,另一个实例就可以连接; "诀窍" 是初始化一个 Remote 驱动程序,并提供上面的_url-这样它将连接到正在运行的selenium进程:

driver2 = webdriver.Remote(command_executor=the_known_url)  
# when the started selenium is a local one, the url is in the form 'http://127.0.0.1:62526'

运行代码后,你会看到一个新的浏览器窗口被打开。
这是因为在初始化驱动程序时,selenium库会自动为其启动一个新会话 - 现在你有1个webdriver进程,其中包含2个会话(浏览器实例)。

如果你导航到某个URL,你会发现它在新的浏览器实例上执行,而不是之前留下的那个 - 这不是期望的行为。
此时需要做两件事情 - a) 关闭当前SE会话(“新的”),b) 将该实例切换到以前的会话:

if driver2.session_id != the_known_session_id:   # this is pretty much guaranteed to be the case
    driver2.close()   # this closes the session's window - it is currently the only one, thus the session itself will be auto-killed, yet:
    driver2.quit()    # for remote connections (like ours), this deletes the session, but does not stop the SE server

# take the session that's already running
driver2.session_id = the_known_session_id

# do something with the now hijacked session:
driver.get('https://www.bing.com/')

这就是了 - 您现在已连接到先前/已存在的会话,带有其所有属性(cookie、LocalStorage等)。

顺便说一句,在启动新的远程驱动程序时,您不必提供desired_capabilities - 这些已存储并从您接管的现有会话继承。


注意 - 在系统中运行SE进程可能会导致某些资源耗尽。

每当启动一个进程然后没有关闭 - 就像在第一段代码中一样 - 它将保留在那里,直到您手动终止它。我的意思是 - 在 Windows 中,您将看到一个“chromedriver.exe”进程,您必须在完成后手动终止它。它不能被连接到它的驱动程序关闭为远程Selenium过程。
原因 - 每当您启动本地浏览器实例,然后调用其quit()方法时,它有两个部分 - 第一个部分是从Selenium实例中删除会话(在上面的第二个代码片段中完成的内容),而另一个部分则是停止本地服务(chrome/geckodriver) - 这通常可以正常工作。

事情是,对于远程会话,第二部分缺失 - 您的本地机器无法控制远程过程,这是该远程中心的工作。因此,该第二部分实际上是一个pass python语句 - 无操作。

如果您在远程中心上启动了太多的Selenium服务,并且无法对其进行控制 - 那将导致来自该服务器的资源耗尽。像BrowserStack这样的云提供商采取措施防止这种情况发生 - 它们关闭最近60秒内没有活动的服务等等,但 - 这是您不想做的事情。

至于本地SE服务 - 只需偶尔清理一下您忘记的孤立的Selenium驱动程序即可 :)


亲爱的@TodorMinakov,这段代码没有达到我的期望。我想要每次运行Python程序时选择唯一打开的窗口,并在该窗口上使用Selenium。我将非常感激您的帮助。 - Joe
为此,您需要知道(已存储)连接的URL - 主机:端口和会话ID;就像上面的解释一样。如果您拥有它们,您将按照上述步骤重新连接。 - Todor Minakov
嗨,由于我已经按照您的指示正确填写了command_executor='http://127.0.0.1:*****'sessionNumber=XXXXXXXXXXXXXXXXX。但实际上发生的是打开了两个新窗口[2,3],但没有一个窗口与先前打开的窗口[1]共享相同的数据。因此现在有三个打开的窗口[1,2,3]。 - Joe
Todor,你的方法对我非常有效,感谢分享! - dmohr

1
对于Chromium浏览器,您可以获取浏览器的运行进程列表,检查该进程的命令行,并搜索"--remote-debugging-port="以提取端口号,并在webdriver的初始化中使用它。
将DriverOptions设置为
ChromiumOptions.DebuggerAddress = "127.0.0.1:" & remotePort

没有设置任何其他选项,开始执行

driver As IWebDriver = New EdgeDriver(driverService, ChromiumOptions, CommandTimeout)

那是在VB.net中对我有效的部分,但我认为可以将其翻译并用于其他语言。
然后像平常一样使用驱动程序。您可以从页面离开的地方继续,或者创建一个新窗口并关闭旧窗口,如果您希望进行一种全新的开始,但保留以前的cookie和缓存。
您可能想要终止旧的"webdriver.exe"进程,因为每次重新连接时它都会生成一个新的进程。
通过这种方式,您可以模拟真实用户行为,因为普通用户不总是在私密浏览模式下浏览。他们会带着旧的cookie和缓存数据回到您的网站。因此,您可以优化您的网站,以正确处理旧的缓存资源,并找到用户无需强制重新加载网站或清除缓存的方法,每次网站更新时。

1

混合了此处分享的各种解决方案并进行了调整后,我现在已经将其工作正常如下。脚本将使用先前打开的Chrome窗口(如果存在) - 远程连接可以完全杀死浏览器(如果需要),代码功能也很好。

我希望有一种自动获取会话ID和上一个活动会话的URL的方法,而不必在上一个会话期间将它们写入文件以供使用...

这是我在这里的第一篇文章,如有违反规定之处,请多多包涵。

#Set manually - read/write from a file for automation
session_id =  "e0137cd71ab49b111f0151c756625d31"
executor_url = "http://localhost:50491"

def attach_to_session(executor_url, session_id):
    original_execute = WebDriver.execute
    def new_command_execute(self, command, params=None):
        if command == "newSession":
            # Mock the response
            return {'success': 0, 'value': None, 'sessionId': session_id}
        else:
            return original_execute(self, command, params)
    # Patch the function before creating the driver object
    WebDriver.execute = new_command_execute
    driver = webdriver.Remote(command_executor=executor_url, desired_capabilities={})
    driver.session_id = session_id
    # Replace the patched function with original function
    WebDriver.execute = original_execute
    return driver
 

remote_session = 0

#Try to connect to the last opened session - if failing open new window
try:
    driver = attach_to_session(executor_url,session_id)
    driver.current_url
    print(" Driver has an active window we have connected to it and running here now : ")
    print(" Chrome session ID ",session_id)
    print(" executor_url",executor_url)

except:
    print("No Driver window open - make a new one")
    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()),options=myoptions)
    session_id = driver.session_id 
    executor_url = driver.command_executor._url

0

这是我在2023年使用的一些有效代码。Python 代码:

from selenium import webdrive

# driver = webdriver.Firefox()
# executor_url = driver.command_executor._url
# session_id = driver.session_id
# print(executor_url)
# print(session_id)


class SessionRemote(webdriver.Remote):
    def start_session(self, desired_capabilities, browser_profile=None):
        # Skip the NEW_SESSION command issued by the original driver
        # and set only some required attributes
        self.w3c = True


driver = SessionRemote(command_executor='your.executor.url', desired_capabilities=None)
driver.session_id = 'your-session-id'

我在this的重复线程上找到了部分答案,以及另一个我现在无法找到的stackoverflow线程上的类定义。取消注释前5行并注释最后2行。然后相反地添加您的URLsessionID

编辑:仅当您在单独的终端中运行第一部分时才起作用。您可以在另一个终端中运行第二部分,并将其重复使用多次。


0
感谢原帖作者开启了这个话题,对于我来说,这次讨论非常有帮助,让我能够提升我的Python技能,适用于特定的用例。
我尝试了上面Todor's提供的方法,但在以下设置中没有成功: 操作系统 = Mac OS Ventura;Python版本 = 3.10.4;SE版本 = 4.12.0
然而,我找到了Shabad提供的方法,非常有帮助。
以下是我针对上述设置编写的代码,更加优雅地运行:
使用Apple Script启动一个具有特定配置文件的新会话。
do shell script "open -a /Applications/Microsoft\\ Edge.app --args --remote-debugging-port=9222 --user-data-dir='~/seEdge'"

Python代码附加到此会话
# Python code to attach to a specific browser session
from selenium import webdriver
import time # used to add delay in the automation script

# set option to make execution of automation scripts
edge_options = webdriver.EdgeOptions()
edge_options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
driver = webdriver.Edge(options=edge_options)

-1

不谈为什么你认为保持一个开放的浏览器窗口会解决缓慢的问题,你实际上不需要一个句柄来做到这一点。只需在不关闭会话或换言之,不调用driver.quit()的情况下继续运行测试,就可以了。不过问题是:有没有自带其自己运行器的框架?像Cucumber一样?

无论如何,你必须有一些“设置”和“清除”代码。所以你需要在“清理”阶段确保浏览器回到它的初始状态。那意味着:

  • 显示空白页
  • 删除会话的cookies

因此,在这种情况下,设置代码将为您打开浏览器并导航到此页面,然后您运行所有测试,清理部分将关闭窗口和浏览器。这看起来不像是一个好的实践,因为每次都没有一个干净的会话而添加可能的问题,但可以作为一种解决方法。另外,如果在每个测试之后清除cookie,也不确定页面会如何表现。 - Eugene S
函数<code>webdriver.Chrome.get(url)</code>将等待页面完全加载。这需要很长时间来加载远程页面。因此,我想打开并加载页面一次。我认为webdriver在退出脚本后仍在后台运行。因此,我想找到webdriver的句柄并重新连接到webdriver以重用已打开的页面。 - imbaiye
你在我回复后编辑了你的评论,但看看它是否有帮助。 - Eugene S
很抱歉。Stackoverflow 设置了 5 分钟的编辑时间。几乎每次我都来不及编辑完成。 - imbaiye
我的英语很差,这是我的不足。 - imbaiye
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接