Командлет invoke webrequest в конвейере команд в позиции 1 укажите значения для следующих параметров
Парсим сайты и веб-страницы с помощью Powershell / Invoke-WebRequest / getElementsByTagName и боремся с производительностью
Автор: Super User. Категория: ИТ.
Иногда бывает нужно отпарсить какой-нибудь сайт или большую веб-страницу. Google предлагает множество программ и целых сложных комплексов для решения этой задачи, но я хочу показать, как это достаточно просто можно делать в Powershell.
В Powershell есть специальный коммандлет Invoke-WebRequest, который собственно и разбирает HTML-страницу на тэги и содержимое. На выходе этот коммандлет выдает объект страницы с полем ParsedHtml. К этому полю можно применять методы по выборке нужных данных.
Допустим, что вам нужно выбрать все ссылки на странице. Вот как это работает.
Для сложных сайтов строим более сложные фильтры:
Производительность Invoke-WebRequest и getElementsByTagName
Я не знаю, в чем причина такого поведения. Погуглив, я вышел на это обсуждение: http://stackoverflow.com/questions/14202054/why-is-this-powershell-code-invoke-webrequest-getelementsbytagname-so-incred. Из текста я понял, что getElementsByTagName дает нам некоторое количество COM-объектов, и мы отдаем эти COM-объекты в другой коммандлет. Это ужасно медленный процесс, который отнимает до 86% всего времени.
У этой проблемы я пока нашел только одно решение: пользуйтесь 32-битным Powershell в таких случаях. Не знаю почему, но это действительно дает результат.
Invoke-WebRequest: Обработка содержимого веб-страниц и HTML сайтов в Powershell
В PowerShell версии 3.0 появилась возможность напрямую обращаться и работать с HTML веб-страницам в Интернете. Для этого был разработан специальный командлет Invoke-WebRequest. Данный командлет позволяет реализовать множество сценариев: начиная от возможности скачать/ загрузить файл с/на любого веб-сайта по HTTP/ HTTPS/ FTP, заканчивая возможностями парсинга HTML страниц, мониторинга состояния веб серверов, заполнения и отправкой веб-форм. В целом, новый командлет предоставляет все необходимые методы для навигации по DOM дереву HTML документа. В этой статье мы разберём базовые примеры работы с командлетом PowerShell Invoke-WebRequest.
Использование командлета Invoke-WebRequest
Командлет Invoke-WebRequest (псевдоним wget) может отправлять и получать HTTP, HTTPS и FTP запросы, обрабатывать возвращаемый сервером ответ. Полученный ответ представляет собой набор коллекции форм, ссылок, изображений и других важных элементов HTML документа.
Попробуем выполнить следующую команду:
Как вы видите, возвращенный ответ представляет собой не простой HTML код страницы. Вы видите различные свойства web-документа. Командлет Invoke-WebRequest, как и большинство других командлетов PowerShell оперирует объектами. Invoke-WebRequest возвращает объект типа HtmlWebResponseObject. Посмотрим все свойства данного объекта:
Чтобы получить сырой HTML код веб страницы, который содержится в данном объекте, выполните:
Вы можете вернуть HTML код вместе с HTTP заголовками, которые вернул веб сервер:
Вы можете проверить только код ответа веб-сервера и HTTP заголовки HTML страницы:
Как вы видите, веб сервер вернул ответ 200, т.е. запрос выполнен успешно и веб сервер доступен и работает корректно.
Получаем список всех HTML ссылок на странице
Чтобы получить и сам текст ссылки (содержится в элементе InnerText), можно воспользоваться такой конструкцией:
$HttpContent.Links | fl innerText, href
Можно выбрать только ссылки с определенным CSS классом:
Или определенным текстом в url:
Парсинг HTML страниц с помощью Powershell
Командлет Invoke-WebRequest позволяет довольно быстро и удобно парсить содержимое любых веб-страниц. При обработке HTML страницы из ее содержимого формируются коллекции ссылок (links), веб-форм (forms), изображений (images), скриптов (scripts) и т.д.
С помощью Powershell получим содержимое главной страницы нашего сайта:
$Img = Invoke-WebRequest “https://winitpro.ru/”
Затем выведем список всех изображений на данной странице:
Сформируем коллекцию из полных url путей к используемым изображениям:
Инициализируем новый экземпляр класса WebClient:
$wc = New-Object System.Net.WebClient
И скачаем все изображения со страницы (с оригинальными именами) в каталог c:\tools\:

Как скачать файл по HTTP с помощью PowerShell
Invoke-WebRequest может работать как аналог Wget или cURL для Windows, позволяя скачать с веб-страницы или ftp сайта нужный файл или файлы. Допустим, нам нужно с помощью PowerShell скачать по HTTP некий файл (в нашем примере дистрибутив Mozilla Firefox). Выполним такую команду:
В результате выполнения командлета с указанного URL адреса будет скачан файл и сохранен в каталоге c:\tools\ под именем firefox setup 32.0.3.exe. Если нужно скачать файл с FTP сайта, просто замените http: // на ftp: //.
Таким образом вы с легкостью можете на определенной веб-странице найти все ссылки, попадающие под конкретные критерии (класс ссылки, разрешение в имени файла, url адрес), и скачать файлы по полученным ссылкам. Например, имеется некий сайт с кучей ссылок на PDF документы. Ваша задача скачать все эти файлы на ваш компьютер. Костяк PowerShell скрипта для массовой скачки файлов может выглядеть так:
В результате выполнения скрипта в целевом каталоге будут загружены все pdf файлы со страницы. Каждый файл сохраняется под произвольным именем.
Заполнение и отправка веб-форм на Powershell
Многие веб-сервисы для работы требуют ввода различных данных в HTML формы. С помощью Invoke-WebRequest можно получить доступ к любой HTML-форме, заполнить необходимые поля и передать заполненную форму обратно на сервер. В этом примере мы покажем, как с помощью Powershell авторизоваться в почтовом ящике популярного российского сервиса mail.ru через его стандартную веб форму.
С помощью следующей конструкции сохраним информацию о куках (Cookies) подключения в отдельной сессионной переменной:
Следующей командой отобразим список заполняемых полей в HTML форме авторизации (форма называется LoginExternal):
Присвоим нужные значения всем полям:
Чтобы передать заполненную форму на веб сервер, вызовем атрибут HTML-формы action.
Недостатки командлета Invoke-WebRequest
Одним из существенных недостатком командлета Invoke-WebRequest является довольно низкая скорость работы. При загрузке файла HTTP поток целиком буферизируется в память, и только после окончания полной загрузки сохраняется на диск. Таким образом, при закачке больших файлов можно столкнутся с нехваткой памяти.
Другая проблема – командлет Invoke-WebRequest тесно связан с Internet Explorer. Например, в редакциях Windows Server Core, в которых IE не установлен, командлет Invoke-WebRequest использовать нельзя.
Если на HTTP сайте используется самоподписанный сертификат, то командлет Invoke-WebRequest отказывается получать данные с него. Чтобы игнорировать некорректный SSL сертификат, используйте следующий код:
Скачиваем и распаковываем файл из Интернета при помощи Powershell
Для загрузки файлов из Интернета в Powershell предусмотрен метод DownloadFile, которому нужно передать два аргумента — что скачиваем и куда.
Скачать по HTTP или HTTPS
DownloadFile
* в данном примере мы скачаем файл http://download_ahyware/file1.zip и разместим его на компьютере по пути C:\Downloads\file1.zip.
Invoke-WebRequest
Данный командлет предназначен для загрузки содержимого html документа. Также с его помощью можно скачивать файлы:
* где URI — путь-источник (что скачиваем); outfile — путь-назначение (куда скачиваем).
Для удобства можно использовать команду wget — по сути, это алиас на Invoke-WebRequest.
Скачать с FTP
Принцип скачивания файлов с FTP-сервера такой же, за исключением того, что сначала необходима авторизация.
$download_url = “ftp://download_ahyware/file1.zip”
$local_path = “C:\Downloads\file1.zip”
$user = “myFtpUser”
$pass = “myPassword123”
* в данном скрипте мы авторизовываемся на FTP-сервере с учетной записью myFtpUser и паролем myPassword123. После, как в примере выше, мы скачали файл в папку C:\Downloads.
Распаковываем архив
Чаще всего, файлы в сети Интернет хранятся в сжатом виде. Поэтому, после загрузки файла есть необходимость его распаковать. Для этого в Powershell можно использовать следующую команду:
Однако, не все версии Powershell поддерживают командлет Expand-Archive. В таком случае можно вызвать стороннее приложение, например 7-zip. Пример скрипта:
Возможные ошибки
Исключение при вызове downloadfile с 2 аргументами исключение во время запроса webclient
Причина: как правило, неправильно указан путь для загрузки или отсутствие прав на папку, в которую должно идти скачивание.
Решение: проверяем путь или пробуем указать другой.
Как работать с Invoke-WebRequest в Powershell и алиасом Wget создавая запросы HTTP и HTTPS
Навигация по посту
Создание запроса HTTP и HTTPS в Powershell
После выполнения простого запроса мы увидим полученную информацию:
Если вы имеете прямую ссылку на файл и хотите его скачать нужно дополнительно указать ключ OutFile:
Так же можно скачивать любой файл формата ZIP,MP4 и так далее.
Доступ к свойствам
Почти любая команда в Powershell по умолчанию выводит меньше информации чем имеет. Что бы вывести все можно использовать следующий подход:
Я не рекомендую использовать подход выше, так как в этом случае вывод будет сложно читать. Мы можем посмотреть какие свойства хранит объект для следующего анализа:
И таким образом выводить данные:
Само содержимое веб документа может быть доступно по двум свойствам:
Использование заголовков
Выполнив запрос к какому-то сайту может оказаться, что полученные данные не соответствуют тому что мы видим на сайте. Причин, обычно, у этого две:
Заголовки указываются в формате хэш-таблиц. Для примера так будет выглядеть запрос с указанными заголовками:
Если вы будете испытывать сложности с составлением заголовков, то можно включить отладчик на F12 и открыть закладку Networks и скопировать их. Они находятся в выделенной части на скриншоте:
Кстати через это же меню в Chrome можно получить уже сформировавшийся запрос с командой нажав на нужный элемент и выбрав “Copy as Powershell”:
Команда получится достаточно длинной, но это поможет избавиться от долгих поисков нужных заголовков:
Вы можете увидеть вторую кнопку “Copy all as Powershell”, которая сформирует объект типа “[object Promise]”, но информацию как работать с этим я не нашел.
Такой способ не поможет пройти аутентификацию на сайте так как в Chrome Cookies устанавливаются в параметр Headers, но судя по документации они должны передаваться через параметр WebSession.
Cookies
Для входа на сайт, где используется аутентификация, понадобится использовать Cookies, которые можно увидеть на скриншоте выше. Cookies вводятся отдельно от заголовков и их можно указать двумя путями.
Во время выполнения аутентификации через Poweshell мы можем указать переменную куда будут сохранены данные, а затем передать ее для следующего запроса. В примере ниже такая переменная называется SavedSession:
Как вы можете увидеть в этой переменной хранятся и заголовки.
Теперь мы должны добавить в поле Cookies данные и передать через командлет, я использовал алиас wget:
Cookie, которые мы передали соответствуют Password=123.
Работа с формами и загрузка
Когда мы заполняем какую-то форму на сайте или выполняем загрузку мы чаще используем метод POST, а не GET, который стоит по умолчанию. Метод можно увидеть в коде или в описании документации, если используете приложения или API:
Чаще всего используются методы описанные выше, но в Powershell доступно больше:
Ключ, в котором используются эти параметры так и называется Method:
Для заполнения форм, в версии Powershell 6 +, можно использовать следующий подход:
Кодировка
Если вы используете данные, которые отличаются от латиницы вы можете столкнуться с проблемой кодировок. Дело в том, что следующие данные будут отосланы не в UTF-8, хоть это и будет указано:
Если вы не хотите испытывать сложностей с этим командлетом я бы советовал использовать разные ключи для заголовков если у вас есть такие данные. Если указать кодировку в параметре ContentType, то все сработает нормально:
Вы так же можете посылать данные таким образом:
Аутентификация
Посмотрим на три способа аутентификации используя: базовую (Basic), с использованием сертификата, NTLM и Kerberos.
Базовая аутентификация
Обычно сервер передает дополнительные данные для аутентификации, но в примере ниже это выполнение без их использования:
Такой способ не рекомендуется использовать, так как данные будут доступны в истории в незашифрованном виде. Вы так же можете использовать ключ Credential, который создаст аналогичный заголовок за вас.
Используя сертификат
Если у вас установлен сертификат, то вы можете его использовать только указав отпечаток (Thumbprint):
NTLM и Kerberos
Этот ключ не будет работать вместе с аутентификацией типа Basic.
Парсинг сайтов
Парсинг, то есть возможность собирать информацию с сайтов, в Powershell реализована достаточно просто. Если мы посмотрим какие свойства имеет объект команды Invoke-WebRequest, сможем увидеть теги:
Картинки
Для примера в HTML картинки хранятся в таком виде:
То есть все картинки имеют тег IMG и SRC. В Powershell часть тегов уже собрана в отдельные свойства и так мы получим список всех картинок:
Мы видим только ссылки на картинки, а значит нам нужно выполнить еще по запросу для каждой картинки. К тому же я хочу сохранить существующие имена для каждой картинки, так как у них разные форматы (PNG, JPG). Это будет выглядеть примерно так:
Обратите внимание, что если у вас не создана директория куда будут сохранятся картинки, появится ошибка. Iwr не создает директории.
Парсинг других тегов
Парсинг остальной части сайта мало чем отличается от примера с картинками. Так как Images и Links выведены как основные свойства, то поиск, например, заголовков должен делаться через свойство AllElements. Для примера так я найду все теги h2:
Кроме поиска по тегам часто приходится искать по классам. Например так я найду все заголовки h2, но используя класс:
По умолчанию мы не можем работать с JSON объектами. Они будут восприниматься как строки пока мы не конвертируем объект в PSCustomObject. Это делается так:
После этого мы можем обращаться к свойствам:
После изменения мы возможно захотим отправить объект. Для этого мы конвертируем из PSCutomObject в JSON:
Как получать и изменять свойства файлов в Powershell
Работа с классом Net.WebClient
При работе в Powershell доступен еще один метод для доступа к веб страницам и скачивания файлов Net.WebClient. Этот класс имеет методы все из которых мы можем увидеть так:
Скачивание
Метод DownloadString() позволяет скачивать информацию. Так, например, мы получим содержимое главной страницы сайта:
Как вы видите есть проблема с кодировкой. Ее можно исправить так:
Для скачивания файлов используем метод DownloadFile():
Вадим Стеркин
Я изучаю PowerShell по мере возникновения задач. Если надо что-то автоматизировать, я смотрю, как соотносится реализация в PowerShell с моими знаниями. И держу в уме, что мне есть к кому обратиться, если знаний не хватит 🙂
[+] Сегодня в программе
История вопроса и задача
Есть замечательный ресурс smartfiction.ru, публикующий по будням короткие рассказы. Для меня главная ценность в их автоматической доставке на Kindle. Работает это очень просто: на сайте даете почтовый адрес Kindle, а в настройках Amazon добавляете в разрешенные адрес рассылки, после чего книга скачивает отправленные рассказы автоматически.
Точнее – работало, потому что в какой-то момент рассказы перестали приходить. Сервис подписки на другом домене, а он недоступен, как выяснилось. Я написал письмо на адрес обратной связи, но оно осталось без ответа. Однако рассказы на сайте публикуются, и под каждым есть ссылка для загрузки в mobi.
Поэтому задача свелась к тому, чтобы автоматизировать закачку этих файлов. Первая мысль была расчехлить консольный wget, но тут же возникла ассоциация с PowerShell. Ведь wget – это псевдоним командлета Invoke-WebRequest.
Получение содержимого страницы и выбор нужных ссылок
Invoke-WebRequest умеет отправлять запросы HTTP/HTTPS/FTP, парсить ответ и возвращать наборы элементов HTML – ссылки, формы, изображения и т.д. Попробуйте любой сайт так:
Для каждой ссылки легко выводится набор атрибутов.
Выше показана только первая ссылка страницы, но нужна конкретная. В Chrome щелкните правой кнопкой мыши по ссылке – «Посмотреть код элемента» и сопоставьте с выводом PowerShell. Интересующие атрибуты – это innerText (текст ссылки) и href (URL).
Передав запрос по конвейеру командлету Where-Object, можно получить список всех ссылок на книги в формате mobi (ниже показана только первая).
Загрузка файлов по ссылкам
Информации выше уже достаточно для первой версии скрипта.
Первые три строки вы уже видели, поэтому разберу четвертую. Список ссылок по конвейеру передается командлету ForEach-Object (псевдоним %). Он выполняет запрос для каждой ссылки ($_.href) и сохраняет ответ сервера в файл со случайным именем и расширением mobi. Таким образом, со страницы скачиваются все книги в формате mobi.
Случайное имя с числовым значением от 0 до 10001 генерирует командлет Get-Random. Это костыль, потому что имя файла в атрибутах ссылки не содержится. Но до него можно добраться!
Парсинг заголовков
В ответ на запрос о ссылке на книгу сервер выдает такую картину.
Имя файла тут есть: filename=”hot_and_cold_blood.mobi”. Но я сразу приуныл, потому что извлечь его можно только регулярным выражением, которые я исторически не осилил. Однако меня быстро утешил в Телеграме Вадимс Поданс 🙂
Регулярное выражение берет из ответа заголовки (Headers) и вытаскивает имя файла из секции content-deposition.
В результате получается такой скрипт.
Экономия на запросах
Код выше вполне рабочий, но перфекциониста не устроит. Каждая ссылка на книгу запрашивается с сервера дважды: сначала для получения имени файла, затем для загрузки. Убрать лишний запрос из цикла несложно – достаточно присвоить ему переменную. Но я не мог сообразить, как это дело вывести в файл.
Вадимс подсказал командлет Set-Content. В данном случае он сохраняет содержимое ответа в файл с именем, полученным с помощью регулярного выражения.
Иногда серверы блокируют слишком частые запросы с одного хоста. Поэтому в качестве последнего штриха я добавил в цикл трехсекундную паузу командлетом Start-Sleep.
Если хотите потренироваться, вот тут масса бесплатных книг Microsoft. А у вас возникают подобные задачи? Как решаете?
Метки: PowerShell, скрипты Информация в статье применима к Windows 7 и новее
Об авторе
Вас также может заинтересовать:
Я в Telegram
Подпишитесь на канал и читайте интересные записи чаще! Есть вопросы? Задайте их в чате.
комментариев 13

Сначала хотел написать, что при помощи Bash и curl/wget получилось бы проще, потом смоделировал скрипт в голове и понял, что нет, не проще.
Думаю, что это можно было сделать с помощью MS Flow (аналог IFTTT, но более мощный) без программирования. Но у них ограничение на количество бесплатных запусков — 75. Хотя в данном случае — этого хватит. 🙂
MS Flow умеет парсить веб-страницы?
nett00n:
Сначала хотел написать, что при помощи Bash и curl/wget получилось бы проще, потом смоделировал скрипт в голове и понял, что нет, не проще.
Лучше на другом сайте тренироваться, а то сломаем хороший сайт окончательно.
Вот тут куча бесплатных книг Microsoft.
Публикуется не более одного рассказа в день. Если скрипт запускать ежедневно, и качать только последний, то Start-Sleep не нужен. Иначе нужно вести учет скачанного или проверять существование файла, для исключения повторной закачки. Для перфекциониста это как серпом по :).
И зачем запрашивать заголовки, если имя файла можно получить сразу из url-а? А так как читалка берет инфу из тегов, и ей имя по-барабану, то можно и суррогатным обойтись. Только не случайное число а дату.
Lecron: Иначе нужно вести учет скачанного или проверять существование файла, для исключения повторной закачки.
Я, конечно, думал об этом, но решил не уводить скрипт в сторону, потому что этот момент все-таки специфичен для мой конкретной задачи.
Lecron: И зачем запрашивать заголовки, если имя файла можно получить сразу из url-а? А так как читалка берет инфу из тегов, и ей имя по-барабану, то можно и суррогатным обойтись.
Цель поста — показать приемы на конкретной задаче. В данном случае имя файла не имеет значения, конечно. Но в каком-то другом — скорее да, чем нет.
Можно парсить URL, но парсинг заголовков полезнее. Например, для задачи, где в URL — linkID=45105045.
Lecron: Только не случайное число а дату.
🙂 Дата была в исходном варианте скрипта, но я уже разбирал Get-Date в блоге, а Get-Random — нет.
Vadim Sterkin: Я, конечно, думал об этом, но решил не уводить скрипт в сторону, потому что этот момент все-таки специфичен для мой конкретной задачи.
ОК. Тогда строго по теме. Доступ не только к ссылке, но и извлечение информации из произвольного селектора (div). Итерация по блокам, например книгам «div.post» и обращение к селекторам только внутри него. Пара примеров доступа по классу, id, произвольному свойству, css query.
Это не выйдет за рамки простого примера, но и не окажется настолько примитивным. Понимаю, что есть книги, но эта информация скорее нужна не для обучения, а для оценки перспектив. Прикинуть, насколько стоит использовать штатный инструмент и когда станет пора расчехлять нечто по-мощнее.
Я написал на примере практической задачи, причем вполне распространенной (см. ссылку в конце статьи). Примитивно? Нет, но просто.
А для ваших хотелок у меня практических задач нет. И даже если будут, вряд ли стану писать для 2.5 человек.
Хотите оценить перспективы — попробуйте сами сделать то, что вы попросили.
Такой же подход, изучаю по мере выполнения задач. Накатал скриптов, начиная от задач, связанных с музыкой — рип, конвертация, прослушивание, в основном, как скриптовый фронт-енд к lame, vorbis, упорядочивания встроенных тэгов и т.д. Для загрузки rss-лент, анекдотов, цитат и прочей погоды. Ну и конечно для выполнения повседневных задач, связанных с администрированием личного ноутбука.



























