什么场景应该用Invoke-WebRequest而不是curl?
选Invoke-WebRequest的三个明确信号:Windows Server常驻脚本、需要与PowerShell对象体系集成、要用Windows凭据。其他场景curl通常更省心。
三大差异对比:
| 维度 | Invoke-WebRequest | curl | 建议选择 |
|---|---|---|---|
| 返回类型 | 强类型PowerShell对象(Headers、Content、StatusCode等属性) | 文本流 | 需要后续用PS对象处理,选前者 |
| 参数命名 | 完整单词加连字符(-Uri、-Method) | 短选项(-X、-H) | 团队协作、脚本可读性,选前者 |
| 代理配置 | -Proxy、-ProxyCredential | -x、--proxy-user | 差别不大 |
| 依赖 | Windows内置 | Win 10 1803之后内置,早期版本要额外装 | 老Windows优选前者 |
| 编码处理 | PowerShell 5.1易乱码,7+改善 | 一致 | 中文站点PS 5.1要额外配 |
Invoke-WebRequest简写为iwr,与它的兄弟Invoke-RestMethod简写为irm。区别在于:iwr返回完整响应对象,包含headers、cookies、状态码;irm自动解析JSON/XML为对象,只返回body。抓取和调试用iwr,纯API消费用irm。
基本请求怎么发?GET/POST与请求头如何设置?
最短的GET请求一行搞定,长期脚本要显式配好超时和错误处理。
GET最简形式:
$r = Invoke-WebRequest -Uri "https://example.com/api/list"
$r.StatusCode # 状态码
$r.Content # 响应体(字符串)
$r.Headers # 响应头带自定义header的GET:
$headers = @{
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0)"
"Accept" = "application/json"
"Authorization" = "Bearer $token"
}
$r = Invoke-WebRequest -Uri $url -Headers $headers -TimeoutSec 30POST三种body格式:
# 1. Form 表单
$body = @{ keyword = "招投标"; page = 1 }
Invoke-WebRequest -Uri $url -Method Post -Body $body
# 2. JSON
$body = @{ query = "舆情"; limit = 100 } | ConvertTo-Json
Invoke-WebRequest -Uri $url -Method Post -Body $body `
-ContentType "application/json; charset=utf-8"
# 3. 文件上传(multipart/form-data)
$form = @{
file = Get-Item "./data.csv"
name = "招标数据"
}
Invoke-WebRequest -Uri $url -Method Post -Form $form超时相关三个参数:
| 参数 | 作用 | 默认值 | 建议 |
|---|---|---|---|
-TimeoutSec | 整个请求超时 | 100秒 | 生产环境显式设10-30秒 |
-MaximumRedirection | 允许重定向次数 | 5 | 抓取静态页可设0 |
-DisableKeepAlive | 关闭长连接 | 默认启用 | 单次请求可开,批量抓取不开 |
如何配置代理和身份认证?
代理配置有三种方式,覆盖大多数场景。
方式一:命令行参数
$proxyUrl = "http://proxy.example.com:8080"
$user = "your_user"
$pass = ConvertTo-SecureString "your_pass" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($user, $pass)
Invoke-WebRequest -Uri $url `
-Proxy $proxyUrl `
-ProxyCredential $cred方式二:环境变量
$env:HTTP_PROXY = "http://user:pass@proxy.example.com:8080"
$env:HTTPS_PROXY = "http://user:pass@proxy.example.com:8080"
$env:NO_PROXY = "localhost,127.0.0.1,*.internal"
Invoke-WebRequest -Uri $url # 自动生效方式三:系统代理
Invoke-WebRequest -Uri $url -UseDefaultCredentials
# 使用当前Windows登录凭据+IE代理设置三种方式对比:
| 方式 | 适用场景 | 注意事项 |
|---|---|---|
| 命令行参数 | 单脚本、临时任务 | 密码明文写在脚本里要脱敏 |
| 环境变量 | CI/CD、多脚本共用 | Windows服务重启会清空 |
| 系统代理 | Windows域内运维 | 仅内网、账密不透明 |
PowerShell 5.1和7+在代理支持上有差异:
- 5.1不支持SOCKS5,只支持HTTP/HTTPS代理
- 5.1不支持
-SkipHttpErrorCheck,4xx/5xx会抛异常 - 7+对HTTP/2支持完整,5.1只走HTTP/1.1
跨境物流信息查询这类需要走境外代理的场景,7+的稳定性和错误处理明显好于5.1,长期任务建议升级。
会话管理Cookie保持怎么用?
跨请求维持登录状态用-SessionVariable创建会话对象,后续请求带-WebSession复用。
登录+抓取的完整流程:
# 1. 登录,创建会话
$login = Invoke-WebRequest -Uri "https://example.com/login" `
-Method Post `
-Body @{ username = "u"; password = "p" } `
-SessionVariable "session"
# 2. 用同一会话抓取
$page = Invoke-WebRequest -Uri "https://example.com/dashboard" `
-WebSession $session
# 3. 会话内的Cookie
$session.Cookies.GetCookies("https://example.com") | Format-Table Name, Value手动读写Cookie:
# 手动添加
$cookie = New-Object System.Net.Cookie
$cookie.Name = "sessionid"
$cookie.Value = "abc123"
$cookie.Domain = "example.com"
$session.Cookies.Add($cookie)
# 序列化保存(长期任务重启后恢复)
$session.Cookies.GetCookies("https://example.com") |
Select-Object Name, Value, Domain, Path, Expires |
ConvertTo-Json | Set-Content "cookies.json"会话管理常见坑:
- 域名不匹配:登录返回的Cookie域名为
.example.com,抓取子域api.example.com时若显式指定domain=example.com会失效 - Path不覆盖:登录返回
Path=/user,抓取/api时Cookie不发送 - HttpOnly不影响PS:Invoke-WebRequest能读到所有Cookie,包括HttpOnly
- Secure Cookie:只在HTTPS请求时发送,混用HTTP/HTTPS要注意
编码乱码、字符集怎么处理?
PowerShell 5.1的中文乱码是最常见的教程盲点。原因是5.1默认按ISO-8859-1解析Content,中文站点几乎必乱。
三种解决方案:
方案一:手动重新解码
$r = Invoke-WebRequest -Uri $url
$bytes = [System.Text.Encoding]::GetEncoding("ISO-8859-1").GetBytes($r.Content)
$content = [System.Text.Encoding]::UTF8.GetString($bytes)方案二:改用Invoke-RestMethod并指定字符集
$r = Invoke-RestMethod -Uri $url -ContentType "text/html; charset=utf-8"方案三:升级到PowerShell 7+
7+默认按响应头Content-Type的charset参数解析,行为符合直觉。
编码问题的自检脚本:
# 输出响应真实字符集
$r = Invoke-WebRequest -Uri $url
$r.Headers["Content-Type"]
$r.BaseResponse.CharacterSet # PowerShell 7+ 可用写入文件时的编码坑同样常见:
# ❌ 5.1 默认写入UTF-16 with BOM
$content | Out-File "data.html"
# ✅ 显式指定UTF-8无BOM
[System.IO.File]::WriteAllText("data.html", $content,
[System.Text.UTF8Encoding]::new($false))TLS版本、证书与安全设置如何配置?
PowerShell 5.1默认不启用TLS 1.2,抓取要求TLS 1.2+的目标站会直接握手失败。
启用TLS 1.2的三种粒度:
# 全会话级(脚本开头一次)
[Net.ServicePointManager]::SecurityProtocol =
[Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13
# 系统级(管理员权限,注册表持久)
Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319' `
-Name 'SchUseStrongCrypto' -Value 1 -Type DWord
# PowerShell 7+ 自动支持 TLS 1.3证书相关配置:
# 跳过证书校验(仅调试用,生产禁用)
# PowerShell 7+
Invoke-WebRequest -Uri $url -SkipCertificateCheck
# PowerShell 5.1(全局跳过,副作用大)
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }常见TLS错误对照:
| 错误 | 根因 | 修复 |
|---|---|---|
| The request was aborted: Could not create SSL/TLS secure channel | 未启用TLS 1.2 | 加Tls12设置 |
| The remote certificate is invalid according to the validation procedure | 系统根证书过期 | certutil -generateSSTFromWU更新 |
| The underlying connection was closed | 目标要求TLS 1.3或特定加密套件 | 升级到PS 7+ |
长期任务下的错误重试与日志怎么做?
生产环境跑Invoke-WebRequest的三个必备能力:错误分类、指数退避重试、结构化日志。
带指数退避的通用请求函数:
function Invoke-RetryRequest {
param(
[string]$Uri,
[string]$Method = "GET",
[hashtable]$Headers = @{},
[int]$MaxRetries = 3,
[int]$InitialDelay = 1000
)
for ($i = 0; $i -lt $MaxRetries; $i++) {
try {
$r = Invoke-WebRequest -Uri $Uri -Method $Method `
-Headers $Headers -TimeoutSec 20 -ErrorAction Stop
return $r
}
catch {
$status = $_.Exception.Response.StatusCode.value__
# 分类:可重试 vs 立即失败
if ($status -in 400,401,403,404) {
Write-Warning "不重试的错误: $status"
throw
}
$delay = $InitialDelay * [Math]::Pow(2, $i)
Write-Warning "第 $($i+1) 次失败: $status,等待 ${delay}ms 重试"
Start-Sleep -Milliseconds $delay
}
}
throw "达到最大重试次数"
}错误分类原则:
| 状态码 | 分类 | 处理 |
|---|---|---|
| 200-299 | 成功 | 正常处理响应 |
| 300-399 | 重定向 | Invoke-WebRequest默认跟随5次 |
| 400/401/403/404 | 客户端错误 | 立即失败,不重试 |
| 407 | 代理鉴权失败 | 立即失败,检查代理配置 |
| 429 | 访问频率限制 | 读Retry-After头,按值等待重试 |
| 500/502/503/504 | 服务端错误 | 指数退避重试 |
| 网络异常 | 连接层 | 指数退避重试 |
结构化日志便于后续采集到日志系统:
function Write-StructuredLog {
param($Level, $Message, $Extra = @{})
$log = @{
timestamp = (Get-Date).ToString("o")
level = $Level
message = $Message
} + $Extra
$log | ConvertTo-Json -Compress | Add-Content "task.log"
}
Write-StructuredLog -Level "INFO" -Message "请求完成" -Extra @{
url = $url
status = $r.StatusCode
ms = $sw.ElapsedMilliseconds
}舆情监测和招投标数据这类需要日跑几万次请求的任务,务必把三样都上齐:分类的错误处理、指数退避、结构化日志。缺任何一项,故障复盘就会很痛苦。
FAQ
Q:Invoke-WebRequest和Invoke-RestMethod到底该用哪个?
抓取网页、需要拿到状态码/headers/原始HTML就用Invoke-WebRequest;纯粹调用返回JSON/XML的REST API、想直接拿到解析后的对象就用Invoke-RestMethod。两者底层实现完全相同,只是返回类型不一样。生产环境把两者结合用最常见:先用iwr做健康检查看状态码,再用irm消费API。
Q:为什么代理配了但请求不走代理?
四种典型原因:一是环境变量在当前会话没生效,$env:HTTP_PROXY需要重启会话或用Set-Item Env:重新赋值;二是目标URL在$env:NO_PROXY匹配范围内;三是使用了-UseDefaultCredentials,此时会走系统IE代理设置而非命令行参数;四是Invoke-WebRequest内部走了.NET WebRequest的默认代理策略,可以显式-Proxy $null覆盖。用Fiddler或Wireshark抓包能直接确认。
Q:PowerShell 5.1能不能用SOCKS5代理?
内置不支持。5.1的Invoke-WebRequest底层是HttpWebRequest,只支持HTTP/HTTPS代理协议。要用SOCKS5有三条路:一是升级到PowerShell 7+,7+原生支持;二是本地起一个HTTP-to-SOCKS转换器如Privoxy,PowerShell走HTTP代理连到本地转换器;三是用.NET Core的SocketsHttpHandler手写,工作量大。多数团队直接选升级7+。
Q:抓取的HTML能不能用CSS选择器或XPath解析?
Invoke-WebRequest返回的$r.ParsedHtml在Windows PowerShell 5.1上是HTMLDocument对象,可以用getElementsByTagName等原生方法,但速度慢且依赖IE引擎。生产环境更推荐用HtmlAgilityPack——它是.NET NuGet包,或者用AngleSharp。PowerShell 7+已经移除ParsedHtml属性,必须用第三方库。
Q:请求量大的时候有没有性能瓶颈?
三个典型瓶颈:一是ServicePoint连接数限制,[System.Net.ServicePointManager]::DefaultConnectionLimit默认2,改成100+;二是DNS缓存,同一域名解析会缓存,长时间脚本要定期清理;三是单线程模型,PowerShell本身是同步阻塞。要打真正的并发要么用Start-Job做进程隔离但开销大,要么用Runspaces的RunspacePool更轻量,要么直接切到PowerShell 7的ForEach-Object -Parallel。
Q:怎么调试请求实际发出的完整内容?
三个方法叠加用最清楚:一是本地起Fiddler或mitmproxy,PowerShell配代理到127.0.0.1:8080,能看到完整request/response;二是$r.BaseResponse.ResponseUri看重定向后的最终URL;三是设置$VerbosePreference = "Continue"后加-Verbose参数,PowerShell会打印内部诊断信息。
