如何在 PowerShell 中使用 Wget 命令 – wiki基地


PowerShell 中的 Wget:深度探索与实用指南

在信息技术领域,自动化和批处理任务的执行效率至关重要。对于许多系统管理员、开发者和日常用户而言,从互联网上下载文件或与Web服务进行交互是常见的操作。在类Unix系统中,wget 命令是完成这些任务的瑞士军刀,以其强大的功能、非交互性和对复杂下载场景的支持而闻名。然而,当我们将目光转向Windows环境下的PowerShell时,许多人可能会好奇:PowerShell中是否存在 wget?如果存在,它又该如何使用?本文将深入探讨PowerShell中 wget 的真正含义,详细介绍其官方等价物 Invoke-WebRequest 的强大功能,以及如何利用PowerShell的脚本能力模拟 wget 的高级特性。

导言:Wget 的魅力与 PowerShell 的疑惑

Wget,全称“Web Get”,是一个自由的、非交互式的网络下载工具,支持HTTP、HTTPS和FTP协议。它的特点是可以在后台运行,即使用户注销后也能继续下载;同时,它还支持断点续传、递归下载整个网站、限速、处理Cookie等高级功能,使其成为命令行下载工具中的佼佼者。

对于习惯了Linux/macOS环境的开发者和系统管理员来说,wget 几乎是下载文件时的第一选择。然而,Windows系统默认并没有内置 wget。这导致了一个常见的疑惑:在PowerShell中输入 wget 还能用吗?

答案是:能用,但此 wget 非彼 wget

PowerShell 自带了一个名为 Invoke-WebRequest 的 cmdlet,它的一个默认别名(Alias)就是 wget。这意味着,当你在PowerShell中输入 wget 时,你实际上是在执行 Invoke-WebRequest 这个 cmdlet。虽然 Invoke-WebRequest 提供了类似 wget 的功能,但它并非 GNU Wget 的原始实现,而是一个由微软为PowerShell环境专门开发的、功能更强大、与PowerShell生态系统深度融合的Web请求工具。

本文的目标是:
1. 澄清 PowerShell 中 wget 别名与 GNU Wget 工具之间的区别。
2. 详细讲解 PowerShell 的核心 Web 请求 cmdlet:Invoke-WebRequestiwr)的基本用法和高级功能。
3. 演示如何利用 Invoke-WebRequest 及其相关的PowerShell特性来模拟 GNU Wget 的常见高级功能,如递归下载、会话管理等。
4. 探讨何时以及如何集成真正的 GNU Wget 到 PowerShell 环境中。
5. 提供使用这些工具进行Web交互时的安全考量和最佳实践。

通过本文的阅读,你将全面掌握在PowerShell中进行Web下载和交互的艺术,无论是简单的文件下载还是复杂的API调用。

I. 理解 Wget:核心价值与非Windows原生性

在深入PowerShell的 Invoke-WebRequest 之前,我们有必要简要回顾一下真正的 GNU Wget 为什么如此受欢迎,以及它在Windows系统中的地位。

1.1 GNU Wget 的核心价值

GNU Wget 的设计哲学是“非交互式”和“健壮性”。这意味着它可以在没有人为干预的情况下完成任务,并且能够优雅地处理网络中断、服务器拒绝连接等异常情况。其核心价值体现在以下几个方面:

  • 非交互性: 可以在脚本、定时任务或后台进程中运行,无需用户输入。
  • 断点续传: 支持从中断的地方继续下载,对于大文件或不稳定的网络环境非常有用。
  • 递归下载: 可以下载整个网站或指定目录下的所有文件和链接,这对于离线浏览或网站备份非常实用。
  • 限速与重试: 可以限制下载速度,并在下载失败时自动重试。
  • 支持多种协议: HTTP、HTTPS、FTP,涵盖了互联网上绝大多数的下载场景。
  • 跨平台: GNU Wget 是一个开源项目,可以在几乎所有类Unix系统上运行,并且有官方或第三方的Windows编译版本。
  • SSL/TLS支持: 可以安全地下载HTTPS资源。
  • 用户代理伪装: 可以模拟不同的浏览器或其他客户端,以应对一些网站的反爬虫机制。

1.2 Wget 在 Windows 系统中的非原生性

尽管 Wget 功能强大,但它并不是Windows操作系统的原生组件。这意味着,开箱即用的Windows系统无法直接通过 wget 命令来执行下载任务。要在Windows上使用真正的 Wget,你需要:

  • 手动下载并配置: 从GNU Wget 的官方网站或相关镜像站点下载Windows编译版本(通常是一个 .zip.exe 文件),然后手动将其添加到系统PATH环境变量中,以便在任何目录下都能直接调用。
  • 包管理器安装: 这是更推荐的方式。Windows上流行的包管理器如 ChocolateyScoop 都提供了 wget 的安装包。例如,使用 Chocolatey 只需执行 choco install wget 即可。这些包管理器会自动处理下载、安装和PATH配置。

一旦真正的 Wget 被安装并配置到PATH中,你就可以在PowerShell中通过其完整路径或直接输入 wget 来调用它。然而,这可能会与PowerShell自身的 wget 别名产生冲突。PowerShell的命令解析顺序是:别名 -> 函数 -> Cmdlet -> 外部可执行文件。这意味着如果 Invoke-WebRequestwget 别名存在,PowerShell会优先执行 Invoke-WebRequest。为了避免混淆,当需要调用真正的 Wget 时,通常会使用其完整路径,或者使用 & "C:\path\to\wget.exe" 这样的形式明确指定执行外部命令。

但大多数情况下,PowerShell的用户会发现 Invoke-WebRequest 已经足够强大,足以替代大部分 wget 的功能。

II. PowerShell 中的 Wget 等价物:Invoke-WebRequest

Invoke-WebRequest (简称 iwr) 是PowerShell中用于向Web服务器发送HTTP、HTTPS或FTP请求的核心 Cmdlet。它不仅可以下载文件,还能获取网页内容、与RESTful API交互、处理表单提交等。它是PowerShell实现“Web Get”功能的真正主力。

2.1 Invoke-WebRequest 是什么?

Invoke-WebRequest 是一个高度灵活的 Cmdlet,它的设计目标是让PowerShell用户能够方便地与Web资源进行交互。它不仅仅是一个下载器,更是一个Web客户端,能够模拟浏览器的大部分行为。

Invoke-WebRequest 的主要特点:

  • 返回丰富对象:wget 仅仅下载文件不同,Invoke-WebRequest 返回一个 HtmlWebResponseObject 对象(对于HTML/XML内容)或 WebResponseObject 对象(对于其他内容),这个对象包含了响应头、状态码、Cookies、链接、图片、表单等丰富的信息,方便后续的PowerShell操作。
  • 易于解析: 对于HTML内容,返回对象提供了 ParsedHtml 属性,可以直接使用 Select-ObjectSelect-Xml 等PowerShell Cmdlet进行DOM解析。对于JSON或XML内容,可以利用 ConvertFrom-JsonSelect-Xml 进行解析。
  • 灵活的请求配置: 提供了大量的参数来配置HTTP请求,包括请求方法(GET、POST、PUT等)、请求头、请求体、Cookie管理、代理设置、认证等。
  • 与PowerShell管道集成: 作为PowerShell的Cmdlet,它可以方便地与其他Cmdlet通过管道进行数据传输和处理,实现复杂的自动化工作流。

2.2 Invoke-WebRequest 的基本用法

掌握 Invoke-WebRequest 的基本用法是开始进行Web交互的第一步。

2.2.1 下载单个文件

这是最常见的 wget 用途之一。使用 Invoke-WebRequest 下载文件,你需要指定 -Uri (URL) 和 -OutFile (保存路径) 参数。

示例:下载一个ZIP文件

“`powershell

目标文件的URL

$fileUrl = “https://github.com/Pester/Pester/releases/download/5.5.0/Pester.zip”

本地保存路径

$destinationPath = “C:\Temp\Pester.zip”

Write-Host “正在从 $fileUrl 下载文件到 $destinationPath …”

try {
Invoke-WebRequest -Uri $fileUrl -OutFile $destinationPath -ErrorAction Stop
Write-Host “文件下载成功!” -ForegroundColor Green
}
catch {
Write-Error “文件下载失败:$($_.Exception.Message)”
}
“`

  • -Uri: 指定要请求的URL。
  • -OutFile: 指定将下载的内容保存到本地文件的路径。如果文件已存在,默认会覆盖。
  • -ErrorAction Stop: 确保在发生错误时脚本停止执行,并进入 catch 块。
  • try...catch: 良好的错误处理实践,确保下载失败时能够捕获并报告错误。

2.2.2 获取网页内容或API响应

如果你不指定 -OutFile 参数,Invoke-WebRequest 会将Web响应的内容作为字符串返回到PowerShell管道。这对于获取HTML页面、JSON数据或XML数据非常有用。

示例:获取一个网页的HTML内容

“`powershell
$webPageUrl = “https://www.example.com”

Write-Host “正在获取网页内容:$webPageUrl”

try {
$response = Invoke-WebRequest -Uri $webPageUrl -UseBasicParsing -ErrorAction Stop
# 获取页面的原始HTML内容
$htmlContent = $response.Content
Write-Host “网页内容已获取,前200字符:`n$($htmlContent.Substring(0, [System.Math]::Min(200, $htmlContent.Length)))”

# 进一步处理HTML内容,例如查找所有链接
Write-Host "`n页面中的所有链接:"
$response.Links | ForEach-Object {
    Write-Host "  - $($_.href)"
}

}
catch {
Write-Error “获取网页内容失败:$($_.Exception.Message)”
}
“`

  • -UseBasicParsing: 这是一个非常重要的参数,尤其是在处理大量请求或性能敏感的场景时。默认情况下,Invoke-WebRequest 会尝试使用IE引擎来解析HTML内容,这会消耗更多资源并可能导致兼容性问题。UseBasicParsing 强制PowerShell使用内置的、更轻量级的解析器,显著提高性能和稳定性。对于非HTML内容(如JSON、XML),它没有任何影响,但建议始终使用。
  • $response.Content: 包含了Web响应的原始内容,通常是HTML、JSON或XML字符串。
  • $response.Links, $response.Images, $response.Forms: Invoke-WebRequest 的返回对象 HtmlWebResponseObject 会自动解析HTML中的常见元素,并提供便捷的属性来访问它们。

示例:获取一个REST API的JSON响应

“`powershell
$apiUrl = “https://jsonplaceholder.typicode.com/posts/1”

Write-Host “正在获取API响应:$apiUrl”

try {
$response = Invoke-WebRequest -Uri $apiUrl -UseBasicParsing -ErrorAction Stop
# 原始JSON字符串
$jsonContent = $response.Content
Write-Host “原始JSON内容:$jsonContent”

# 将JSON字符串转换为PowerShell对象
$dataObject = $jsonContent | ConvertFrom-Json
Write-Host "`n解析后的对象属性:"
$dataObject | Get-Member -MemberType Property | Select-Object Name, Definition
Write-Host "`n获取特定属性:Title = $($dataObject.title)"

}
catch {
Write-Error “获取API响应失败:$($_.Exception.Message)”
}
“`

  • ConvertFrom-Json: 这是PowerShell中用于将JSON格式字符串转换为PowerShell自定义对象(PSCustomObject)的Cmdlet,极大地简化了JSON数据的处理。

2.3 Invoke-WebRequest 的高级参数与技巧

Invoke-WebRequest 提供了丰富的参数,使其能够应对各种复杂的Web请求场景。

2.3.1 请求方法 (-Method)

默认是 GET。你可以使用 -Method 参数指定不同的HTTP方法,如 POST, PUT, DELETE 等,这对于与RESTful API交互至关重要。

示例:发送一个POST请求

“`powershell
$postUrl = “https://jsonplaceholder.typicode.com/posts”
$headers = @{ “Content-Type” = “application/json” }
$body = @{
title = “foo”
body = “bar”
userId = 1
} | ConvertTo-Json # 将PowerShell对象转换为JSON字符串

Write-Host “正在发送POST请求到 $postUrl …”

try {
$response = Invoke-WebRequest -Uri $postUrl -Method POST -Headers $headers -Body $body -UseBasicParsing -ErrorAction Stop
Write-Host “请求状态码:$($response.StatusCode)”
Write-Host “响应内容:$($response.Content | ConvertFrom-Json | Format-List)”
}
catch {
Write-Error “POST请求失败:$($_.Exception.Message)”
}
“`

  • -Method POST: 指定HTTP POST请求。
  • -Headers: 允许你添加自定义的HTTP请求头。这里我们设置 Content-Typeapplication/json
  • -Body: 包含请求体的数据。对于POST请求,这通常是表单数据或JSON/XML负载。ConvertTo-Json 用于将PowerShell对象转换为JSON字符串。

2.3.2 请求头 (-Headers)

-Headers 参数允许你向请求添加一个或多个自定义的HTTP请求头。这对于设置 User-AgentAuthorization 令牌、Accept 类型等非常有用。

示例:设置自定义User-Agent

“`powershell
$url = “http://httpbin.org/user-agent” # 一个回显User-Agent的测试服务
$customUserAgent = “MyCustomPowerShellClient/1.0”

$response = Invoke-WebRequest -Uri $url -Headers @{ “User-Agent” = $customUserAgent } -UseBasicParsing
Write-Host “发送的User-Agent是: $($response.Content)”
“`

2.3.3 会话管理 (-SessionVariable)

对于需要保持会话(如登录后访问受保护资源)的Web应用,Invoke-WebRequest 提供了 -SessionVariable 参数来管理 WebSession 对象,它会自动处理Cookies。

示例:模拟登录并访问受保护页面

“`powershell

假设的登录URL和受保护页面URL

$loginUrl = “https://example.com/login”
$protectedUrl = “https://example.com/dashboard”

假设的登录凭据

$username = “myuser”
$password = “mypassword”

创建一个会话变量

$sessionName = “mySession”

Write-Host “正在尝试登录…”
try {
# 模拟登录(通常是一个POST请求)
# 假设登录表单的字段是 username 和 password
$loginBody = @{
username = $username
password = $password
}

$loginResponse = Invoke-WebRequest -Uri $loginUrl -Method POST -Body $loginBody -SessionVariable $sessionName -UseBasicParsing -ErrorAction Stop

Write-Host "登录请求完成,状态码: $($loginResponse.StatusCode)"
# 你可以检查 $loginResponse.Content 来确认是否登录成功
# 例如:if ($loginResponse.Content -like "*Welcome, $username*") { ... }

Write-Host "正在使用会话访问受保护页面..."
# 使用同一个会话变量访问受保护页面
$protectedPageResponse = Invoke-WebRequest -Uri $protectedUrl -WebSession (Get-Variable $sessionName).Value -UseBasicParsing -ErrorAction Stop

Write-Host "受保护页面内容(部分):"
Write-Host "$($protectedPageResponse.Content.Substring(0, [System.Math]::Min(500, $protectedPageResponse.Content.Length)))"

}
catch {
Write-Error “会话管理或登录失败:$($_.Exception.Message)”
}
“`

  • -SessionVariable $sessionName: Invoke-WebRequest 会创建一个 WebSession 对象并将其存储在 $sessionName 变量中。后续的 Invoke-WebRequest 请求可以通过 -WebSession (Get-Variable $sessionName).Value 参数来引用这个会话,从而自动使用会话中保存的Cookies和其他会话信息。

2.3.4 认证 (-Credential, -Headers)

对于需要身份验证的资源,可以使用 -Credential 参数传递 PSCredential 对象,或者手动构造 Authorization 请求头。

示例:使用基本认证

“`powershell
$authUrl = “https://httpbin.org/basic-auth/user/passwd”
$username = “user”
$password = “passwd”

创建 PSCredential 对象

$credential = New-Object System.Management.Automation.PSCredential($username, (ConvertTo-SecureString $password -AsPlainText -Force))

Write-Host “正在尝试基本认证访问 $authUrl …”

try {
$response = Invoke-WebRequest -Uri $authUrl -Credential $credential -UseBasicParsing -ErrorAction Stop
Write-Host “认证成功!响应内容:$($response.Content)”
}
catch {
Write-Error “认证失败:$($_.Exception.Message)”
}
“`

  • -Credential: 对于基本认证(Basic Authentication)或某些NTLM认证,可以直接使用此参数。
  • Authorization Header: 对于Bearer Token或其他类型的认证,需要手动在 -Headers 参数中构造 Authorization 头。

2.3.5 代理 (-Proxy, -ProxyCredential)

如果你需要通过代理服务器访问互联网,可以使用 -Proxy-ProxyCredential 参数。

“`powershell
$proxyUrl = “http://your.proxy.server:8080”
$targetUrl = “http://example.com”

Write-Host “正在通过代理 $proxyUrl 访问 $targetUrl …”

try {
$response = Invoke-WebRequest -Uri $targetUrl -Proxy $proxyUrl -UseBasicParsing -ErrorAction Stop
Write-Host “通过代理访问成功!状态码:$($response.StatusCode)”
}
catch {
Write-Error “通过代理访问失败:$($_.Exception.Message)”
}
“`

2.3.6 其他实用参数

  • -TimeoutSec: 设置请求的超时时间(秒)。
  • -MaximumRedirection: 允许的最大重定向次数。
  • -AllowUnencrypted: 允许通过非加密连接(HTTP)发送敏感信息(如凭据)。谨慎使用。
  • -DisableKeepAlive: 关闭HTTP持久连接。
  • -FollowRelLink: 遵循 rel 属性为 nextprevfirstlast 的链接,有助于导航API分页。

2.4 解析返回内容

Invoke-WebRequest 返回的对象 $response 具有多种属性,可以方便地访问和解析Web响应。

  • $response.Content: Web响应的原始文本内容。
  • $response.StatusCode: HTTP状态码,如 200 (OK), 404 (Not Found), 500 (Internal Server Error) 等。
  • $response.Headers: 一个哈希表,包含Web响应的所有HTTP头。
  • $response.RawContent: 包含原始响应字符串,包括HTTP头和内容。
  • $response.Links, $response.Images, $response.Forms: (仅当 -UseBasicParsing 未使用,或解析引擎成功识别HTML时) 分别是 ParsedHtml 对象中解析出的链接、图片和表单元素的集合。每个元素又是一个 HtmlLinkHtmlImageHtmlForm 对象,包含了它们的属性(如 href, src, action 等)。
  • $response.ParsedHtml: 一个HTML DOM对象,你可以像在浏览器开发者工具中那样遍历和操作它。可以使用 Select-HtmlSelect-Xml Cmdlet(需要导入 Microsoft.PowerShell.Utility 模块或使用特定PowerShell版本)进行XPath或CSS选择器查询。

示例:使用 $response.ParsedHtmlSelect-Html(或 Select-Xml)进行高级解析

“`powershell

这是一个更高级的用法,需要PowerShell 6.0+ 或特定模块支持。

对于PowerShell 5.1,可以直接操作 $response.ParsedHtml 对象的 GetElementsByTagName() 等方法。

假设我们想从一个网页中提取所有标题 (h1, h2, h3)

$webPageUrl = “https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest”

try {
$response = Invoke-WebRequest -Uri $webPageUrl -UseBasicParsing -ErrorAction Stop

# 如果是PowerShell 5.1或更早版本,可能需要这样操作:
# $response.ParsedHtml.getElementsByTagName("h1")
# 或者使用 Select-Xml cmdlet

# 对于PowerShell Core (6.0+) 或安装了 PnP.PowerShell 模块的PowerShell 5.1,可以使用更现代的CSS选择器:
$titles = $response.ParsedHtml.SelectNodes("h1, h2, h3") # XPath 示例
# 或者 $response.ParsedHtml.querySelectorAll("h1, h2, h3") # CSS Selector 示例(部分HTML对象支持)

Write-Host "页面中的标题:"
if ($titles) {
    $titles | ForEach-Object {
        Write-Host "  - $($_.innerText.Trim())" # innerText 获取元素内部的文本
    }
} else {
    Write-Host "未找到任何标题或解析失败。"
}

}
catch {
Write-Error “解析网页内容失败:$($_.Exception.Message)”
}
“`

  • 注意事项: ParsedHtml 的可用性和功能可能因PowerShell版本和底层COM对象的差异而异。对于最健壮的HTML解析,许多专业脚本会选择引入外部库,如 .NET 的 Html Agility Pack,或者在PowerShell 7+ 中直接使用 HtmlParser 相关类。但对于简单的场景,$response.Links, $response.Images 以及 $response.ParsedHtml.GetElementsByTagName() 方法通常足够。

III. 模拟 Wget 的高级功能

尽管 Invoke-WebRequest 没有直接提供 wget 的所有高级功能(如内置的递归下载),但PowerShell的强大脚本能力可以让我们轻松地模拟它们。

3.1 递归下载 (Recursive Download)

这是 wget 最强大的功能之一。Invoke-WebRequest 没有 -Recursive 参数。要实现递归下载,我们需要编写一个递归函数。

基本逻辑:

  1. 定义一个递归函数,接受URL和下载深度作为参数。
  2. 下载当前URL的HTML内容。
  3. 解析HTML内容,提取所有链接。
  4. 过滤链接(只处理同一域名下的链接,排除外部链接,排除特定文件类型等)。
  5. 对于每个有效链接,如果下载深度允许,则递归调用自身。
  6. 保存文件(如果链接指向的是文件,而不是另一个HTML页面)。
  7. 维护一个已访问URL的列表,避免重复下载和无限循环。

简化示例 (仅获取页面链接并递归,不涉及文件保存和复杂过滤):

“`powershell
function Invoke-WebRecursiveDownload {
param (
[string]$Uri,
[int]$Depth = 0,
[int]$MaxDepth = 2,
[hashtable]$VisitedUrls = @{} # 用于存储已访问的URL,避免循环
)

if ($Depth -gt $MaxDepth) {
    return
}

if ($VisitedUrls.ContainsKey($Uri)) {
    Write-Host "已访问: $Uri - 跳过"
    return
}

$VisitedUrls[$Uri] = $true
Write-Host "正在下载 ($Depth/$MaxDepth): $Uri"

try {
    $response = Invoke-WebRequest -Uri $Uri -UseBasicParsing -ErrorAction SilentlyContinue
    if ($response -and $response.StatusCode -eq 200 -and $response.Content) {
        # 假设我们只关心HTML内容
        if ($response.Headers.'Content-Type' -like '*text/html*') {
            # 提取页面中的所有链接
            $links = $response.Links | Select-Object -ExpandProperty href

            foreach ($link in $links) {
                try {
                    # 将相对路径转换为绝对路径
                    $absoluteLink = Resolve-Uri -BaseUri $Uri -RelativeUri $link

                    # 仅处理同一域名下的链接
                    $baseUriHost = (New-Object System.Uri($Uri)).Host
                    $linkUriHost = (New-Object System.Uri($absoluteLink)).Host

                    if ($linkUriHost -eq $baseUriHost) {
                        Invoke-WebRecursiveDownload -Uri $absoluteLink -Depth ($Depth + 1) -MaxDepth $MaxDepth -VisitedUrls $VisitedUrls
                    }
                }
                catch {
                    # Write-Verbose "处理链接 $link 失败: $($_.Exception.Message)"
                }
            }
        }
        # 如果是文件,可以保存到本地
        # 例如,如果URL以.zip, .pdf 等结尾,则保存
        # if ($Uri -match '\.(zip|pdf|docx|xlsx|pptx)$') {
        #     $fileName = Split-Path $Uri -Leaf
        #     $outputPath = Join-Path "C:\Downloads\Recursive" $fileName
        #     Invoke-WebRequest -Uri $Uri -OutFile $outputPath -ErrorAction SilentlyContinue
        #     Write-Host "保存文件: $outputPath"
        # }
    }
}
catch {
    Write-Warning "下载 $Uri 失败:$($_.Exception.Message)"
}

}

调用示例:从 example.com 开始,最多递归2层

$initialUri = “http://example.com”
Invoke-WebRecursiveDownload -Uri $initialUri -MaxDepth 1
“`

  • Resolve-Uri: 这是一个PowerShell 6+ 中的新Cmdlet,用于将相对URL解析为绝对URL。在旧版本中需要手动处理或使用 .NET 的 System.Uri 类。
  • VisitedUrls 哈希表: 防止无限循环和重复下载。
  • 域名过滤: 确保只在目标网站内部进行递归。
  • 文件保存逻辑: 你需要根据实际需求添加判断逻辑,以区分HTML页面和可下载文件,并保存文件。

实现一个完整且健壮的递归下载器非常复杂,涉及到URL规范化、文件类型判断、目录结构模拟、错误重试、限速等,通常会比上面的简化示例复杂得多。然而,PowerShell提供了所有必要的工具链(Invoke-WebRequest、文件系统操作Cmdlet、字符串和URL处理函数)来构建这样的工具。

3.2 断点续传 (Resuming Downloads)

wget -c 允许从上次中断的地方继续下载。Invoke-WebRequest 本身没有直接的 -Resume 参数。但是,我们可以通过检查本地文件大小和使用HTTP Range 头来模拟这一行为。

基本逻辑:

  1. 检查目标文件是否存在于本地。
  2. 如果存在,获取其当前大小。
  3. 向服务器发送 HEAD 请求获取文件总大小(或在 GET 请求中解析 Content-Length)。
  4. 如果本地文件小于服务器文件,则在 GET 请求中设置 Range 头,请求从本地文件末尾开始的数据。
  5. 将下载到的数据追加到现有文件中。

示例:

“`powershell
function Invoke-WebDownloadWithResume {
param (
[string]$Uri,
[string]$OutFile
)

$localFileSize = 0
if (Test-Path $OutFile -PathType Leaf) {
    $localFileSize = (Get-Item $OutFile).Length
    Write-Host "本地文件 $OutFile 已存在,大小为 $localFileSize 字节。"
}

try {
    # 获取远程文件总大小,通过HEAD请求或GET请求头
    $remoteHeaders = (Invoke-WebRequest -Uri $Uri -Method Head -UseBasicParsing -ErrorAction Stop).Headers
    $remoteFileSize = [int64]$remoteHeaders.'Content-Length'

    Write-Host "远程文件总大小为 $remoteFileSize 字节。"

    if ($localFileSize -ge $remoteFileSize) {
        Write-Host "文件已完整下载,无需续传。"
        return $OutFile
    }

    if ($localFileSize -gt 0 -and $localFileSize -lt $remoteFileSize) {
        Write-Host "正在尝试续传从 $localFileSize 字节开始..."
        $rangeHeader = "bytes=$localFileSize-"
        $downloadHeaders = @{ "Range" = $rangeHeader }

        # 使用 -OutFile 参数时,Invoke-WebRequest 会自动覆盖文件。
        # 所以我们需要先下载到临时文件,再追加。
        $tempFile = "$OutFile.part"
        Invoke-WebRequest -Uri $Uri -Headers $downloadHeaders -OutFile $tempFile -UseBasicParsing -ErrorAction Stop

        # 将临时文件内容追加到主文件
        Add-Content -Path $OutFile -LiteralPath $tempFile -Encoding Byte # 使用 -Encoding Byte 进行二进制追加
        Remove-Item $tempFile -Force # 删除临时文件

        Write-Host "文件续传成功!"
    }
    else {
        Write-Host "正在下载新文件..."
        Invoke-WebRequest -Uri $Uri -OutFile $OutFile -UseBasicParsing -ErrorAction Stop
        Write-Host "文件下载成功!"
    }
}
catch {
    Write-Error "下载/续传文件失败:$($_.Exception.Message)"
}
return $OutFile

}

调用示例:

$downloadUrl = “https://speed.hetzner.de/100MB.bin” # 一个测试下载URL
$outputFile = “C:\Temp\100MB.bin”
Invoke-WebDownloadWithResume -Uri $downloadUrl -OutFile $outputFile
“`

  • Add-Content -Encoding Byte: 这是关键,确保以二进制模式追加内容,避免文件损坏。
  • 临时文件: 由于 Invoke-WebRequest -OutFile 默认会覆盖文件,所以续传时先下载到临时文件,再追加是常用的方法。
  • 服务器支持: 这种方法依赖于服务器支持 Range HTTP头。大多数现代Web服务器都支持。

3.3 限速 (Rate Limiting)

wget 可以通过 -limit-rate 参数限制下载速度。Invoke-WebRequest 没有直接的限速功能,但你可以通过在循环中下载时插入 Start-Sleep 来模拟对请求频率的限制。对于文件下载,这无法直接限制传输速度,只能限制并发请求或请求之间的间隔。

“`powershell

示例:每隔5秒下载一个文件(模拟限速请求频率)

$fileUrls = @(
“https://example.com/file1.zip”,
“https://example.com/file2.zip”,
“https://example.com/file3.zip”
)

$downloadDir = “C:\Downloads”
if (-not (Test-Path $downloadDir)) { New-Item -Path $downloadDir -ItemType Directory | Out-Null }

$intervalSeconds = 5 # 每隔5秒下载一个

foreach ($url in $fileUrls) {
$fileName = Split-Path $url -Leaf
$outputPath = Join-Path $downloadDir $fileName

Write-Host "正在下载 $url 到 $outputPath ..."
try {
    Invoke-WebRequest -Uri $url -OutFile $outputPath -ErrorAction Stop
    Write-Host "下载完成: $outputPath"
}
catch {
    Write-Error "下载失败: $url - $($_.Exception.Message)"
}

if ($url -ne $fileUrls[-1]) { # 最后一个文件下载完后不再等待
    Write-Host "等待 $intervalSeconds 秒..."
    Start-Sleep -Seconds $intervalSeconds
}

}
Write-Host “所有文件下载任务完成。”
“`

  • Start-Sleep -Seconds X: 暂停脚本执行X秒。这对于控制请求频率,避免对服务器造成过大压力或触发反爬虫机制非常有用。

3.4 用户代理与伪装 (-UserAgent)

如前所述,-Headers 参数可以设置任何自定义头,包括 User-AgentInvoke-WebRequest 还有一个专门的 -UserAgent 参数,可以更方便地设置。

“`powershell
$url = “http://httpbin.org/user-agent”

模拟 Chrome 浏览器

$responseChrome = Invoke-WebRequest -Uri $url -UserAgent “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36” -UseBasicParsing
Write-Host “模拟 Chrome 的 User-Agent: $($responseChrome.Content)”

模拟 PowerShell 默认的 User-Agent

$responseDefault = Invoke-WebRequest -Uri $url -UseBasicParsing
Write-Host “PowerShell 默认的 User-Agent: $($responseDefault.Content)”
“`

IV. 何时使用真正的 Wget

尽管 Invoke-WebRequest 功能强大,但在某些特定场景下,你可能仍然会考虑使用独立的 GNU Wget 工具。

  1. 复杂的递归下载逻辑: 真正的 wget 在递归下载方面拥有非常成熟且经过优化的算法,包括对 robots.txtnofollow 属性的遵守、目录结构镜像、各种深度和文件类型过滤等。如果你的需求涉及极其复杂的网站抓取或离线镜像,手动用PowerShell脚本模拟可能非常耗时且容易出错。
  2. 特定的FTP功能: wget 对FTP协议的支持可能比 Invoke-WebRequest 更全面和健壮,尤其是在处理旧版FTP服务器或特定FTP操作时。
  3. 跨平台脚本的一致性: 如果你的自动化脚本需要在Windows和Linux/macOS环境之间无缝切换,并且希望保持命令的一致性,那么安装并使用真正的 wget 可能更方便。
  4. 极度内存敏感的场景: 尽管 Invoke-WebRequest 性能良好,但在处理超大文件或极大量小文件时,其在PowerShell内存中的对象创建和管理可能会带来额外的开销。wget 作为C语言编写的轻量级命令行工具,在某些极端性能场景下可能更具优势。
  5. 习惯与偏好: 对于长期使用Linux并熟悉 wget 的用户来说,直接使用他们熟悉的工具可能效率更高。

4.1 如何在 PowerShell 中调用真正的 Wget

如果你的系统中已经通过Chocolatey或Scoop安装了真正的 wget 并将其路径添加到了环境变量中,那么可以直接在PowerShell中调用它。

示例:调用外部 wget.exe

“`powershell

检查 wget.exe 是否在 PATH 中

if (Get-Command wget.exe -ErrorAction SilentlyContinue) {
Write-Host “发现真正的 wget.exe。”

$downloadUrl = "https://speed.hetzner.de/100MB.bin"
$outputPath = "C:\Temp\wget_100MB.bin"

# 使用 & 操作符明确执行外部命令
& wget.exe $downloadUrl -O $outputPath --continue --limit-rate=500k

if ($LASTEXITCODE -eq 0) {
    Write-Host "真正的 wget 下载成功!" -ForegroundColor Green
} else {
    Write-Error "真正的 wget 下载失败,退出码:$LASTEXITCODE"
}

}
else {
Write-Warning “未找到真正的 wget.exe。请通过 Chocolatey (choco install wget) 或 Scoop (scoop install wget) 安装。”
}
“`

  • &: 调用操作符,用于执行外部命令或脚本块。
  • $LASTEXITCODE: 捕获外部程序(如 wget.exe)的退出码。0通常表示成功。
  • wget.exe 的参数:wget 有自己一套丰富的命令行参数,例如 -O (输出文件), --continue (续传), --limit-rate (限速) 等。

重要提示: 当同时存在PowerShell别名 wget 和外部可执行文件 wget.exe 时,PowerShell的命令解析器会优先使用别名。所以,直接输入 wget 仍然会执行 Invoke-WebRequest。要强制执行外部程序,要么使用其完整路径(C:\Program Files\Wget\wget.exe),要么使用 & wget.exe (如果它在PATH中)。

V. 安全与最佳实践

在使用 Invoke-WebRequestwget 进行网络交互时,安全性和最佳实践至关重要。

  1. 验证来源: 永远只从信任的来源下载文件或获取数据。对不熟悉的URL保持警惕,以防恶意软件或钓鱼攻击。
  2. 错误处理: 使用 try...catch 块来捕获网络请求中可能发生的错误(如网络连接中断、DNS解析失败、HTTP错误码),并提供有意义的错误信息。设置 $ErrorActionPreference = 'Stop' 或在Cmdlet上使用 -ErrorAction Stop 可以强制错误终止脚本,确保错误不会被悄悄忽略。
  3. 输入验证: 如果你的脚本接受用户输入的URL或其他参数,务必进行严格的输入验证,防止命令注入或URL欺骗。
  4. 凭据安全: 避免在脚本中明文存储敏感信息(如API密钥、用户名密码)。使用 Get-Credential 动态获取凭据,或使用 ConvertTo-SecureStringConvertFrom-SecureString 安全地存储加密凭据。对于API令牌,考虑使用环境变量或安全存储机制。
  5. 最小权限原则: 运行脚本的用户或账户应只拥有完成任务所需的最小权限。
  6. 日志记录: 对于自动化脚本,记录关键操作(如开始下载、下载完成、错误信息)到日志文件,以便后续审计和故障排除。
  7. 性能考量:
    • 对于大量请求,使用 -UseBasicParsing 提高性能。
    • 在循环中频繁请求时,考虑使用 Start-Sleep 限制请求频率,避免对目标服务器造成DDoS效果,同时防止你的IP被封禁。
    • 对于非常大的文件下载,可以考虑分块下载,或者评估是否需要真正的 wget
  8. 遵守 robots.txt 如果你的脚本是爬取网站内容,务必检查并遵守网站的 robots.txt 文件,尊重网站所有者的意愿,避免对服务器造成不必要的负担。Invoke-WebRequest 不会自动遵守 robots.txt,这需要你手动实现检查逻辑。
  9. 证书验证: 默认情况下,Invoke-WebRequest 会验证SSL/TLS证书。不要轻易使用 -SkipCertificateCheck 或类似参数,除非你完全了解风险。

VI. 总结

PowerShell 的 Invoke-WebRequest Cmdlet 是一个极其强大的工具,它在PowerShell环境中完美地扮演了 wget 的角色,甚至在与Web API交互方面提供了更丰富的功能。虽然它没有直接提供 wget 的所有特性(如内置的递归下载),但PowerShell的脚本能力完全可以弥补这些不足,让你构建出满足各种复杂需求的定制化Web下载和交互解决方案。

理解 wget 别名与 Invoke-WebRequest 的真正关系是掌握在PowerShell中进行Web操作的第一步。通过熟练运用 Invoke-WebRequest 的各种参数,结合PowerShell的管道、对象处理和脚本编写能力,你将能够高效、安全、灵活地完成从简单的文件下载到复杂的Web爬取和API自动化等一切任务。而当遇到 Invoke-WebRequest 确实力所不及的特定场景时,集成真正的 GNU Wget 也是一个可行的备选方案。

掌握 Invoke-WebRequest,就掌握了PowerShell世界中与互联网无缝连接的关键钥匙。现在,是时候打开你的PowerShell,开始你的Web探索之旅了!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部