Не так давно мне понадобилось скачать драйверы и утилиты для множества моделей компьютеров HP. Учитывая что обычно для каждой модели приводится несколько десятков драйверов, скачивать их вручную и раскладывать по папкам мне показалось слишком долго и нудно. Кроме того я не хотел устанавливать какие либо менеджеры закачек которые помогли бы просто выдрать все ссылки со страницы, да они и не смогли бы правильно разложить файлы по категориям… Поэтому я решил написать простенький сценарий, который бы разбирал html страницы с драйверами для модели, понимал бы к какой категории относится драйвер или утилита, скачивал бы их, и раскладывал бы в соответствующие папки (при необходимости создавая эти папки самостоятельно).
На вход сценарий принимает адрес страницы с которой необходимо скачать драйверы, например такой http://h20000.www2.hp.com/bizsupport/TechSupport/SoftwareIndex.jsp?lang=ru&cc=ru&prodNameId=462858&prodTypeId=321957&prodSeriesId=462857&swLang=33&taskId=135&swEnvOID=1093
Если вы приглядитесь к кнопкам “Загрузить” на этой странице, то вы увидите что в них содержится ftp ссылка на файл, так что для закачки нам не подойдет простой System.Net.WebClient или BitsTransfer а понадобится System.Net.FtpWebRequest. Он несколько сложнее в использовании, но я нашел хороший пример функции для скачивания файлов по FTP здесь, и включил его в тело сценария. Разумеется можно просто поместить его в свой профиль, но я посчитал что лучше сделать сценарий самодостаточным и независимым от окружения.
Кроме страницы для скачивания, сценарию можно указать еще параметры Destination (папка куда скачивать файлы, по умолчанию текущий каталог) и ключ Plain (если он указан, то файлы сваливаются в одну кучу, без раскладывания по папкам).
Ну и наконец Get-HPDrivers.ps1:
param ($Url, $Destination=$Pwd, [switch]$Plain) #Функция Get-FTPFile взята отсюда - http://powershell.com/cs/media/p/804.aspx function Get-FTPFile ($Source,$Target,$UserName,$Password) { # Create a FTPWebRequest object to handle the connection to the ftp server $ftprequest = [System.Net.FtpWebRequest]::create($Source) # set the request's network credentials for an authenticated connection $ftprequest.Credentials = New-Object System.Net.NetworkCredential($username,$password) $ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile $ftprequest.UseBinary = $true $ftprequest.KeepAlive = $false # send the ftp request to the server $ftpresponse = $ftprequest.GetResponse() # get a download stream from the server response $responsestream = $ftpresponse.GetResponseStream() # create the target file on the local system and the download buffer $targetfile = New-Object IO.FileStream ($Target,[IO.FileMode]::Create) [byte[]]$readbuffer = New-Object byte[] 1024 # loop through the download stream and send the data to the target file do{ $readlength = $responsestream.Read($readbuffer,0,1024) $targetfile.Write($readbuffer,0,$readlength) } while ($readlength -ne 0) $targetfile.close() } #Выводим на экран текущее выполняемое действие: Write-Progress "Receiving metadata" "Downloading" #Создаем объект WebClient для выкачивания текста страницы $wc = new-object System.Net.WebClient #Устанавливаем для него кодировку $wc.Encoding = [System.Text.Encoding]::GetEncoding("UTF-8") #Указываем что надо использовать учетные данные той учетной записи #под которой запущен сценарий $wc.UseDefaultCredentials = $true #Загружаем страницу в переменную $Page $Page = $wc.DownloadString($url) #Обновляем статус Write-Progress "Receiving metadata" "Parsing HTML" $Meta = $( #С помощью конструкции switch разбираем текст страницы используя регулярные выражения #Так как разные элементы (категория, имя, ссылка) находятся на разных строках, я использовал #переменные $Category и $Name для сохранения их значений switch -regex ($page.split("`n")) { #Это категория, устанавливаем переменную $Category '<a name="\d+"></a><b>([^<]+)</b>' {$Category = $matches[1]} #Это имя пакета, помещаем его в $Name 'SoftwareDescription.jsp[^"]+">([^<]+)' {$Name = $matches[1]} #Это уже ссылка 'targetPage=([^&]+exe)' { #Размаскировываем ссылку и помещаем в переменную $Link $Link = [system.uri]::UnescapeDataString($matches[1]) #Создаем новый объект со свойствами Name, Category и Link. New-Object PSObject -Property @{Name=$Name; Category=$Category; Link = $Link} } } ) #Страница разобрана, полученные объекты помещены в массив $Meta #Получаем имя для файла со списком распарсенного, #он будет лежать для справки в корне целевой папки $FileName = Join-Path $Destination "Index.csv" #Отображаем состояние Write-Progress "Exporting Metadata" $FileName #Экспортируем $Meta в файл $Meta | Export-Csv $FileName -Encoding UTF8 -NoTypeInformation -Delimiter "," #Для каждого объекта в массиве $Meta... foreach ($Driver in $Meta) { #Выводим статус и имя текущего драйвера/программы Write-Progress "Downloading files" $Driver.Name #Если не указан ключ $Plain то... if (!$Plain) { #Определяем папки для категории и файла, и при необходимости создаем их $CategoryFolder = Join-Path $Destination $Driver.Category if (-not (test-path $CategoryFolder)) {md $CategoryFolder | out-null} $DriverFolder = Join-Path $CategoryFolder $Driver.Name.replace("/","") if (-not (test-path $DriverFolder)) {md $DriverFolder | out-null} #Устанавливаем имя под которым будет сохранен файл в соответствующей папке $FileName = Join-Path $DriverFolder ($Driver.Link -replace '^.+/') } Else { #Устанавливаем имя под которым будет сохранен файл в корне папки $FileName = Join-Path $Destination ($Driver.Link -replace '^.+/') } #Если файл еще не существует... if (-not (test-path $FileName)) { #Вызываем функцию Get-FTPFile для его скачивания Get-FTPFile -Source $Driver.Link -Target $Filename -User "Anonymous" -Pass "anon@hp.com" } else { #Иначе выдаем предупреждение, и продолжаем работу Write-Warning "File $FileName already exists, skipping..." } }
Так как моделей у меня было много, я не стал для каждой ссылки вызывать сценарий, а просто сохранил ссылки в текстовый файл urls.txt и запустил следующую команду:
Get-Content .\urls.txt | foreach { C:\Root\Get-HPDrivers.ps1 -Url $_ }
Ну и вот как то так оно выглядит в работе
Стоит выразить отдельную благодарность Hewlett-Packard за хорошо структурированную страницу, которую можно разобрать подобным образом. Не многие поставщики способны похвастаться таким.
Недавно натолкнулся вот на этот






