做了多年VBA和VBScript开发,我一直觉得“浏览器自动化”是又爱又恨的领域。爱的是它能轻松处理重复的网页操作和数据采集,恨的是传统方案太不争气。用IE对象控制浏览器,新页面一点动态渲染就抓瞎;用XMLHTTP去模拟请求,遇到要执行JS的表单,直接歇菜。后来接触了SeleniumBasic,这套路才彻底走通,它是被封装好的WebDriver库,让我在VBA里像写普通过程一样去控制Chrome、Firefox和IE,不需要部署Java、不需要额外跑服务端,配置起来比官方Selenium省心得多。
写这篇文章,主要是想给还在用老办法和网页斗智斗勇的朋友指条路。SeleniumBasic适合谁?你至少得会写一点VBA或者VBScript,Excel用得顺手,平时要处理网页登录、带条件的批量查询、数据回填、页面验证这类重复劳动。只要满足这些,接下来的内容能直接变成你的生产力工具。
1. 为什么VBA开发者离不开SeleniumBasic
1.1 它解决了什么痛点
以前VBA采集网页,最常用的无非两种方案。一种是IE对象,CreateObject("InternetExplorer.Application"),很简单,但IE本身是一个还在维护的浏览器,面对现在大量用Ajax、Vue、React渲染的页面,能取到的内容非常有限,经常是Dom里面空空如也。另一种是WinHttp/XMLHttp请求,这个只能拿到服务端返回的HTML源码,如果网页是动态生成数据,那只能对着空结果发愣。
SeleniumBasic的方案是模拟一个真实的浏览器操作过程。它封装了WebDriver协议,启动Chrome、Firefox之后,由浏览器自己渲染页面、执行JavaScript,再去DOM中寻找元素、点击、输入、读取内容。这意味着你把网页正常使用时的操作流程录下来,用代码重写一遍,基本都能跑通。现在许多系统登录后还要检查“登录状态”“CSRF Token”,这些都会被浏览器自动处理好,不用在代码里额外维护会话细节。
1.2 它和Python生态的Selenium有什么不同
很多Python开发者用Selenium,格外羡慕它天然适合写爬虫。但站在VBA开发者的视角,不同点其实很关键。其一,不需要安装Python运行环境,不需要在一堆依赖包里挣扎;只要Office环境和SeleniumBasic的DLL都在,就能直接跑。其二,SeleniumBasic在“体系结构”上很轻,安装后自带完整驱动,不像官方Selenium那样要手动去找匹配的浏览器驱动。其三,VBA能直接和Excel表格深度结合。采集到的数据可以马上写进单元格,再从Excel读取参数作为输入,循环处理上百条数据,这个流程在Python里反而要多写不少代码。
当然,Python生态也有一些优势,比如并发处理能力更强、支持无头模式更成熟。但SeleniumBasic在VBA阵营里的匹配度是无可替代的,它把WebDriver的能力,以COM方式开放出来,让VBA/VBScript直接调用,这就是它的核心价值。
1.3 底层原理与适用场景
看一眼架构会更好理解。你的VBA代码,实际是调用SeleniumBasic.dll,这个DLL组件再通过HTTP协议去驱动本地的浏览器驱动进程(比如chromedriver.exe),最后由驱动进程把指令转发给浏览器执行。整个过程不需要外部服务,完全在本地闭环。
场景上,我整理过最常见的几种:
| 场景类型 | 典型需求 | SeleniumBasic能做什么 |
|---|---|---|
| 数据采集 | 从业务系统抓取表格数据 | 登录系统、翻页、读取表格内容并写入Excel |
| 批量操作 | 大量录入/回填表单 | 循环执行填写、点击、提交、验证结果 |
| 自动化测试 | 检查系统功能是否正常 | 模拟用户操作,检查流程是否成功 |
| 报表生成 | 从多个系统汇总数据 | 并行/串行打开页面,提取数据后汇总计算 |
| 办公辅助 | 从网页复制数据到Excel | 替代复制粘贴,减少人为失误 |
2. SeleniumBasic的安装与初始化
2.1 安装文件和DLL注册
SeleniumBasic的安装包通常是一个压缩包,解压后你会看到几个关键文件,包括SeleniumBasic.dll、SeleniumBasic.x64.dll以及不同浏览器对应的驱动exe,比如chromedriver.exe、geckodriver.exe、IEDriverServer.exe等。建议将这些文件放在一个固定目录,比如C:\SeleniumBasic,方便后续维护。
注册DLL这一步常被忽略。有些朋友安装后直接在VBA里引用,发现“找不到Selenium Type Library”,基本就是没注册。注册方法:
- 以管理员身份打开命令提示符。
- 进入文件所在目录:
cd C:\SeleniumBasic - 执行注册命令:
regsvr32 SeleniumBasic.dll
如果系统提示注册成功,再去VBA的“工具 → 引用”中就能看到SeleniumBasic的相关项目。如果是64位Office且需要64位环境,还要另外注册SeleniumBasic.x64.dll。这里有个常见坑,Office本身是32位的,哪怕Windows是64位,也只能注册32位版本,选错文件就会出现“模块找不到”或加载失败的报错。
2.2 浏览器的安装与驱动匹配
SeleniumBasic不是只要装上DLL就完事,它还需要一个真实浏览器,以及对应版本的驱动文件。比如用Chrome,就要求分辨出你的Chrome大版本,再换成匹配的chromedriver.exe。SeleniumBasic的早期版本是捆绑一批常用版本的驱动,如果浏览器更新到较新的大版本,原有驱动就无法启动,启动时直接报错,提示“The driver executable does not exist”或者sessionnotcreated。所以养成习惯,安装新版本后先确认驱动是否兼容,不兼容就去更新SeleniumBasic或者单独把新版本驱动的exe文件替换进去。
2.3 VBA最小化启动示例
在VBA中启动Chrome,代码量很少。先保证通过“工具 → 引用”勾选了SeleniumBasic对象库,然后新建模块写这段代码:
Sub DemoLaunch() Dim driver As New Selenium.ChromeDriver driver.Get "https://example.com" MsgBox "页面标题是:" & driver.Title driver.Quit End Sub运行后,会看到一个新Chrome窗口打开example.com,弹窗显示页面标题,最后自动关闭。这就算环境跑通了。
VBScript里的写法也几乎一样:
Set drv = CreateObject("Selenium.ChromeDriver") drv.Get "https://example.com" MsgBox "页面标题是:" & drv.Title drv.Quit是不是感觉没什么门槛?最难的往往是环境问题,一旦环境OK,后面自然就顺起来。
2.4 自动化的前置检查
浏览器被自动化驱动后,会在窗口顶部显示“Chrome正受到自动测试软件的控制”。这是正常现象,不是报错。但有一些页面检测到自动化环境后会人为限制互动,比如要求验证码。我的建议是:遇到验证码不要硬闯,停在当前状态,让人工手动完成一次,再让代码继续往下跑。市面上流传的自动绕过方案不仅不稳定,还可能违反平台规则,得不偿失。
3. 核心API精讲,从定位元素到表单操作
3.1 定位元素的几种方式
SeleniumBasic的核心操作就是“找元素”。可以在WebDriver或某个WebElement上调用查找方法。常见方法有:
driver.FindElementById("username") driver.FindElementByName("password") driver.FindElementByClassName("btn-primary") driver.FindElementByTagName("button") driver.FindElementByCss(".nav > li > a") driver.FindElementByXPath("//div[@id='main']/table/tbody/tr[2]/td[1]")如果页面有多个满足条件的元素,使用FindElementsBy...方法,返回的是一个WebElements集合,可以遍历。还有一个需要注意的地方:SeleniumBasic的返回对象是Selenium.WebElement,但旧版本中有部分查找方法要写成FindElementByXPath这种命名,如果你使用的版本较老,方法名可能有些差异,写代码前先看一眼对象浏览器(F2)里的成员列表。
选哪个定位方式?我个人的经验:
- 有id优先用id,id是页面中最稳定的属性。
- 没有id,有name或者class也可以,但要留意class中是否包含动态生成的空格或序号。
- XPath最强大,适合复杂结构,但写不好容易脆弱,反而不如多写几步。
例如CssSelector在SeleniumBasic里也支持,但如果你对CSS选择器语法不熟,直接用XPath更直白。
3.2 表单填写与点击
让我写一个有参考价值的例子。假设要登录一个业务系统,这个系统需要输入用户名、密码,再点击登录按钮,最后等待跳转:
Sub LoginSystem(driver As Selenium.WebDriver) driver.FindElementById("username").SendKeys "admin" driver.FindElementById("password").SendKeys "123456" driver.FindElementById("loginButton").Click End Sub就这么简单。但有些细节要说明一下:
SendKeys是给输入框发送键盘输入,能模拟用户真实打字。- 在输入前最好先调用
.Clear清空,防止输入框里已有默认值。 - 如果输入框是密码框,SendKeys同样适用。
- 下拉框如果用的是原生
select标签,可以尝试调用.Select "选项文本";但很多现代页面用div模拟下拉,那就需要先点击下拉框,再点击弹出的选项元素。
比如处理自定义下拉:
driver.FindElementById("citySelect").Click Application.Wait Now + TimeSerial(0, 0, 1) driver.FindElementByXPath("//li[@data-value='beijing']").Click这个思路在SeleniumBasic里能应对多数“假下拉框”。
3.3 多窗口、IFrame和弹窗
页面里出现iframe时,直接在原始DOM里寻找元素注定找不到。要先“切进去”。SeleniumBasic的做法是:
Dim frame As Selenium.WebElement Set frame = driver.FindElementByTagName("iframe") driver.SwitchToFrame frame ' 在iframe内部寻找元素 driver.FindElementById("innerInput").SendKeys "你好" ' 切回主文档 driver.SwitchToDefaultContent如果是点击链接后弹出新窗口,想在新窗口里继续操作:
driver.SwitchToNextWindow这个方法会切换到下一个窗口,再用driver.Title或driver.Url确认是否切换成功。多窗口切换时,为了保险,可以提前保存当前窗口句柄,切完再切回:
Dim mainHandle As String mainHandle = driver.WindowHandles(0) driver.SwitchToNextWindow ' 在新窗口里做完事后 driver.SwitchToWindow mainHandle弹窗(JavaScript的Alert)属于另一种特殊处理。SeleniumBasic有driver.Alert对象,可以做Accept、Dismiss、SendKeys操作。一个常见的坑是,弹窗出现后如果再调用别的查找方法,程序会一直卡住,正确的姿势是发现弹窗后马上处理:
driver.Alert.Accept3.4 显式等待与动态加载
现代网页几乎没有秒开的,登录、查询、翻页都有加载过程。如果页面还没渲染完成就去找元素,最常见的报错是“未找到指定元素”。这时候就得靠等待机制。SeleniumBasic没有默认的“隐式等待”选项,所以我一般是写一个自定义等待函数:
Public Function WaitElement(ByVal driver As Selenium.WebDriver, _ ByVal selectorType As String, _ ByVal selector As String, _ ByVal timeoutSeconds As Integer) As Selenium.WebElement Dim i As Integer On Error Resume Next For i = 1 To timeoutSeconds * 2 Set WaitElement = Nothing Select Case LCase(selectorType) Case "id" Set WaitElement = driver.FindElementById(selector) Case "class" Set WaitElement = driver.FindElementByClassName(selector) Case "xpath" Set WaitElement = driver.FindElementByXPath(selector) Case Else Set WaitElement = driver.FindElementByCss(selector) End Select If Not WaitElement Is Nothing Then Exit Function Application.Wait Now + TimeSerial(0, 0, 0.5) Next i End Function这里的逻辑其实就是“轮询”:每半秒尝试查找一次,超过指定秒数就放弃。这种轮询方式比直接Sleep一大坨要稳定得多,因为加载时间不稳定时,等待1秒不够,等待5秒可能又太多。轮询能刚好等到元素出现再继续。
用起来也简单:
Dim obj As Selenium.WebElement Set obj = WaitElement(driver, "class", "result-table", 10) If obj Is Nothing Then MsgBox "超时未找到结果表格" Else ' 执行后续操作 End If4. 实战:用SeleniumBasic写一个批量数据采集工具
4.1 任务拆解与准备工作
这里用一个模拟场景说明完整的采集流程。假设我们要在某网站查询一批订单号,页面需要登录,登录后输入订单号查询,页面返回结果后我们读取订单当前状态。
先拆解步骤:
- 启动Chrome,打开登录页。
- 输入用户名密码登录。
- 等待登录成功,进入查询页。
- 循环读取Excel中待查询的订单号。
- 在查询输入框逐个输入订单号,点击查询。
- 等待查询结果渲染完成。
- 提取订单状态,写回Excel。
这是非常经典的数据采集流程,几乎每家公司都可能有类似需求。
4.2 核心采集代码走读
先定义一个主过程,参数是Sheet中从第2行开始到有数据的最后一行:
Sub BatchQueryOrders() Dim driver As New Selenium.ChromeDriver Dim ws As Worksheet Dim lastRow As Long Dim i As Long Dim orderId As String Dim statusEl As Selenium.WebElement Set ws = ThisWorkbook.Sheets("订单") lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row driver.Get "https://demo-system.com/login" ' 登录 driver.FindElementById("username").SendKeys "tester" driver.FindElementById("password").SendKeys "test123" driver.FindElementById("loginBtn").Click ' 等待登录后跳转 Application.Wait Now + TimeSerial(0, 0, 3) For i = 2 To lastRow orderId = ws.Cells(i, 1).Value If orderId <> "" Then ' 回到查询页(这里假设登录后已在查询页,也可用driver.Get重新打开) driver.FindElementById("searchKey").Clear driver.FindElementById("searchKey").SendKeys orderId driver.FindElementById("searchBtn").Click ' 等待结果出现 Set statusEl = WaitElement(driver, "class", "status-text", 8) If Not statusEl Is Nothing Then ws.Cells(i, 2).Value = statusEl.Text Else ws.Cells(i, 2).Value = "未查到结果" End If ' 每次查询间隔一点时间,避免请求过密 Application.Wait Now + TimeSerial(0, 0, 1) End If Next i driver.Quit MsgBox "批量查询完成" End Sub这里有个很关键的点:查询按钮点击后,结果状态可能不是立即渲染的,我通过自定义的WaitElement去等待.status-text这个元素出现,比固定Sleep更稳妥。
4.3 翻页与大量数据的拉取
如果查询结果不止一页,需要在循环里不断点击“下一页”按钮,直到没有下一页。我常用的写法:
Do ' 采集当前页的数据 Call ExtractCurrentPage(driver, ws, rowIndex) ' 查找“下一页”按钮 Set nextBtn = Nothing On Error Resume Next Set nextBtn = driver.FindElementByXPath("//a[contains(@class,'next')]") On Error GoTo 0 If nextBtn Is Nothing Then Exit Do nextBtn.Click Application.Wait Now + TimeSerial(0, 0, 2) Loop其中ExtractCurrentPage里再写一个循环,遍历table底下的行,把每个单元格的文本读出来。
4.4 把临时脚本变成运维工具
脚本一旦频繁使用,就要考虑日志和异常处理。否则跑到一半卡住,去任务管理器里结束chrome都来不及。
我会在循环外加重试机制:
Dim retryCount As Integer retryCount = 0 Do On Error Resume Next driver.FindElementById("searchBtn").Click If Err.Number = 0 Then Exit Do Err.Clear retryCount = retryCount + 1 Application.Wait Now + TimeSerial(0, 0, 2) Loop While retryCount < 3还需要把每行数据采集过程用On Error GoTo包住,如果某一行失败,记录到日志列,然后继续下一行。千万不要让整个循环因为一个数据异常而崩溃。
5. 常见报错与排查实录
5.1 高频错误速查表
| 报错现象 | 可能原因 | 解决思路 |
|---|---|---|
| 找不到元素 “element not found” | 页面未加载完,或选择器不对 | 用WaitElement轮询,或调整XPath/CSS选择器 |
| SessionNotCreatedException | Chrome版本与驱动不匹配 | 下载对应版本的chromedriver |
| 启动时弹出“系统找不到指定的程序” | 驱动exe被删,或路径不对 | 检查SeleniumBasic安装目录里的driver是否存在 |
| 注册DLL时报“模块无法注册” | 32位/64位架构不匹配 | 确认Office位数,换对应DLL注册 |
| 运行时错误“未设置对象变量或With block变量” | 查找语句没找到元素 | 先判断元素是否为空,再调用后续方法 |
| 屏幕上的元素明明存在却click无效 | 页面存在遮罩层或元素不可见 | 先点击遮罩关闭,或改用JS方式滚动到可见位置 |
| 浏览器不断弹出广告/新窗口 | 网站机制 | 用driver.WindowHandles过滤并关闭非目标窗口 |
5.2 定位不到的顽固元素
这是SeleniumBasic使用中最普遍的问题。元素在浏览器里肉眼可见,但代码就是找不到。原因通常有这么几种:
- 元素在iframe里。这个前面提过,需要先SwitchToFrame。
- 元素是动态生成的,页面里暂时不存在,需要等待。
- 元素隐藏属性不是“display:none”,而是用CSS类控制,可视但不可点击,这时可以先通过
.ClearText? 不对,正确做法是滚动到元素位置或使用JavaScript点击。SeleniumBasic提供了driver.ExecuteScript方法,可以执行一段JS:
driver.ExecuteScript "arguments[0].click();", element这种基于JS的点击方式能绕过一些元素被遮挡、被其他层盖住导致的Click无效问题。
- XPath写得太死,比如
//td[2],页面结构偶有变化,建议用更长但更稳定的路径,或通过文字内容定位://div[contains(text(), '订单')]。
5.3 页面加载耗时太长
如果一个页面本身加载很慢,但只需操作一部分内容,可以考虑先设置页面加载策略,让浏览器不要等待整个页面所有资源都加载完。在SeleniumBasic中可能没有直接暴露该参数,但可以通过driver.capabilities? 这个比较复杂。更实际的做法是:启动后立刻用WaitElement去等关键元素,不需要等页面完全就绪。如果确实要操作一个尚未渲染完的按钮,就只能在按钮出现后再等一小段时间。
5.4 自动化操作识别的防御
部分网站会检测浏览器是否自动化控制,进而限制操作。我不会建议规避人工验证,但有一些现实经验是:不要用太快的操作节奏,比如连续点击之间加随机间隔,模拟真人盯屏幕的时间;不要在并发请求上做得太夸张。这些做法本质上是降低对目标系统的压力,也更容易把事情做长久。验证码往往在规定次数后出现,遇到就让人员介入,手动完成后继续。
6. 必须处理好的收尾、清理和代码组织
6.1 释放浏览器进程与内存管理
如果代码结束时只是关掉了浏览器窗口,没有调用driver.Quit,那么chromedriver.exe进程很可能残留在任务管理器里。残留进程多了,之后的自动化就越来越不稳定。正确做法是:
driver.Quit Set driver = Nothing如果因为异常跳过了这行,可以在过程开头声明一个布尔变量并配合On Error GoTo,确保过程结束后一定会执行清理。万一脏进程还是出现了,在任务管理器里结束chromedriver.exe和chrome.exe是最后的兜底。
6.2 封装成通用类模块
项目写多了就会发现,大量代码是重复的。公共等待函数、登录逻辑、Excel交互,都可以抽到一个标准模块。进一步可以封装一个类,比如CreateDriver、Login、SearchOrder、ReadStatus。在VBA里这算高级玩法,但收益很直观,一个公司的不同部门可以共享同一套类,后续维护只需要改类内部的方法,而不是改几十个脚本。
给别人维护时,类的优势尤其明显。写一个简单的类,外部使用变成:
Dim bizTools As New OrderTool bizTools.InitializeDriver "chrome" bizTools.Login "tester", "test123" bizTools.QueryOrders ws, "A", "B" bizTools.Cleanup这里OrderTool就是你的类模块,里面封装了前面提到的所有底层层逻辑。这样主程序看起来顺畅清晰,也不会因为页面的偶尔改版影响整体。
6.3 从VBA迁移到VBScript和批处理
有些环境不想要Excel,只想用VBScript实现自动化。其实SeleniumBasic完全支持。VBScript的好处是双击就能跑,无需打开Excel。可以写成.ps脚本由计划任务调用,或者直接把它挂到Windows的定时任务里。这在日常运维里特别实用,比如每天早上自动登录内部系统、抓取报表并发送至指定目录。
Set drv = CreateObject("Selenium.ChromeDriver") drv.Get "https://demo-system.com/report" ' ... 操作逻辑 drv.Quit注意VBScript对类型不那么敏感,错误处理靠On Error Resume Next,如果之前VBA代码里用了Application.Wait,VBScript里没有Workbook对象,需要改用WScript.Sleep 2000。
6.4 版本维护与项目升级
SeleniumBasic的维护频率并不高,但如果你长期依赖它,最好固定一套经过验证的浏览器版本,不要总把浏览器升级到最新的大版本,却忘了检查SeleniumBasic是否跟进。内部工具的稳定性,比追新重要得多。若实验室或团队有条件,建议把驱动文件、安装包、注册脚本放到共享目录里,新同事接手时可以一键完成环境搭建。
最后的一点个人经验
接触SeleniumBasic这几年,最大的体会是,它能省下大量纯手工操作的精力,但不是“装完就能躺赢”的工具。浏览器版本要盯着,页面结构细微变化需要同步修改,等待逻辑更是要反复打磨。把核心流程封装成类模块之后,日常维护会轻松很多。我现在越来越倾向于让Python去处理更复杂的定时任务和数据处理,但凡是Excel和VBA周围的流程,SeleniumBasic依然是最省心的选择。如果你正准备尝试,建议从一个小场景开始,比如自动登录系统,跑通环境,再一步步加上查询、翻页、采集、写回Excel的流程。只要走通一条完整链路,后面扩展就是复制、剪贴、加参数的事。