🐥

PowerShell:ファイルの一括ダウンロードとリネーム

2022/06/02に公開

とあるサイトからファイルを一括でダウンロードして、ファイルのリネームをしたかったので作ってみた。

概要

大量のファイルがアップされているサイトで、リンクテキストとダウンロードされるファイル名が異なるので、リンクテキストにリネームするもの。

処理の流れ

  1. ダウンロードするサイトを指定
  2. リンクを取得(今回はPDF,XLSX,ZIPに絞った)
  3. ファイルを一個ずつダウンロード
  4. ファイルをリンクテキストにリネーム

ハマったポイント

[Invoke-WebRequest]のLinksでリンクテキストを取得しようとしたが、文字化けしてしまう。
[Invoke-WebRequest]は、ISO-8859-1にエンコードされてしまうので、サイトの文字コードを判断したうえで、処理が必要だった。

サイトの文字コード判断も綺麗ではないがサイトのhtmlをダウンロードし、そこからMETAタグ内のcharsetがUTF-8か検索してやってた。

もっと綺麗なやり方があれば教えてください。。。

完成したスクリプト

FileDL_RN.ps1
#-----------------------------------------------------------------------------
# URL形式のチェック用Function作成
function IsAbsoluteUrl([string] $url){
    return [System.Uri]::IsWellFormedUriString($url, [System.UriKind]::Absolute);
}
#-----------------------------------------------------------------------------
# 禁止文字の削除用Function作成
Function Remove-InvalidFileNameChars {
	param(
	  [Parameter(Mandatory=$true,
		Position=0,
		ValueFromPipeline=$true,
		ValueFromPipelineByPropertyName=$true)]
	  [String]$Name
	)

	$invalidChars = [IO.Path]::GetInvalidFileNameChars() -join ''
	$re = "[{0}]" -f [RegEx]::Escape($invalidChars)
	return ($Name -replace $re)
  }
#-----------------------------------------------------------------------------

# 一括ダウンロード&リネーム処理開始
# URLの指定
$TargetUrl = Read-Host "(ファイルを取得したいサイトのURLを入力してください。)"

if (-not (IsAbsoluteUrl($TargetUrl))) {
    Write-Output "$TargetUrl は正しい形式のURLではありません。"
    exit;
}

$uri = New-Object System.Uri ($TargetUrl)

# SSL/TLSのエラーが発生する場合、以下のコメントアウトを解除して、再度実行してみてください。
# [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# フォルダ選択ダイアログ表示
Add-Type -AssemblyName System.Windows.Forms
$FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -Property @{
    RootFolder = "Desktop"
    Description = "ファイルを保存するフォルダを選択してください"
}

# フォルダ選択の有無を判定
if($FolderBrowser.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK){
    Write-Output "フォルダ選択がキャンセルされました。"
    exit;
}

# 選択したフォルダパスの格納
$SavePath = $FolderBrowser.SelectedPath

# 文字コード判定(2022/09/02追記)
# sourceファイルの取得
Invoke-WebRequest -Uri $uri -UseBasicParsing -OutFile "$SavePath\tmp.txt"
# METAタグ内のcharsetがUTF-8か検索
$Encoding_Flag = Select-String "meta.*charset.*UTF-8" "$SavePath\tmp.txt" -Quiet
# tmpファイル削除
Remove-Item "$SavePath\tmp.txt"

try{
    # Webページから、ファイルへのリンクを取得
    # 指定されたuriを取得
    $response = Invoke-WebRequest -Uri $uri -UseBasicParsing

    # aタグ(outerHTML)とリンク(href)を取得
    # お好みで拡張子を変更

    # PDFの場合
    # $links = $response.Links | Where-Object { $_.href -like "*.pdf" } | Select-Object outerHTML, href

    # PDF,XLSXの場合
    # $links = $response.Links | Where-Object { $_.href -like "*.pdf" -or $_.href -like "*.xlsx" } | Select-Object outerHTML, href
   
    # PDF,XLSX,ZIPの場合(2022/09/01追記)
    $links = $response.Links | Where-Object { $_.href -like "*.pdf" -or $_.href -like "*.xlsx" -or $_.href -like "*.zip" } | Select-Object outerHTML, href


    if ($links.Count -eq 0) {
        Write-Output ダウンロード対象のファイルがありません。
        return;
    }

    # 個々のファイルダウンロード
    foreach ($link in $links) {
        # ダウンロード用のファイル名抽出
        $FileName = Split-Path $link.href -Leaf

        # 保存先パス作成(フォルダ + ファイル名)
        $OutFilePath = Join-Path $SavePath $FileName

        if (IsAbsoluteUrl($link.href)) {
            # 絶対パスの場合:そのまま利用する
            $DownloadUrl = New-Object System.Uri ($link.href)
        } else {
            # DL対象ファイルのURL取得(Uriの機能で、絶対パスと相対パスをくっつける)
            $DownloadUrl = New-Object System.Uri ($uri, $link.href)
        }

        # ファイルのダウンロード
        Invoke-WebRequest -Uri $DownloadUrl.AbsoluteUri -OutFile $OutFilePath

        # ファイル名の変更
        # byte列変換_文字化け対策(2022/09/02追記)
        $bytes = [System.Text.Encoding]::GetEncoding('ISO-8859-1').GetBytes($link.outerHTML)

        # バイト型→文字コードを指定して変換(UTF-8とShift_JISのみ対応)
        if  ($Encoding_Flag){
            # UTF-8
            $a_Tag = [System.Text.Encoding]::GetEncoding('UTF-8').GetString($bytes)
        } else {
            # "Shift_JIS"
            $a_Tag = [System.Text.Encoding]::GetEncoding('Shift_JIS').GetString($bytes)
        }

        # aタグからファイル名を抽出
        $NewFileText = $a_Tag -replace (".*<a.*?>(.*?)<(.|\n)*", '$1')

        if ($NewFileText -ne ""){
            # 禁止・不要文字削除
            $ReName = Remove-InvalidFileNameChars "$NewFileText" | ForEach-Object {$_.replace("&nbsp;", "")}

            # 置換前のファイルの拡張子を取得
            $Str_Extension = [System.IO.Path]::GetExtension($FileName)

            # リネーム
            Rename-Item  $OutFilePath -NewName "$ReName$Str_Extension"
        }
    }

}catch [System.Net.WebException]{
    $statusCode = $_.Exception.Response.StatusCode;

    Write-Output エラー発生のため、処理を中断します。

    Write-Output "エラーステータス: $statusCode"
}

参考

とても参考にさせていただきました。
https://qiita.com/hiron2225/items/eb5cbf18fed27b96a622
https://www.hi-matic.org/diary/?20190105

Discussion