1. 自动化需求拆解:为什么从打开和关闭浏览器说起做自动化测试或者爬虫开发的朋友,基本都逃不过Selenium这一关。很多人刚开始接触自动化,信心满满地装好环境,结果第一个脚本就在“打开浏览器”这一步卡住了。这个现象特别常见,不是代码写错,而是没搞清楚Selenium的底层逻辑和浏览器驱动的关系。我最早踩这个坑的时候,折腾了整整一个下午,最后才发现是驱动版本不对。“打开、关闭浏览器和网页”看起来是两个简单的动作,实际上涵盖了WebDriver初始化的核心机制、会话(Session)的生命周期管理、页面加载策略等多个关键知识点。你把这两个基础操作吃透了,后面处理窗口切换、等待策略、页面元素定位都会顺畅很多。这篇文章就是围绕这两个基础操作展开,把WebDriver的启动方式、浏览器驱动的对应关系、get方法的行为、close和quit的区别一次性讲清楚,并且附上我在实际项目中排查问题的一些原始经验。适合谁来读?刚接触Selenium的初学者、写了几天脚本但总在环境配置上出问题的半新手,以及想系统梳理基础API细节的自动化从业者。这篇文章不会讲花哨的高级技巧,就是把地基打牢。毕竟,你连浏览器都关不明白,怎么敢让脚本跑一整夜无人值守?先说个整体思路:Selenium模拟用户操作浏览器的核心,就是通过一套标准化的WebDriver协议,把Python代码里的请求翻译成浏览器能听懂的命令。你用webdriver.Chrome()是在启动一个真正的Chrome实例,而不是什么模拟器。这跟用Requests直接发HTTP请求有本质区别——Requests拿到的是HTML源码,Selenium拿到的是一个真实的、能执行JavaScript、能渲染CSS的浏览器环境。这个区别决定了Selenium适合处理需要真实渲染的页面,也决定了它的性能开销会比其他方案大得多。明白了这个底层逻辑,接下来选型和配置的思路就很清晰了。2. 环境与工具选型:驱动版本匹配才是隐藏的核心难点2.1 Python环境的准备写Selenium脚本用Python 3.6以上版本就可以,我用的是3.8到3.12都跑过,没遇到兼容性问题。创建虚拟环境是必须养成的习惯,```python python -m venv selenium_env然后激活环境再安装依赖,这一步能避免不少其他项目的包冲突。一个小建议,Windows上激活虚拟环境后确认一下命令行前缀出现了`(selenium_env)`,否则安装的包还是会跑到全局环境里去。 Selenium库的安装就一条命令: ```bash pip install selenium目前主流版本是4.x,和3.x的API有一些差异,但基础的打开关闭操作基本兼容。不过有个细节值得注意,4.x版本里多个WebDriver的配置参数从options对象传入,而不是直接写在webdriver.Chrome()的参数里,比如多行ChromeOptions的设置方法有很大变化。下面我会用4.x的写法示范,如果你还在用3.x,建议尽快升级。2.2 WebDriver和浏览器的版本匹配逻辑很多人的第一个坑就在这里。Selenium本身只是个客户端库,它真正去操作浏览器,靠的是一个叫做WebDriver的可执行文件。Chrome对应的是ChromeDriver,Firefox对应的是GeckoDriver,Edge对应的是EdgeDriver。这几个驱动是独立于Selenium库的,需要单独下载。版本匹配有个铁律:WebDriver的大版本必须和浏览器的大版本一致。比如你的Chrome是120版,就要用120版的ChromeDriver,差一个大版本都不行。我用114的Chrome配118的驱动时,直接报SessionNotCreatedException,这个错误信息写得很清楚——“This version of ChromeDriver only supports Chrome version 114”,所以遇到错误别慌,先读一下报错文本。怎么查自己浏览器的版本?Chrome浏览器地址栏输入chrome://version,第一行就是版本号。下载驱动的时候,根据这个版本号去对应的存储库找,下载完成后解压到一个专门的目录。我这里有个习惯性的做法,把驱动放在C:\webdrivers这类固定目录,然后把这个目录加进系统PATH环境变量。2.3 用WebDriver Manager省去手动管理驱动的麻烦手动管理驱动版本确实太折腾,尤其是Chrome经常自动更新,今天写好的脚本明天就可能报错。这个问题有个成熟的解决方案:```python pip install webdriver-manager这个库会在每次脚本运行时自动检测浏览器版本,然后下载匹配的驱动,省去了手动维护的烦恼。用法也很简单: ```python from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))我第一次用这个方案的时候,第一次运行会看到下载驱动的日志,等一两秒后就自动启动浏览器了。这个方案在团队协作里的价值很高,别人拉下来你的代码,不用再费劲配置驱动,直接run就行。注意新同事入职时最容易在这一环节卡壳,用Manager能省去大量时间。2.4 ChromeOptions的基础配置打开浏览器之前还要养成传options的习惯。核心参数就这几个:from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument('--start-maximized') # 最大化启动 options.add_argument('--incognito') # 无痕模式 options.add_argument('--disable-notifications') # 禁用通知弹窗 driver = webdriver.Chrome(options=options)--start-maximized一定要加,不加的话窗口初始大小可能偏小,造成页面元素被遮挡。无头模式也是高频参数,在服务器上跑脚本时没有显示器,就用这个参数,样子是:options.add_argument('--headless=new')这里--headless=new是Chrome近版本的新无头模式,比老的--headless更稳定,很多页面里JS渲染或者懒加载更适合新模式执行。3. 打开浏览器:从最小示例到完整方案3.1 最基础的浏览器启动先看一个完整的最小示例,所有东西都在里面:from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument('--start-maximized') driver = webdriver.Chrome(options=options) driver.get("https://www.baidu.com") print("当前页面标题:", driver.title) print("当前页面URL:", driver.current_url)运行这段代码,Chrome窗口会自动弹出并打开百度。driver.title拿到的是页面标签上的文字,driver.current_url拿的是当前地址栏的完整URL。这两个属性是后面做页面断言时的高频API,建议熟记。webdriver.Chrome()这行代码本质上做了三件事:找到ChromeDriver可执行文件、启动一个浏览器实例、和这个实例建立会话连接。这个会话就是后面所有操作的基础。如果这行代码报错,九成是驱动路径或版本问题,往下看第八章。3.2 get方法:加载网页的完整行为driver.get(url)是最常用的打开网页的方法,它的行为和手动在地址栏输入回车很像,但有几个细节需要注意。第一,get()会进行阻塞操作,代码会等待页面加载完再继续执行下一行。但这里的“页面加载完”不等于“所有元素都加载完”,JavaScript异步发起的请求、懒加载图片、动态渲染的列表都不会被等待。所以你经常会在get()之后立刻去定位元素,结果傻了眼——元素不存在。这个问题的标准解法是显式等待,后面讲。第二,get()跳转到新页面后,浏览器的历史记录里会保留之前的页面,按浏览器的返回按钮可以回到上一个页面。Selenium为这个操作提供了driver.back()和driver.forward()两个方法,在做多页面流程测试时会用到。第三,get()可以接收带协议的完整URL,也可以接收不带协议的字符串。我测试过“www.baidu.com”这种写法也能正常打开,但强烈建议写完整协议,否则某些浏览器可能自动补全成搜索引擎地址,导致你定位半天找不到页面。3.3 导航栏操作:刷新、前进、后退浏览器操作除了打开关闭,还有三个常用的导航方法:driver.refresh() # 刷新当前页面 driver.back() # 后退一页 driver.forward() # 前进一页刷新在爬取实时数据的场景里很实用,比如监控某个页面价格,每隔一段时间刷新重新抓取。但刷新会重新加载整个页面,耗时较长,如果只需要更新某块数据,考虑用AJAX请求代替刷新会更高效。前进和后退在测试“页面跳转是否符合预期”时很有用,比如测试购物流程——从商品页进入详情页,再返回商品列表页,验证列表状态是否被保留。3.4 直接打开本地HTML文件Selenium不仅能打开网络地址,还能打开本地HTML文件。用法就一条:from pathlib import Path html_path = Path("demo.html").resolve() driver.get(f"file://{html_path}")这个方法在调试页面时特别好用。你可以把目标页面保存成HTML文件,然后离线调试定位逻辑,速度极快。注意必须是file://开头的URL格式,直接传C:\xxx\demo.html这种路径是不被接受的。还有一个好处,本地文件的加载速度非常快,特别适合测试元素的定位表达式是否正确。3.5 多标签页的打开逻辑driver.get()默认打开新的标签页,不是新窗口。一次会话里多次调用get(),每次都覆盖当前标签页的内容。如果你想开一个新标签页而不影响当前页面,可以用JavaScript实现:driver.execute_script("window.open('https://www.example.com')") # 切到新标签页 driver.switch_to.window(driver.window_handles[-1])driver.window_handles返回当前会话所有标签页的标识列表,switch_to.window()实现切换。如果你的脚本需要同时操作多个页面,比如从页面A提取信息,到页面B提交表单,这种多标签切换能力就非常重要。新手最容易犯的错是开了新标签页后没有切换,还在旧页面上操作,结果元素找不到,排查了半天才发现是标签页上下文不对。当初我自己就曾被这个坑困住了一个多小时。3.6 打开网页的异常处理网络环境不好时,get()可能抛出TimeoutException。默认的页面加载超时时间是30秒,你可以按需调整:from selenium.common.exceptions import TimeoutException driver.set_page_load_timeout(15) # 设置15秒超时 try: driver.get("https://www.example.com") except TimeoutException: print("页面加载超时,但页面可能已部分加载") # 可以选择截图或者继续尝试定位元素这里有个经验值得注意:超时异常抛出后,浏览器并不会自动关闭。你可以尝试关闭弹窗、点击页面右下角的重试按钮,甚至继续定位某些已经加载出来的元素。不要一超时就立刻quit(),很多大型网站的首页包含多个资源文件,加载超十分钟也正常,关键是看核心内容是否已经渲染完毕。4. 关闭浏览器:close与quit的哲学区别4.1 两者的本质区别driver.close()和driver.quit()最容易被混为一谈,很多初学者代码里随便写一个,反正都能退出。但从底层行为来看,两者的区别很重要:close():关闭当前聚焦的标签页。如果浏览器里只有一个标签页,那它同时也会关闭整个浏览器。quit():关闭整个浏览器会话,无论当前开了多少个标签页。它会清理WebDriver进程,释放端口。我建议做自动化脚本坚持用quit()。原因很简单:close()只关标签页,不释放WebDriver进程。如果你在一个长任务里反复close()却不quit(),系统里会积累僵尸进程。Windows上的表现是出现大量残留的chromedriver.exe进程,CPU和内存都受影响。更麻烦的是,这些残留进程可能占用端口,导致下次启动时新脚本连不上。4.2 正确使用with语句管理浏览器生命周期既然要保证quit()一定会执行,最稳妥的办法是用上下文管理器。Selenium 4中的webdriver对象本身就支持with语法:from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument('--start-maximized') with webdriver.Chrome(options=options) as driver: driver.get("https://www.example.com") # 这里写业务逻辑 title = driver.title print(title) # 退出with块之后,driver自动执行quit()这个写法能保证在代码抛出任何异常时,浏览器资源也能被正常释放。我在跑半夜的定时任务时,这个特性对稳定性保障很重要,没有它可能半夜脚本挂了浏览器一直开着,第二天满屏僵尸进程。4.3 使用try-finally做手动资源释放如果你所在的团队Python版本比较保守,或者你不想用with语法,那就用try-finally:driver = None try: options = Options() options.add_argument('--start-maximized') driver = webdriver.Chrome(options=options) driver.get("https://www.example.com") # 业务逻辑 finally: if driver: driver.quit()注意这里一定要先把driver初始化为None,防的是webdriver.Chrome()本身抛出异常,那此时driver变量根本没赋值,进入finally时driver为None,quit()调用就会报错。4.4 单标签页场景下close的特殊表现如果你只开了一个标签页,直接调用close()其实也会关闭整个浏览器。但如果你开了三个标签页,close()只会关掉焦点所在的那个。所以有的业务场景里,需要先切换到目标标签页再关闭它,例如:# 关闭除了第一个标签页之外的所有标签页 for handle in driver.window_handles[1:]: driver.switch_to.window(handle) driver.close() # 切回第一个标签页继续操作 driver.switch_to.window(driver.window_handles[0])这个场景在批量处理多个页面数据时很有用。比如你要访问A、B、C三个页面,抓完C的数据,可以关闭C标签页,切回A标签页继续操作,避免标签页数量无上限地增长。4.5 强制关闭与异常兜底有时候quit()也会失败,比如浏览器进程卡死,或者浏览器弹出了“脚本正在使浏览器变慢”的警告框,quit()会一直等到超时。这种场景需要一些暴力的兜底方法,根据操作系统的不同,杀掉进程的方式也不同:Windows下可以用:import subprocess subprocess.run(["taskkill", "/F", "/IM", "chrome.exe"]) subprocess.run(["taskkill", "/F", "/IM", "chromedriver.exe"])这块命令在脚本里慎用,它会杀掉所有Chrome窗口,包括你自己正在用的浏览器。我自己在手动调试的时候基本不用这招,只在无人值守的服务器任务里,检测到quit()抛异常后当作最后手段。真实的项目里,我建议把你的业务逻辑用try-finally包住,finally里先试quit(),再捕获异常做兜底,稳中求稳。4.6 释放资源的完整仪式一个有点仪式感的收尾流程,在退出浏览器前可以做几件事:try: # 1. 尝试拿一下页面上的必要信息 final_data = driver.page_source # 2. 截图留底 driver.save_screenshot("final_state.png") # 3. 清理Cookie状态 driver.delete_all_cookies() except Exception: pass finally: driver.quit()第2步在排查问题时非常有价值,尤其是脚本跑挂后无法复现现场,截图就是唯一的排查依据。第3步能避免一些网站做登录态校验时给你抛出奇怪的校验错误。5. 完整实操脚本:一个三步走的示例项目5.1 场景设计做一个模拟日常任务:打开多个网页、验证页面标题、然后退出浏览器。这个示例覆盖了前面讲到的核心操作,结构也贴近真实项目里的脚本骨架。场景就不写某个具体网站了,用“某跨平台系统”代指,既避免了具体的网站依赖带来的不稳定因素,也方便你根据自己的目标替换URL。5.2 完整代码import time from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 配置浏览器选项 def create_options(): options = Options() options.add_argument('--start-maximized') options.add_argument('--disable-extensions') options.add_argument('--disable-gpu') options.add_argument('--no-sandbox') # 在容器环境内建议加上 return options def main(): options = create_options() driver = None try: driver = webdriver.Chrome(options=options) # 1. 打开第一个页面 driver.get("https://example.com") print(f"[步骤1] 页面标题: {driver.title}") # 2. 用显式等待确保关键元素出现 wait = WebDriverWait(driver, 10) wait.until(EC.presence_of_element_located((By.TAG_NAME, "body"))) print("[步骤2] 页面主体元素已加载") # 3. 打开第二个页面(新标签页) driver.execute_script("window.open('https://example.org')") print("当前标签页数量:", len(driver.window_handles)) # 4. 切换到新标签页 driver.switch_to.window(driver.window_handles[-1]) wait.until(EC.presence_of_element_located((By.TAG_NAME, "body"))) # 5. 拿到标题后返回第一个标签页 second_title = driver.title print(f"[步骤4] 第二个页面标题: {second_title}") driver.close() # 关闭第二个标签页 driver.switch_to.window(driver.window_handles[0]) print(f"[步骤5] 回到第一个页面,标题: {driver.title}") except Exception as e: # 打印完整异常,不吞掉,方便定位 import traceback traceback.print_exc() finally: if driver: driver.quit() print("浏览器已安全退出") if __name__ == "__main__": main()5.3 代码效果对照这段脚本运行后的控制台输出大概是:[步骤1] 页面标题: Example Domain [步骤2] 页面主体元素已加载 当前标签页数量: 2 [步骤4] 第二个页面标题: Example Domain [步骤5] 回到第一个页面,标题: Example Domain简单解释几个设计用意:--disable-extensions可以减少无关浏览器插件的干扰,避免插件在页面上注入元素导致定位不准确。这个选项在跑爬虫和自动化测试时几乎是标配。--no-sandbox是在没有图形界面的容器或服务器环境里常用的参数,但本地Windows环境不需要。traceback.print_exc()打印完整堆栈,不要只打印str(e),完整堆栈信息里能直接看到是哪一行出问题。真实项目排查时,一半的bug是靠堆栈第一眼发现的。finally里的driver.quit()是所有情况的兜底。5.4 参数选择逻辑在WebDriverWait(driver, 10)里,10秒超时是经验值。太短了页面稍慢就误报,太长了如果页面确实卡住,脚本要白等很久。一般针对同局域网或访问速度快的站设5秒,通用互联网页面设10秒,如果是内容复杂的平台可以设30秒。显式等待的底层是按固定间隔轮询DOM状态,不是真的一直阻塞到超时,所以适当设长一点不会浪费太多时间。从实践来看,显式等待比time.sleep(固定秒数)可靠得多。sleep是拍脑袋,显式等待是盯着条件成立才继续。这两个的区别从设计方案上就把脚本的稳定性补上了一大块。6. 常见问题与排查技巧实录6.1 ChromeDriver版本不匹配报错形式:selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 114 Current browser version is 120.0.6099.109 with ...处理方法:看报错文本里的Current browser version字段,确认当前浏览器的具体版本号。下载对应版本的ChromeDriver,替换掉旧驱动。或者用webdriver-manager自动管理,一劳永逸。这个报错是“SessionNotCreatedException”,问题出在启动阶段,连浏览器窗口都不会弹出来。你的代码逻辑写得再漂亮,这一步过不去一切都白搭。所以我在教团队新人的时候,让他们先跑通“打开浏览器然后关闭”这样一条最小脚本,确认环境没问题,再往里填业务代码。6.2 WebDriver可执行文件路径报错报错形式:selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH处理方法:确认驱动文件和脚本在同一个目录。或者手动指定服务位置:from selenium.webdriver.chrome.service import Service service = Service(r"C:\webdrivers\chromedriver.exe") driver = webdriver.Chrome(service=service)这里再强调一次,executable_path参数在Selenium 4里已经被移除了,不需要在webdriver.Chrome()里传这个参数,要用Service来包装。6.3 元素找不到报错形式:selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element这个报错往往不是真的元素不存在,而是页面还没加载完你的定位语句就执行了。解决办法是用显式等待代替直接定位:from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "target-id")) )这个写法是Selenium自动化里最高频显式等待用法,比time.sleep靠谱得多。presence_of_element_located检查的是DOM里有没有这个节点,而visibility_of_element_located检查的是节点是否可见,具体用哪个看你实际目的。如果是要点击按钮,后者更稳。6.4 浏览器闪退打开浏览器后立即崩溃,一个常见原因是机器内存不足,Selenium启动Chrome的开销不小,尤其是开着无头模式时仍然占满一个进程的全部资源。你可以用--disable-dev-shm-usage参数缓解:options.add_argument('--disable-dev-shm-usage')这参数在容器环境里几乎必须要加,因为在某些Linux环境中/dev/shm很小,Chrome一跑多标签页就会崩。本地Windows机器一般不会触发这个问题,但如果你的脚本要在服务器上跑,建议标配。6.5 权限与沙箱问题报错形式:[ERROR:bus.cc(163)] Failed to connect to the bus这个报错在Linux容器环境里很常见,不影响打开浏览器,但日志刷得让人不安。结合--no-sandbox和--disable-dev-shm-usage能明显减少这类噪音。但注意--no-sandbox在不受信任的网页环境中会降低安全性,仅推荐用于可信网站的自动化场景。6.6 常见问题速查表问题现象可能原因解决方案启动时SessionNotCreatedException驱动版本与浏览器不匹配下载匹配版本或用webdriver-manager“executable needs to be in PATH”驱动未加入PATH手动指定Service路径NoSuchElementException页面未加载完成就定位改用WebDriverWait显式等待浏览器闪退内存不足或沙箱配置问题加--disable-dev-shm-usage参数脚本结束后浏览器不退出只用了close()没quit()统一用quit()或with语法页面加载超时网络慢或页面资源多调整set_page_load_timeout并捕获异常7. 从基础到进阶的实操心得7.1 给初学者的三个建议第一,从小脚本开始。不要一上来就写一个几千行的爬虫框架,先写一个打开百度、打印标题、关闭浏览器,跑通环境。这个最小闭环的建立,比任何文档都更有价值。第二,遇到报错先读完整堆栈。Selenium的报错信息其实非常详细,无论是“which is only supported in Chrome version”还是“no such element”,都清楚指向了问题的根源。很多初学者直接跳过报错文本去乱猜,反而浪费时间。把报错信息复制到搜索引擎也比瞎猜高效得多。第三,尽量做好每10分钟左右的调试节奏控制。长时间盯着代码容易产生视觉盲区,随手print(driver.title)就能快速判断代码执行到哪一步。我写脚本的习惯是每个关键步骤都打印一行带标记的日志,比如[步骤1]、[步骤2],跑挂之后看日志就知道卡在哪个环节,排查效率上天。7.2 项目落地时几个容易忽略的细节浏览器窗口尺寸会影响页面布局。桌面端和移动端的元素定位表达式可能不一样,--start-maximized能减少这类麻烦,但如果你要模拟移动端用户,还得用mobile_emulation参数设置设备指标。Cookie处理要想到。脚本执行过程中如果需要登录,登录态的Cookie要妥善保存和复用。先手动登录一次,然后把driver.get_cookies()序列化保存到本地,下次运行直接add_cookie()加载,能省掉每次都要扫码或输密码的烦恼。日志和截图是调试的王牌。driver.save_screenshot()在关键节点截图,脚本出错时翻图就知道页面当时长什么样。配上driver.page_source保存当前的HTML,可以离线用自己本地的浏览器复现问题。这俩方法对定位疑难杂症帮助极大,我在实际项目里几乎每步都截。7.3 脚本稳定性提升的几个惯用套路在循环里跑批量任务时,建议每个任务包一层异常捕获,不要让单个页面的失败搞挂整个脚本:urls = ["https://example.com", "https://example.org", "https://example.net"] for url in urls: try: driver.get(url) print(driver.title) except Exception as e: print(f"访问失败: {url}, 错误: {e}") continue这样即使某个网站反应慢,脚本也不会中断。再加上每次访问之间做短暂停,避免对目标站造成压力,也避免自己的IP被临时限制。平常跑数据采集脚本,保持这个节奏基本能稳定跑很久。7.4 后续还能扩展的方向基础操作讲完,你后面自然会接触到几个进阶方向:页面元素定位和交互(点击、输入、拖拽)、显式等待与隐式等待的完整组合策略、iframe与弹窗处理、ActionChains模拟键盘鼠标操作、headless模式的性能调优、页面加载性能分析等。还有一个值得提醒的问题:并不是所有网页都适合用Selenium来抓取。如果你的目标站是普通后端渲染的页面,直接用Requests可能更轻快;只有遇到前端渲染、AJAX动态加载,或者需要模拟登录、点击、滚动这类真实用户行为时,Selenium才体现出不可替代的价值。选择什么工具,取决于目标页面的实际结构和你的需求复杂度,这个判断能力比任何具体API都重要。我在实际项目中还发现一个细节,使用Selenium打开网页时,浏览器会默认加载图片、CSS、字体等大量资源,很多其实没必要加载。通过ChromeOptions设置--blink-settings=imagesEnabled=false禁用图片加载,打开页面的速度能快不少。具体操作是:options.add_argument('--blink-settings=imagesEnabled=false')这个方法在批量采集时效果尤其明显,带宽占用下降,整体耗时有肉眼可见的缩短。当然,如果你要截图验证页面视觉样式,那图片是必须加载的,这个需求场景自己权衡。踩过几次坑之后,我的一个深刻体会是,自动化脚本和手工操作之间的差距,大部分都来自“等待”策略的不完善。Selenium提供显式等待、隐式等待、强制sleep三套方案,什么时候用哪个、组合起来怎么用,确实是决定脚本稳定性的关键。这篇文章不可能一次全讲完,但只要你把打开和关闭这个周期管理好,把异常退出兜底做好,一个基础稳定的大坝就立起来了。后面再往上面加盖高楼,也只是时间问题。
1. 自动化需求拆解:为什么从打开和关闭浏览器说起做自动化测试或者爬虫开发的朋友,基本都逃不过Selenium这一关。很多人刚开始接触自动化,信心满满地装好环境,结果第一个脚本就在“打开浏览器”这一步卡住了。这个现象特别常见,不是代码写错,而是没搞清楚Selenium的底层逻辑和浏览器驱动的关系。我最早踩这个坑的时候,折腾了整整一个下午,最后才发现是驱动版本不对。“打开、关闭浏览器和网页”看起来是两个简单的动作,实际上涵盖了WebDriver初始化的核心机制、会话(Session)的生命周期管理、页面加载策略等多个关键知识点。你把这两个基础操作吃透了,后面处理窗口切换、等待策略、页面元素定位都会顺畅很多。这篇文章就是围绕这两个基础操作展开,把WebDriver的启动方式、浏览器驱动的对应关系、get方法的行为、close和quit的区别一次性讲清楚,并且附上我在实际项目中排查问题的一些原始经验。适合谁来读?刚接触Selenium的初学者、写了几天脚本但总在环境配置上出问题的半新手,以及想系统梳理基础API细节的自动化从业者。这篇文章不会讲花哨的高级技巧,就是把地基打牢。毕竟,你连浏览器都关不明白,怎么敢让脚本跑一整夜无人值守?先说个整体思路:Selenium模拟用户操作浏览器的核心,就是通过一套标准化的WebDriver协议,把Python代码里的请求翻译成浏览器能听懂的命令。你用webdriver.Chrome()是在启动一个真正的Chrome实例,而不是什么模拟器。这跟用Requests直接发HTTP请求有本质区别——Requests拿到的是HTML源码,Selenium拿到的是一个真实的、能执行JavaScript、能渲染CSS的浏览器环境。这个区别决定了Selenium适合处理需要真实渲染的页面,也决定了它的性能开销会比其他方案大得多。明白了这个底层逻辑,接下来选型和配置的思路就很清晰了。2. 环境与工具选型:驱动版本匹配才是隐藏的核心难点2.1 Python环境的准备写Selenium脚本用Python 3.6以上版本就可以,我用的是3.8到3.12都跑过,没遇到兼容性问题。创建虚拟环境是必须养成的习惯,```python python -m venv selenium_env然后激活环境再安装依赖,这一步能避免不少其他项目的包冲突。一个小建议,Windows上激活虚拟环境后确认一下命令行前缀出现了`(selenium_env)`,否则安装的包还是会跑到全局环境里去。 Selenium库的安装就一条命令: ```bash pip install selenium目前主流版本是4.x,和3.x的API有一些差异,但基础的打开关闭操作基本兼容。不过有个细节值得注意,4.x版本里多个WebDriver的配置参数从options对象传入,而不是直接写在webdriver.Chrome()的参数里,比如多行ChromeOptions的设置方法有很大变化。下面我会用4.x的写法示范,如果你还在用3.x,建议尽快升级。2.2 WebDriver和浏览器的版本匹配逻辑很多人的第一个坑就在这里。Selenium本身只是个客户端库,它真正去操作浏览器,靠的是一个叫做WebDriver的可执行文件。Chrome对应的是ChromeDriver,Firefox对应的是GeckoDriver,Edge对应的是EdgeDriver。这几个驱动是独立于Selenium库的,需要单独下载。版本匹配有个铁律:WebDriver的大版本必须和浏览器的大版本一致。比如你的Chrome是120版,就要用120版的ChromeDriver,差一个大版本都不行。我用114的Chrome配118的驱动时,直接报SessionNotCreatedException,这个错误信息写得很清楚——“This version of ChromeDriver only supports Chrome version 114”,所以遇到错误别慌,先读一下报错文本。怎么查自己浏览器的版本?Chrome浏览器地址栏输入chrome://version,第一行就是版本号。下载驱动的时候,根据这个版本号去对应的存储库找,下载完成后解压到一个专门的目录。我这里有个习惯性的做法,把驱动放在C:\webdrivers这类固定目录,然后把这个目录加进系统PATH环境变量。2.3 用WebDriver Manager省去手动管理驱动的麻烦手动管理驱动版本确实太折腾,尤其是Chrome经常自动更新,今天写好的脚本明天就可能报错。这个问题有个成熟的解决方案:```python pip install webdriver-manager这个库会在每次脚本运行时自动检测浏览器版本,然后下载匹配的驱动,省去了手动维护的烦恼。用法也很简单: ```python from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))我第一次用这个方案的时候,第一次运行会看到下载驱动的日志,等一两秒后就自动启动浏览器了。这个方案在团队协作里的价值很高,别人拉下来你的代码,不用再费劲配置驱动,直接run就行。注意新同事入职时最容易在这一环节卡壳,用Manager能省去大量时间。2.4 ChromeOptions的基础配置打开浏览器之前还要养成传options的习惯。核心参数就这几个:from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument('--start-maximized') # 最大化启动 options.add_argument('--incognito') # 无痕模式 options.add_argument('--disable-notifications') # 禁用通知弹窗 driver = webdriver.Chrome(options=options)--start-maximized一定要加,不加的话窗口初始大小可能偏小,造成页面元素被遮挡。无头模式也是高频参数,在服务器上跑脚本时没有显示器,就用这个参数,样子是:options.add_argument('--headless=new')这里--headless=new是Chrome近版本的新无头模式,比老的--headless更稳定,很多页面里JS渲染或者懒加载更适合新模式执行。3. 打开浏览器:从最小示例到完整方案3.1 最基础的浏览器启动先看一个完整的最小示例,所有东西都在里面:from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument('--start-maximized') driver = webdriver.Chrome(options=options) driver.get("https://www.baidu.com") print("当前页面标题:", driver.title) print("当前页面URL:", driver.current_url)运行这段代码,Chrome窗口会自动弹出并打开百度。driver.title拿到的是页面标签上的文字,driver.current_url拿的是当前地址栏的完整URL。这两个属性是后面做页面断言时的高频API,建议熟记。webdriver.Chrome()这行代码本质上做了三件事:找到ChromeDriver可执行文件、启动一个浏览器实例、和这个实例建立会话连接。这个会话就是后面所有操作的基础。如果这行代码报错,九成是驱动路径或版本问题,往下看第八章。3.2 get方法:加载网页的完整行为driver.get(url)是最常用的打开网页的方法,它的行为和手动在地址栏输入回车很像,但有几个细节需要注意。第一,get()会进行阻塞操作,代码会等待页面加载完再继续执行下一行。但这里的“页面加载完”不等于“所有元素都加载完”,JavaScript异步发起的请求、懒加载图片、动态渲染的列表都不会被等待。所以你经常会在get()之后立刻去定位元素,结果傻了眼——元素不存在。这个问题的标准解法是显式等待,后面讲。第二,get()跳转到新页面后,浏览器的历史记录里会保留之前的页面,按浏览器的返回按钮可以回到上一个页面。Selenium为这个操作提供了driver.back()和driver.forward()两个方法,在做多页面流程测试时会用到。第三,get()可以接收带协议的完整URL,也可以接收不带协议的字符串。我测试过“www.baidu.com”这种写法也能正常打开,但强烈建议写完整协议,否则某些浏览器可能自动补全成搜索引擎地址,导致你定位半天找不到页面。3.3 导航栏操作:刷新、前进、后退浏览器操作除了打开关闭,还有三个常用的导航方法:driver.refresh() # 刷新当前页面 driver.back() # 后退一页 driver.forward() # 前进一页刷新在爬取实时数据的场景里很实用,比如监控某个页面价格,每隔一段时间刷新重新抓取。但刷新会重新加载整个页面,耗时较长,如果只需要更新某块数据,考虑用AJAX请求代替刷新会更高效。前进和后退在测试“页面跳转是否符合预期”时很有用,比如测试购物流程——从商品页进入详情页,再返回商品列表页,验证列表状态是否被保留。3.4 直接打开本地HTML文件Selenium不仅能打开网络地址,还能打开本地HTML文件。用法就一条:from pathlib import Path html_path = Path("demo.html").resolve() driver.get(f"file://{html_path}")这个方法在调试页面时特别好用。你可以把目标页面保存成HTML文件,然后离线调试定位逻辑,速度极快。注意必须是file://开头的URL格式,直接传C:\xxx\demo.html这种路径是不被接受的。还有一个好处,本地文件的加载速度非常快,特别适合测试元素的定位表达式是否正确。3.5 多标签页的打开逻辑driver.get()默认打开新的标签页,不是新窗口。一次会话里多次调用get(),每次都覆盖当前标签页的内容。如果你想开一个新标签页而不影响当前页面,可以用JavaScript实现:driver.execute_script("window.open('https://www.example.com')") # 切到新标签页 driver.switch_to.window(driver.window_handles[-1])driver.window_handles返回当前会话所有标签页的标识列表,switch_to.window()实现切换。如果你的脚本需要同时操作多个页面,比如从页面A提取信息,到页面B提交表单,这种多标签切换能力就非常重要。新手最容易犯的错是开了新标签页后没有切换,还在旧页面上操作,结果元素找不到,排查了半天才发现是标签页上下文不对。当初我自己就曾被这个坑困住了一个多小时。3.6 打开网页的异常处理网络环境不好时,get()可能抛出TimeoutException。默认的页面加载超时时间是30秒,你可以按需调整:from selenium.common.exceptions import TimeoutException driver.set_page_load_timeout(15) # 设置15秒超时 try: driver.get("https://www.example.com") except TimeoutException: print("页面加载超时,但页面可能已部分加载") # 可以选择截图或者继续尝试定位元素这里有个经验值得注意:超时异常抛出后,浏览器并不会自动关闭。你可以尝试关闭弹窗、点击页面右下角的重试按钮,甚至继续定位某些已经加载出来的元素。不要一超时就立刻quit(),很多大型网站的首页包含多个资源文件,加载超十分钟也正常,关键是看核心内容是否已经渲染完毕。4. 关闭浏览器:close与quit的哲学区别4.1 两者的本质区别driver.close()和driver.quit()最容易被混为一谈,很多初学者代码里随便写一个,反正都能退出。但从底层行为来看,两者的区别很重要:close():关闭当前聚焦的标签页。如果浏览器里只有一个标签页,那它同时也会关闭整个浏览器。quit():关闭整个浏览器会话,无论当前开了多少个标签页。它会清理WebDriver进程,释放端口。我建议做自动化脚本坚持用quit()。原因很简单:close()只关标签页,不释放WebDriver进程。如果你在一个长任务里反复close()却不quit(),系统里会积累僵尸进程。Windows上的表现是出现大量残留的chromedriver.exe进程,CPU和内存都受影响。更麻烦的是,这些残留进程可能占用端口,导致下次启动时新脚本连不上。4.2 正确使用with语句管理浏览器生命周期既然要保证quit()一定会执行,最稳妥的办法是用上下文管理器。Selenium 4中的webdriver对象本身就支持with语法:from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument('--start-maximized') with webdriver.Chrome(options=options) as driver: driver.get("https://www.example.com") # 这里写业务逻辑 title = driver.title print(title) # 退出with块之后,driver自动执行quit()这个写法能保证在代码抛出任何异常时,浏览器资源也能被正常释放。我在跑半夜的定时任务时,这个特性对稳定性保障很重要,没有它可能半夜脚本挂了浏览器一直开着,第二天满屏僵尸进程。4.3 使用try-finally做手动资源释放如果你所在的团队Python版本比较保守,或者你不想用with语法,那就用try-finally:driver = None try: options = Options() options.add_argument('--start-maximized') driver = webdriver.Chrome(options=options) driver.get("https://www.example.com") # 业务逻辑 finally: if driver: driver.quit()注意这里一定要先把driver初始化为None,防的是webdriver.Chrome()本身抛出异常,那此时driver变量根本没赋值,进入finally时driver为None,quit()调用就会报错。4.4 单标签页场景下close的特殊表现如果你只开了一个标签页,直接调用close()其实也会关闭整个浏览器。但如果你开了三个标签页,close()只会关掉焦点所在的那个。所以有的业务场景里,需要先切换到目标标签页再关闭它,例如:# 关闭除了第一个标签页之外的所有标签页 for handle in driver.window_handles[1:]: driver.switch_to.window(handle) driver.close() # 切回第一个标签页继续操作 driver.switch_to.window(driver.window_handles[0])这个场景在批量处理多个页面数据时很有用。比如你要访问A、B、C三个页面,抓完C的数据,可以关闭C标签页,切回A标签页继续操作,避免标签页数量无上限地增长。4.5 强制关闭与异常兜底有时候quit()也会失败,比如浏览器进程卡死,或者浏览器弹出了“脚本正在使浏览器变慢”的警告框,quit()会一直等到超时。这种场景需要一些暴力的兜底方法,根据操作系统的不同,杀掉进程的方式也不同:Windows下可以用:import subprocess subprocess.run(["taskkill", "/F", "/IM", "chrome.exe"]) subprocess.run(["taskkill", "/F", "/IM", "chromedriver.exe"])这块命令在脚本里慎用,它会杀掉所有Chrome窗口,包括你自己正在用的浏览器。我自己在手动调试的时候基本不用这招,只在无人值守的服务器任务里,检测到quit()抛异常后当作最后手段。真实的项目里,我建议把你的业务逻辑用try-finally包住,finally里先试quit(),再捕获异常做兜底,稳中求稳。4.6 释放资源的完整仪式一个有点仪式感的收尾流程,在退出浏览器前可以做几件事:try: # 1. 尝试拿一下页面上的必要信息 final_data = driver.page_source # 2. 截图留底 driver.save_screenshot("final_state.png") # 3. 清理Cookie状态 driver.delete_all_cookies() except Exception: pass finally: driver.quit()第2步在排查问题时非常有价值,尤其是脚本跑挂后无法复现现场,截图就是唯一的排查依据。第3步能避免一些网站做登录态校验时给你抛出奇怪的校验错误。5. 完整实操脚本:一个三步走的示例项目5.1 场景设计做一个模拟日常任务:打开多个网页、验证页面标题、然后退出浏览器。这个示例覆盖了前面讲到的核心操作,结构也贴近真实项目里的脚本骨架。场景就不写某个具体网站了,用“某跨平台系统”代指,既避免了具体的网站依赖带来的不稳定因素,也方便你根据自己的目标替换URL。5.2 完整代码import time from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 配置浏览器选项 def create_options(): options = Options() options.add_argument('--start-maximized') options.add_argument('--disable-extensions') options.add_argument('--disable-gpu') options.add_argument('--no-sandbox') # 在容器环境内建议加上 return options def main(): options = create_options() driver = None try: driver = webdriver.Chrome(options=options) # 1. 打开第一个页面 driver.get("https://example.com") print(f"[步骤1] 页面标题: {driver.title}") # 2. 用显式等待确保关键元素出现 wait = WebDriverWait(driver, 10) wait.until(EC.presence_of_element_located((By.TAG_NAME, "body"))) print("[步骤2] 页面主体元素已加载") # 3. 打开第二个页面(新标签页) driver.execute_script("window.open('https://example.org')") print("当前标签页数量:", len(driver.window_handles)) # 4. 切换到新标签页 driver.switch_to.window(driver.window_handles[-1]) wait.until(EC.presence_of_element_located((By.TAG_NAME, "body"))) # 5. 拿到标题后返回第一个标签页 second_title = driver.title print(f"[步骤4] 第二个页面标题: {second_title}") driver.close() # 关闭第二个标签页 driver.switch_to.window(driver.window_handles[0]) print(f"[步骤5] 回到第一个页面,标题: {driver.title}") except Exception as e: # 打印完整异常,不吞掉,方便定位 import traceback traceback.print_exc() finally: if driver: driver.quit() print("浏览器已安全退出") if __name__ == "__main__": main()5.3 代码效果对照这段脚本运行后的控制台输出大概是:[步骤1] 页面标题: Example Domain [步骤2] 页面主体元素已加载 当前标签页数量: 2 [步骤4] 第二个页面标题: Example Domain [步骤5] 回到第一个页面,标题: Example Domain简单解释几个设计用意:--disable-extensions可以减少无关浏览器插件的干扰,避免插件在页面上注入元素导致定位不准确。这个选项在跑爬虫和自动化测试时几乎是标配。--no-sandbox是在没有图形界面的容器或服务器环境里常用的参数,但本地Windows环境不需要。traceback.print_exc()打印完整堆栈,不要只打印str(e),完整堆栈信息里能直接看到是哪一行出问题。真实项目排查时,一半的bug是靠堆栈第一眼发现的。finally里的driver.quit()是所有情况的兜底。5.4 参数选择逻辑在WebDriverWait(driver, 10)里,10秒超时是经验值。太短了页面稍慢就误报,太长了如果页面确实卡住,脚本要白等很久。一般针对同局域网或访问速度快的站设5秒,通用互联网页面设10秒,如果是内容复杂的平台可以设30秒。显式等待的底层是按固定间隔轮询DOM状态,不是真的一直阻塞到超时,所以适当设长一点不会浪费太多时间。从实践来看,显式等待比time.sleep(固定秒数)可靠得多。sleep是拍脑袋,显式等待是盯着条件成立才继续。这两个的区别从设计方案上就把脚本的稳定性补上了一大块。6. 常见问题与排查技巧实录6.1 ChromeDriver版本不匹配报错形式:selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 114 Current browser version is 120.0.6099.109 with ...处理方法:看报错文本里的Current browser version字段,确认当前浏览器的具体版本号。下载对应版本的ChromeDriver,替换掉旧驱动。或者用webdriver-manager自动管理,一劳永逸。这个报错是“SessionNotCreatedException”,问题出在启动阶段,连浏览器窗口都不会弹出来。你的代码逻辑写得再漂亮,这一步过不去一切都白搭。所以我在教团队新人的时候,让他们先跑通“打开浏览器然后关闭”这样一条最小脚本,确认环境没问题,再往里填业务代码。6.2 WebDriver可执行文件路径报错报错形式:selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH处理方法:确认驱动文件和脚本在同一个目录。或者手动指定服务位置:from selenium.webdriver.chrome.service import Service service = Service(r"C:\webdrivers\chromedriver.exe") driver = webdriver.Chrome(service=service)这里再强调一次,executable_path参数在Selenium 4里已经被移除了,不需要在webdriver.Chrome()里传这个参数,要用Service来包装。6.3 元素找不到报错形式:selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element这个报错往往不是真的元素不存在,而是页面还没加载完你的定位语句就执行了。解决办法是用显式等待代替直接定位:from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "target-id")) )这个写法是Selenium自动化里最高频显式等待用法,比time.sleep靠谱得多。presence_of_element_located检查的是DOM里有没有这个节点,而visibility_of_element_located检查的是节点是否可见,具体用哪个看你实际目的。如果是要点击按钮,后者更稳。6.4 浏览器闪退打开浏览器后立即崩溃,一个常见原因是机器内存不足,Selenium启动Chrome的开销不小,尤其是开着无头模式时仍然占满一个进程的全部资源。你可以用--disable-dev-shm-usage参数缓解:options.add_argument('--disable-dev-shm-usage')这参数在容器环境里几乎必须要加,因为在某些Linux环境中/dev/shm很小,Chrome一跑多标签页就会崩。本地Windows机器一般不会触发这个问题,但如果你的脚本要在服务器上跑,建议标配。6.5 权限与沙箱问题报错形式:[ERROR:bus.cc(163)] Failed to connect to the bus这个报错在Linux容器环境里很常见,不影响打开浏览器,但日志刷得让人不安。结合--no-sandbox和--disable-dev-shm-usage能明显减少这类噪音。但注意--no-sandbox在不受信任的网页环境中会降低安全性,仅推荐用于可信网站的自动化场景。6.6 常见问题速查表问题现象可能原因解决方案启动时SessionNotCreatedException驱动版本与浏览器不匹配下载匹配版本或用webdriver-manager“executable needs to be in PATH”驱动未加入PATH手动指定Service路径NoSuchElementException页面未加载完成就定位改用WebDriverWait显式等待浏览器闪退内存不足或沙箱配置问题加--disable-dev-shm-usage参数脚本结束后浏览器不退出只用了close()没quit()统一用quit()或with语法页面加载超时网络慢或页面资源多调整set_page_load_timeout并捕获异常7. 从基础到进阶的实操心得7.1 给初学者的三个建议第一,从小脚本开始。不要一上来就写一个几千行的爬虫框架,先写一个打开百度、打印标题、关闭浏览器,跑通环境。这个最小闭环的建立,比任何文档都更有价值。第二,遇到报错先读完整堆栈。Selenium的报错信息其实非常详细,无论是“which is only supported in Chrome version”还是“no such element”,都清楚指向了问题的根源。很多初学者直接跳过报错文本去乱猜,反而浪费时间。把报错信息复制到搜索引擎也比瞎猜高效得多。第三,尽量做好每10分钟左右的调试节奏控制。长时间盯着代码容易产生视觉盲区,随手print(driver.title)就能快速判断代码执行到哪一步。我写脚本的习惯是每个关键步骤都打印一行带标记的日志,比如[步骤1]、[步骤2],跑挂之后看日志就知道卡在哪个环节,排查效率上天。7.2 项目落地时几个容易忽略的细节浏览器窗口尺寸会影响页面布局。桌面端和移动端的元素定位表达式可能不一样,--start-maximized能减少这类麻烦,但如果你要模拟移动端用户,还得用mobile_emulation参数设置设备指标。Cookie处理要想到。脚本执行过程中如果需要登录,登录态的Cookie要妥善保存和复用。先手动登录一次,然后把driver.get_cookies()序列化保存到本地,下次运行直接add_cookie()加载,能省掉每次都要扫码或输密码的烦恼。日志和截图是调试的王牌。driver.save_screenshot()在关键节点截图,脚本出错时翻图就知道页面当时长什么样。配上driver.page_source保存当前的HTML,可以离线用自己本地的浏览器复现问题。这俩方法对定位疑难杂症帮助极大,我在实际项目里几乎每步都截。7.3 脚本稳定性提升的几个惯用套路在循环里跑批量任务时,建议每个任务包一层异常捕获,不要让单个页面的失败搞挂整个脚本:urls = ["https://example.com", "https://example.org", "https://example.net"] for url in urls: try: driver.get(url) print(driver.title) except Exception as e: print(f"访问失败: {url}, 错误: {e}") continue这样即使某个网站反应慢,脚本也不会中断。再加上每次访问之间做短暂停,避免对目标站造成压力,也避免自己的IP被临时限制。平常跑数据采集脚本,保持这个节奏基本能稳定跑很久。7.4 后续还能扩展的方向基础操作讲完,你后面自然会接触到几个进阶方向:页面元素定位和交互(点击、输入、拖拽)、显式等待与隐式等待的完整组合策略、iframe与弹窗处理、ActionChains模拟键盘鼠标操作、headless模式的性能调优、页面加载性能分析等。还有一个值得提醒的问题:并不是所有网页都适合用Selenium来抓取。如果你的目标站是普通后端渲染的页面,直接用Requests可能更轻快;只有遇到前端渲染、AJAX动态加载,或者需要模拟登录、点击、滚动这类真实用户行为时,Selenium才体现出不可替代的价值。选择什么工具,取决于目标页面的实际结构和你的需求复杂度,这个判断能力比任何具体API都重要。我在实际项目中还发现一个细节,使用Selenium打开网页时,浏览器会默认加载图片、CSS、字体等大量资源,很多其实没必要加载。通过ChromeOptions设置--blink-settings=imagesEnabled=false禁用图片加载,打开页面的速度能快不少。具体操作是:options.add_argument('--blink-settings=imagesEnabled=false')这个方法在批量采集时效果尤其明显,带宽占用下降,整体耗时有肉眼可见的缩短。当然,如果你要截图验证页面视觉样式,那图片是必须加载的,这个需求场景自己权衡。踩过几次坑之后,我的一个深刻体会是,自动化脚本和手工操作之间的差距,大部分都来自“等待”策略的不完善。Selenium提供显式等待、隐式等待、强制sleep三套方案,什么时候用哪个、组合起来怎么用,确实是决定脚本稳定性的关键。这篇文章不可能一次全讲完,但只要你把打开和关闭这个周期管理好,把异常退出兜底做好,一个基础稳定的大坝就立起来了。后面再往上面加盖高楼,也只是时间问题。