PowerShell 技能连载 - Browser Use 浏览器自动化

适用于 PowerShell 7.0 及以上版本

背景

2026 年,浏览器自动化技术已经从简单的网页测试工具发展为 AI Agent 执行任务的核心能力。以 Browser Use 为代表的框架让 AI 模型能够直接操控浏览器完成复杂任务,例如自动填表、数据采集、跨站点流程编排等。Playwright 和 Selenium 作为两大主流浏览器自动化引擎,都提供了成熟的 API,而 PowerShell 凭借其强大的对象管道和 .NET 生态集成能力,成为串联这些工具的绝佳胶水语言。

在实际运维和数据处理场景中,我们经常需要从没有 API 的内部系统中提取数据、定时提交报表、或批量执行重复的网页操作。传统做法依赖手动操作或录制宏脚本,维护成本高且容易出错。PowerShell 结合 Playwright 可以编写声明式的自动化脚本,配合 AI 模型甚至能实现”说一句话,浏览器自动完成操作”的智能体验。

本文将分三个层次介绍 PowerShell 浏览器自动化:从 Playwright 基础操作,到数据采集与表单自动化,再到 AI 驱动的智能浏览器操作,帮助读者逐步掌握这项实用技能。

Playwright 自动化基础

Playwright 是微软开发的跨浏览器自动化框架,原生支持 Chromium、Firefox 和 WebKit。PowerShell 可以通过 .NET 互操作直接调用 Playwright 的 NuGet 包,实现从浏览器启动到元素操作的全流程控制。

以下代码演示了 Playwright 的安装、浏览器启动、页面导航、等待元素加载、点击按钮、读取文本等基本操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 安装 Playwright NuGet 包和浏览器二进制文件
Install-Module -Name Microsoft.Playwright -Scope CurrentUser -Force
dotnet tool install --global Microsoft.Playwright.CLI
playwright install chromium

# 导入模块并启动浏览器
using module Microsoft.Playwright

$playwright = [Microsoft.Playwright.Program]::CreatePlaywright()
$browser = $playwright.Chromium.LaunchAsync(@{ headless = $true }).GetAwaiter().GetResult()

# 创建新页面并导航到目标网站
$page = $browser.NewPageAsync().GetAwaiter().GetResult()
$page.GotoAsync("https://example.com").GetAwaiter().GetResult()

# 等待页面标题加载并获取标题文本
$page.WaitForSelectorAsync("h1").GetAwaiter().GetResult() | Out-Null
$title = $page.TextContentAsync("h1").GetAwaiter().GetResult()
Write-Host "页面标题: $title"

# 查找链接并获取所有链接地址
$links = $page.QuerySelectorAllAsync("a").GetAwaiter().GetResult()
foreach ($link in $links) {
$href = $link.GetAttributeAsync("href").GetAwaiter().GetResult()
$text = $link.TextContentAsync().GetAwaiter().GetResult()
Write-Host " 链接: $text -> $href"
}

# 关闭浏览器释放资源
$browser.CloseAsync().GetAwaiter().GetResult()
$playwright.Dispose()

执行结果示例:

1
2
页面标题: Example Domain
链接: More information... -> https://www.iana.org/domains/example

在实际项目中,推荐使用 try-finally 块确保浏览器资源被正确释放,避免残留进程占用系统内存。

数据采集与表单自动化

网页数据提取和表单自动填写是浏览器自动化最常见的应用场景。PowerShell 可以将采集到的结构化数据直接转化为对象,利用管道进行筛选和导出,这是其他脚本语言难以比拟的优势。

下面的脚本展示了从网页表格提取数据、自动填写搜索表单、以及将页面保存为截图和 PDF 的完整流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using module Microsoft.Playwright

$playwright = [Microsoft.Playwright.Program]::CreatePlaywright()
$browser = $playwright.Chromium.LaunchAsync(@{ headless = $true }).GetAwaiter().GetResult()
$page = $browser.NewPageAsync().GetAwaiter().GetResult()

# 导航到包含表格的页面
$page.GotoAsync("https://example.com/tables").GetAwaiter().GetResult()
$page.WaitForSelectorAsync("table").GetAwaiter().GetResult() | Out-Null

# 提取表格数据并转化为 PowerShell 对象
$rows = $page.QuerySelectorAllAsync("table tbody tr").GetAwaiter().GetResult()
$tableData = foreach ($row in $rows) {
$cells = $row.QuerySelectorAllAsync("td").GetAwaiter().GetResult()
[PSCustomObject]@{
Name = $cells[0].TextContentAsync().GetAwaiter().GetResult().Trim()
Value = $cells[1].TextContentAsync().GetAwaiter().GetResult().Trim()
Status = $cells[2].TextContentAsync().GetAwaiter().GetResult().Trim()
}
}

# 筛选状态为 Active 的记录并导出为 CSV
$tableData | Where-Object { $_.Status -eq "Active" } | Export-Csv -Path "active_items.csv" -NoTypeInformation -Encoding utf8
Write-Host "已导出 $($tableData.Count) 条记录"

# 自动填写搜索表单
$page.GotoAsync("https://example.com/search").GetAwaiter().GetResult()
$page.FillAsync("#search-input", "PowerShell automation").GetAwaiter().GetResult()
$page.SelectOptionAsync("#category", "technology").GetAwaiter().GetResult()
$page.ClickAsync("#search-button").GetAwaiter().GetResult()

# 等待搜索结果加载
$page.WaitForSelectorAsync(".result-item").GetAwaiter().GetResult() | Out-Null

# 截取搜索结果页面的屏幕截图
$page.ScreenshotAsync(@{ path = "search-results.png"; fullPage = $true }).GetAwaiter().GetResult() | Out-Null
Write-Host "截图已保存到 search-results.png"

# 将当前页面导出为 PDF
$page.PdfAsync(@{ path = "search-results.pdf"; format = "A4" }).GetAwaiter().GetResult() | Out-Null
Write-Host "PDF 已保存到 search-results.pdf"

$browser.CloseAsync().GetAwaiter().GetResult()
$playwright.Dispose()

执行结果示例:

1
2
3
已导出 12 条记录
截图已保存到 search-results.png
PDF 已保存到 search-results.pdf

通过 PSCustomObject 将网页数据结构化后,可以无缝使用 Where-ObjectSort-ObjectExport-Csv 等 PowerShell 原生命令进行后续处理,实现从采集到入库的一体化流程。

AI 驱动的浏览器操作

2026 年最具变革性的趋势是将大语言模型(LLM)与浏览器自动化结合,让 AI 理解页面语义后自动决定操作步骤。PowerShell 在这个场景中扮演调度器的角色:调用 LLM API 分析页面结构,解析返回的指令,再驱动浏览器执行具体动作。

以下脚本展示了如何用 PowerShell 调用本地 Ollama 模型分析网页元素,实现 AI 驱动的智能浏览器操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
using module Microsoft.Playwright

# 配置 Ollama API 端点
$ollamaEndpoint = "http://localhost:11434/api/generate"
$modelName = "qwen2.5:7b"

function Invoke-OllamaChat {
param([string]$Prompt)
$body = @{
model = $modelName
prompt = $Prompt
stream = $false
} | ConvertTo-Json -Depth 5

$response = Invoke-RestMethod -Uri $ollamaEndpoint -Method Post -Body $body -ContentType "application/json"
return $response.response
}

# 启动浏览器并导航到目标页面
$playwright = [Microsoft.Playwright.Program]::CreatePlaywright()
$browser = $playwright.Chromium.LaunchAsync(@{ headless = $false }).GetAwaiter().GetResult()
$page = $browser.NewPageAsync().GetAwaiter().GetResult()
$page.GotoAsync("https://news.example.com").GetAwaiter().GetResult()

# 提取页面可交互元素的摘要信息
$elements = $page.QuerySelectorAllAsync("a, button, input, select").GetAwaiter().GetResult()
$elementSummary = foreach ($el in $elements[0..19]) {
$tag = $el.EvaluateHandleAsync("e => e.tagName").GetAwaiter().GetResult().JsonValue
$text = $el.TextContentAsync().GetAwaiter().GetResult()
$role = $el.GetAttributeAsync("role").GetAwaiter().GetResult()
" <$tag> text='$($text.Trim().Substring(0, [Math]::Min(50, $text.Trim().Length)))' role='$role'"
}
$summaryText = $elementSummary -join "`n"

# 让 AI 分析页面并推荐操作
$aiPrompt = @"
你是一个浏览器自动化助手。以下是页面上的可交互元素:

$summaryText

用户任务:找到今天浏览量最高的科技新闻标题。
请告诉我应该点击哪个元素(用序号表示),以及后续操作建议。
只返回简洁的操作指令,不要解释。
"@

$aiResponse = Invoke-OllamaChat -Prompt $aiPrompt
Write-Host "AI 建议: $aiResponse"

# 根据 AI 建议提取操作索引并执行点击
if ($aiResponse -match "点击.*?(\d+)") {
$index = [int]$Matches[1]
if ($index -lt $elements.Count) {
$elements[$index].ClickAsync().GetAwaiter().GetResult() | Out-Null
Write-Host "已执行 AI 建议的点击操作(元素 #$index)"
}
}

# 等待新页面加载后提取标题
Start-Sleep -Seconds 2
$articleTitle = $page.TitleAsync().GetAwaiter().GetResult()
Write-Host "当前页面标题: $articleTitle"

$browser.CloseAsync().GetAwaiter().GetResult()
$playwright.Dispose()

执行结果示例:

1
2
3
AI 建议: 点击元素 #3,这是"科技"分类链接。进入后点击第一个新闻标题即可。
已执行 AI 建议的点击操作(元素 #3)
当前页面标题: 最新科技新闻 - News Example

这种方式将 LLM 的语义理解能力与 Playwright 的精确操作能力结合,实现了”自然语言到浏览器操作”的闭环。在生产环境中,建议加入操作确认机制和异常回退逻辑,确保 AI 的操作可预测且可追溯。

注意事项

  1. 资源管理:Playwright 的浏览器实例是重量级资源,务必使用 try-finally 块确保 CloseAsyncDispose 被调用,避免残留进程消耗系统资源。

  2. 异步处理:Playwright 的 .NET API 大量使用 async/await 模式,在 PowerShell 中需要通过 .GetAwaiter().GetResult() 同步等待。避免在循环中频繁调用,可改用批量操作减少开销。

  3. 等待策略:页面加载时间受网络状况影响较大,推荐使用 WaitForSelectorAsync 替代固定的 Start-Sleep,既保证元素可用,又避免不必要的等待。

  4. 无头模式选择:开发调试时使用 headless = $false 以便观察浏览器行为,生产环境使用 headless = $true 提升性能。在 Linux 服务器上运行时需要安装额外的系统依赖库。

  5. AI 操作安全性:让 LLM 直接控制浏览器操作具有不确定性,务必对 AI 返回的指令进行校验(如索引范围检查、URL 白名单过滤),防止误操作导致数据丢失或安全风险。

  6. 反爬虫应对:部分网站会检测自动化工具并限制访问。可通过设置 User-Agent、使用浏览器上下文隔离、控制请求频率等方式降低被识别的概率,同时确保遵守目标网站的服务条款。

PowerShell 技能连载 - Web 数据采集

适用于 PowerShell 5.1 及以上版本

运维和开发中经常需要从网页获取数据——监控服务状态页、采集系统指标、下载最新版本的工具、从内部管理平台提取报表。PowerShell 内置的 Invoke-WebRequest 可以发送 HTTP 请求并解析 HTML,结合正则表达式和 HTML 解析能力,可以高效完成大部分数据采集任务。

本文将讲解 HTTP 请求、HTML 解析、表单提交、会话管理,以及浏览器自动化技术。

HTTP 请求基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 基本 GET 请求
$response = Invoke-WebRequest -Uri "https://httpbin.org/get" -UseBasicParsing
Write-Host "状态码:$($response.StatusCode)"
Write-Host "内容长度:$($response.Content.Length) bytes"

# 解析 JSON 响应
$json = $response.Content | ConvertFrom-Json
Write-Host "来源 IP:$($json.origin)"
Write-Host "User-Agent:$($json.headers.'User-Agent')"

# 自定义请求头
$headers = @{
"User-Agent" = "PowerShell/7.4 (OpsBot)"
"Accept" = "application/json"
"X-Custom-Header" = "monitoring"
}

$response = Invoke-WebRequest -Uri "https://httpbin.org/headers" `
-Headers $headers -UseBasicParsing
$json = $response.Content | ConvertFrom-Json
$json.headers | Format-Table -AutoSize

# POST 请求(JSON 数据)
$body = @{
hostname = $env:COMPUTERNAME
status = "healthy"
uptime = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime.ToString("o")
} | ConvertTo-Json

$response = Invoke-WebRequest -Uri "https://httpbin.org/post" `
-Method Post `
-ContentType "application/json; charset=utf-8" `
-Body ([System.Text.Encoding]::UTF8.GetBytes($body)) `
-UseBasicParsing

Write-Host "POST 响应:$($response.StatusCode)"

执行结果示例:

1
2
3
4
5
6
7
8
9
10
状态码:200
内容长度:356 bytes
来源 IP:203.0.113.42
User-Agent:PowerShell/7.4 (OpsBot)

Host User-Agent X-Custom-Header
---- ---------- ---------------
httpbin.org PowerShell/7.4 (OpsBot) monitoring

POST 响应:200

HTML 解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 解析 HTML 页面提取链接
function Get-WebLinks {
param(
[Parameter(Mandatory)]
[string]$Url,
[string]$Pattern = ".*"
)

$response = Invoke-WebRequest -Uri $Url -UseBasicParsing

# 从 HTML 中提取所有链接
$links = [regex]::Matches($response.Content, 'href=["''](https?://[^"'']+)["'']') |
ForEach-Object { $_.Groups[1].Value } |
Where-Object { $_ -match $Pattern } |
Sort-Object -Unique

return $links
}

# 提取页面中的所有下载链接
$downloadLinks = Get-WebLinks -Url "https://example.com/downloads" -Pattern "\.msi$|\.zip$|\.exe$"
$downloadLinks | ForEach-Object { Write-Host " $_" -ForegroundColor Cyan }

# 使用 HTML 解析提取表格数据
function Get-HtmlTable {
param(
[Parameter(Mandatory)]
[string]$Url,
[int]$TableIndex = 0
)

$response = Invoke-WebRequest -Uri $Url -UseBasicParsing
$html = $response.Content

# 提取表格行
$rows = [regex]::Matches($html, '<tr[^>]*>(.*?)</tr>', [System.Text.RegularExpressions.RegexOptions]::Singleline)

$tableData = foreach ($row in $rows) {
$cells = [regex]::Matches($row.Groups[1].Value, '<t[dh][^>]*>(.*?)</t[dh]>', [System.Text.RegularExpressions.RegexOptions]::Singleline)
$cellValues = $cells | ForEach-Object {
# 去除 HTML 标签
$val = $_.Groups[1].Value -replace '<[^>]+>', ''
$val.Trim()
}
if ($cellValues) {
$cellValues -join '|'
}
}

return $tableData
}

$tableData = Get-HtmlTable -Url "https://example.com/status"
$tableData | ForEach-Object { Write-Host $_ }

执行结果示例:

1
2
3
4
5
6
7
  https://example.com/downloads/tool-v3.2.1.msi
https://example.com/downloads/tool-v3.2.1.zip

服务名|状态|响应时间
Auth-Service|Running|45ms
API-Gateway|Running|23ms
Worker-01|Stopped|N/A

会话管理与认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 使用 WebRequestSession 维持会话
$session = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
$session.UserAgent = "PowerShell-OpsBot/1.0"

# 模拟登录(第一步:获取登录页面)
$loginPage = Invoke-WebRequest -Uri "https://example.com/login" `
-WebSession $session -UseBasicParsing

# 提取 CSRF Token
$csrfToken = if ($loginPage.Content -match 'name="csrf_token"\s+value="([^"]+)"') {
$Matches[1]
} else {
""
}

# 第二步:提交登录表单
$loginBody = @{
username = "admin"
password = "P@ssw0rd123"
csrf_token = $csrfToken
}

$loginResponse = Invoke-WebRequest -Uri "https://example.com/login" `
-Method Post `
-Body $loginBody `
-WebSession $session `
-UseBasicParsing

Write-Host "登录状态:$($loginResponse.StatusCode)"

# 第三步:使用会话访问受保护页面
$dashboard = Invoke-WebRequest -Uri "https://example.com/dashboard" `
-WebSession $session -UseBasicParsing

Write-Host "仪表板内容长度:$($dashboard.Content.Length) bytes"

# Bearer Token 认证
$token = $env:API_TOKEN
$protectedData = Invoke-RestMethod -Uri "https://api.example.com/v1/status" `
-Headers @{ "Authorization" = "Bearer $token" }

$protectedData | Format-Table -AutoSize

执行结果示例:

1
2
3
4
5
6
7
8
登录状态:200
仪表板内容长度:45230 bytes

Service Status Uptime
------- ------ ------
auth-service Running 45d 12h
api-gateway Running 45d 12h
worker-01 Running 30d 8h

监控网页状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
function Test-WebPageHealth {
<#
.SYNOPSIS
监控网页可用性和关键内容
#>
param(
[Parameter(Mandatory)]
[string[]]$Urls,

[string]$ExpectedContent = "",

[int]$TimeoutSeconds = 10
)

$results = foreach ($url in $Urls) {
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()

try {
$response = Invoke-WebRequest -Uri $url `
-TimeoutSec $TimeoutSeconds `
-UseBasicParsing `
-ErrorAction Stop

$stopwatch.Stop()

$contentOk = if ($ExpectedContent) {
$response.Content -match [regex]::Escape($ExpectedContent)
} else {
$true
}

[PSCustomObject]@{
Url = $url
Status = $response.StatusCode
LatencyMs = $stopwatch.ElapsedMilliseconds
ContentMatch = $contentOk
ContentLen = $response.Content.Length
Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
}
} catch {
$stopwatch.Stop()
[PSCustomObject]@{
Url = $url
Status = "Error"
LatencyMs = $stopwatch.ElapsedMilliseconds
ContentMatch = $false
ContentLen = 0
Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
}
}
}

$results | Format-Table -AutoSize

$failed = $results | Where-Object { $_.Status -ne 200 -or -not $_.ContentMatch }
if ($failed) {
Write-Host "`n告警:$($failed.Count) 个页面异常" -ForegroundColor Red
$failed | ForEach-Object { Write-Host " $($_.Url) - Status: $($_.Status)" -ForegroundColor Red }
}
}

# 监控多个服务端点
Test-WebPageHealth -Urls @(
"https://blog.vichamp.com"
"https://api.example.com/health"
"https://portal.example.com"
) -ExpectedContent "OK"

执行结果示例:

1
2
3
4
5
6
7
8
Url                              Status LatencyMs ContentMatch ContentLen Timestamp
--- ------ --------- ------------ ---------- ---------
https://blog.vichamp.com 200 245 True 52301 2025-06-27 09:15:30
https://api.example.com/health 200 128 True 256 2025-06-27 09:15:31
https://portal.example.com Error 0 False 0 2025-06-27 09:15:41

告警:1 个页面异常
https://portal.example.com - Status: Error

文件下载管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function Start-FileDownload {
<#
.SYNOPSIS
带进度显示的文件下载
#>
param(
[Parameter(Mandatory)]
[string]$Url,

[string]$OutputPath = (Join-Path $PWD (Split-Path $Url -Leaf)),

[int]$TimeoutSeconds = 300
)

Write-Host "下载:$Url" -ForegroundColor Cyan
Write-Host "保存到:$OutputPath"

try {
$response = Invoke-WebRequest -Uri $Url -OutFile $OutputPath `
-TimeoutSec $TimeoutSeconds -UseBasicParsing -PassThru

$file = Get-Item $OutputPath
$sizeMB = [math]::Round($file.Length / 1MB, 2)
Write-Host "下载完成:$sizeMB MB" -ForegroundColor Green

return $OutputPath
} catch {
Write-Host "下载失败:$($_.Exception.Message)" -ForegroundColor Red
return $null
}
}

# 批量下载
$files = @(
@{ Url = "https://example.com/data/report-june.csv"; Name = "report-june.csv" },
@{ Url = "https://example.com/data/report-may.csv"; Name = "report-may.csv" }
)

$downloadDir = "C:\Downloads\reports"
New-Item $downloadDir -ItemType Directory -Force | Out-Null

foreach ($file in $files) {
$output = Join-Path $downloadDir $file.Name
Start-FileDownload -Url $file.Url -OutputPath $output
}

执行结果示例:

1
2
3
4
5
6
下载:https://example.com/data/report-june.csv
保存到:C:\Downloads\reports\report-june.csv
下载完成:2.35 MB
下载:https://example.com/data/report-may.csv
保存到:C:\Downloads\reports\report-may.csv
下载完成:1.87 MB

注意事项

  1. 遵守 robots.txt:采集前检查目标网站的 robots.txt 和使用条款,尊重爬取规则
  2. 请求频率:添加合理的 Start-Sleep 间隔,避免对目标服务器造成压力
  3. 编码处理:网页编码可能不一致,使用 -UseBasicParsing 并手动处理编码
  4. 证书验证:内网自签名证书环境使用 -SkipCertificateCheck(PowerShell 7+)
  5. 数据敏感:采集到的数据可能包含敏感信息,注意脱敏和存储安全
  6. User-Agent:设置合理的 User-Agent,部分网站会屏蔽默认的 PowerShell UA

PowerShell 技能连载 - 浏览器自动化实战

适用于 PowerShell 7.0 及以上版本

2025 年,AI + 浏览器(Browser Using)成为技术热点,各大模型厂商纷纷推出浏览器操控能力。浏览器自动化不再只是测试工程师的专属工具——数据采集、运维巡检、表单自动填写、可用性监控等场景都离不开它。

PowerShell 作为 Windows 和 macOS 上广泛使用的脚本语言,同样可以胜任浏览器自动化任务。本文将分别介绍 Selenium 和 Playwright 两种方案,从环境搭建到实际应用,帮助你快速上手。

方案一:Selenium

Selenium 是老牌的浏览器自动化框架,生态成熟,社区资源丰富。它的核心思路是通过 WebDriver 协议控制浏览器,PowerShell 可以直接调用其 .NET 绑定。

安装环境

首先安装 Selenium 模块和 Chrome WebDriver。以下函数会自动完成这些步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Install-SeleniumDriver {
param(
[ValidateSet("Chrome", "Firefox", "Edge")]
[string]$Browser = "Chrome"
)

# 安装 Selenium PowerShell 模块
Install-Module -Name Selenium -Scope CurrentUser -Force

# 创建 WebDriver 存放目录
$driverPath = "$env:TEMP\selenium"
if (-not (Test-Path $driverPath)) {
New-Item -Path $driverPath -ItemType Directory | Out-Null
}

Write-Host "$Browser WebDriver 已就绪"
}

执行安装:

1
2
PS> Install-SeleniumDriver
Chrome WebDriver 已就绪

创建浏览器会话

安装完成后,需要一个函数来启动浏览器。我们支持无头模式(Headless),这样在服务器或 CI 环境中也能正常运行。关键参数 --headless=new 使用 Chrome 新版无头模式,兼容性更好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Start-BrowserSession {
param(
[ValidateSet("Chrome", "Firefox", "Edge")]
[string]$Browser = "Chrome",

[switch]$Headless
)

$options = New-Object OpenQA.Selenium.Chrome.ChromeOptions
if ($Headless) {
$options.AddArgument("--headless=new")
}
$options.AddArgument("--disable-gpu")
$options.AddArgument("--no-sandbox")

$driver = New-Object OpenQA.Selenium.Chrome.ChromeDriver($options)
return $driver
}

页面操作封装

每次都手动管理 Driver 的生命周期容易遗漏资源释放。下面封装一个通用的页面操作函数,自动处理会话创建和销毁。你只需传入一个脚本块来定义具体操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function Invoke-WebPageTask {
param(
[Parameter(Mandatory)]
[string]$Url,

[scriptblock]$Action,

[switch]$Headless,

[int]$TimeoutSeconds = 30
)

$driver = Start-BrowserSession -Headless:$Headless
try {
# 设置隐式等待,避免元素未加载就操作
$driver.Manage().Timeouts().ImplicitWait = [TimeSpan]::FromSeconds($TimeoutSeconds)
$driver.Navigate().GoToUrl($Url)
Write-Host "已打开: $Url"

$result = & $Action -Driver $driver
return $result
}
finally {
# 务必释放浏览器进程
$driver.Quit()
}
}

使用示例——抓取页面标题和所有 HTTPS 链接:

1
2
3
4
5
6
7
8
9
10
11
12
$links = Invoke-WebPageTask -Url "https://learn.microsoft.com/powershell" -Headless -Action {
param($Driver)

$title = $Driver.Title
$links = $Driver.FindElements([OpenQA.Selenium.By]::TagName("a")) |
Where-Object { $_.GetAttribute("href") -match "^https://" } |
Select-Object @{ N = "Text"; E = { $_.Text } },
@{ N = "Href"; E = { $_.GetAttribute("href") } }

Write-Host "页面: $title, 链接数: $($links.Count)"
return $links
}

执行结果示例:

1
2
3
4
5
6
7
已打开: https://learn.microsoft.com/powershell
页面: PowerShell | Microsoft Learn, 链接数: 42

Text Href
---- ----
PowerShell 文档 https://learn.microsoft.com/powershell/
安装 PowerShell https://learn.microsoft.com/powershell/script...

截图功能

用 Selenium 可以轻松截取完整页面截图。以下函数设置视口大小后导航到目标页面,等待 2 秒让动态内容加载完毕,再保存截图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function Save-WebPageScreenshot {
param(
[Parameter(Mandatory)]
[string]$Url,

[Parameter(Mandatory)]
[string]$OutputPath,

[int]$Width = 1920,
[int]$Height = 1080
)

$driver = Start-BrowserSession -Headless
try {
$driver.Manage().Window.Size = New-Object System.Drawing.Size($Width, $Height)
$driver.Navigate().GoToUrl($Url)
Start-Sleep -Seconds 2

$screenshot = $driver.GetScreenshot()
$screenshot.SaveAsFile($OutputPath, [OpenQA.Selenium.ScreenshotImageFormat]::Png)
Write-Host "截图已保存: $OutputPath"
}
finally {
$driver.Quit()
}
}
1
2
PS> Save-WebPageScreenshot -Url "https://example.com" -OutputPath "$HOME/Desktop/demo.png"
截图已保存: /Users/wubo/Desktop/demo.png

方案二:Playwright(推荐)

Playwright 是微软推出的新一代浏览器自动化工具,相比 Selenium 有三大优势:自动等待(不需要手写 Sleep)、内置多浏览器支持、原生支持 PDF 导出。如果你是新项目,强烈推荐直接使用 Playwright。

安装 Playwright

Playwright 是基于 Node.js 的工具,需要先安装 Node.js 环境,然后通过 npm 安装 Playwright 并下载浏览器内核。

1
2
3
4
5
# 全局安装 Playwright 包
npm install -g playwright

# 下载 Chromium 浏览器内核
npx playwright install chromium
1
2
PS> npx playwright install chromium
Downloading Chromium 131.0.6778.33 (darwin) ... done

通用调用函数

PowerShell 本身没有 Playwright 的原生绑定,但可以通过生成临时 JS 脚本文件来调用。以下函数封装了这个流程:接收 URL 和 JS 脚本片段,自动拼接完整的 Playwright 程序,执行后删除临时文件。

注意 here-string 内不要嵌入三反引号,否则会导致 PowerShell 解析错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function Invoke-PlaywrightScript {
param(
[Parameter(Mandatory)]
[string]$Url,

[Parameter(Mandatory)]
[string]$Script
)

# 拼接完整的 Node.js 脚本
$fullScript = @"
const { chromium } = require('playwright');

(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('$Url');

$Script

await browser.close();
})();
"@

# 写入临时文件并执行
$scriptPath = [System.IO.Path]::GetTempFileName() + ".js"
$fullScript | Out-File -FilePath $scriptPath -Encoding UTF8
node $scriptPath
Remove-Item $scriptPath
}

截图与 PDF 导出

Playwright 的截图和 PDF 功能比 Selenium 更强大。特别是 PDF 导出,Selenium 需要借助 Chrome DevTools Protocol,而 Playwright 直接提供 API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function Save-PlaywrightScreenshot {
param(
[Parameter(Mandatory)]
[string]$Url,

[Parameter(Mandatory)]
[string]$OutputPath
)

$jsCode = "await page.screenshot({ path: '$($OutputPath.Replace('\','/'))', fullPage: true });"
Invoke-PlaywrightScript -Url $Url -Script $jsCode
Write-Host "截图已保存: $OutputPath"
}

function Save-PlaywrightPdf {
param(
[Parameter(Mandatory)]
[string]$Url,

[Parameter(Mandatory)]
[string]$OutputPath
)

$jsCode = "await page.pdf({ path: '$($OutputPath.Replace('\','/'))', format: 'A4' });"
Invoke-PlaywrightScript -Url $Url -Script $jsCode
Write-Host "PDF 已保存: $OutputPath"
}
1
2
3
4
5
PS> Save-PlaywrightScreenshot -Url "https://example.com" -OutputPath "$HOME/Desktop/pw.png"
截图已保存: /Users/wubo/Desktop/pw.png

PS> Save-PlaywrightPdf -Url "https://example.com" -OutputPath "$HOME/Desktop/page.pdf"
PDF 已保存: /Users/wubo/Desktop/page.pdf

网站可用性监控

无论选择哪种方案,浏览器自动化最常见的运维场景就是网站健康检查。以下函数使用 Selenium 批量检测多个 URL 的可访问性和加载耗时,输出结构化的监控数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function Test-WebsiteHealth {
param(
[Parameter(Mandatory)]
[string[]]$Urls,

[int]$TimeoutSeconds = 15
)

$driver = Start-BrowserSession -Headless
$results = @()

try {
foreach ($url in $Urls) {
$sw = [System.Diagnostics.Stopwatch]::StartNew()
try {
$driver.Manage().Timeouts().PageLoad = [TimeSpan]::FromSeconds($TimeoutSeconds)
$driver.Navigate().GoToUrl($url)
$sw.Stop()

$results += [PSCustomObject]@{
Url = $url
Status = "OK"
LoadTimeMs = $sw.ElapsedMilliseconds
Title = $driver.Title
Timestamp = Get-Date
}
}
catch {
$sw.Stop()
$results += [PSCustomObject]@{
Url = $url
Status = "FAIL"
LoadTimeMs = $sw.ElapsedMilliseconds
Title = ""
Timestamp = Get-Date
Error = $_.Exception.Message
}
}
}
}
finally {
$driver.Quit()
}

return $results | Format-Table -AutoSize
}
1
2
3
4
5
6
7
PS> Test-WebsiteHealth -Urls "https://example.com","https://httpbin.org/status/500","https://nonexist.invalid"

Url Status LoadTimeMs Title Timestamp
--- ------ ---------- ----- ---------
https://example.com OK 312 Example Domain 2025/3/28 10:00:00
https://httpbin.org/st... OK 587 2025/3/28 10:00:01
https://nonexist.invalid FAIL 5001 2025/3/28 10:00:06

小结

Selenium 适合简单的页面交互场景,依赖 .NET 生态,PowerShell 调用起来比较自然。Playwright 更现代,自动等待、原生 PDF 支持、多浏览器覆盖等特性让它在复杂场景下更可靠。无头模式下务必注意内存回收,长时间运行的任务一定要在 finally 块中调用 Quit()close() 释放浏览器进程。