Парсим сайты и веб-страницы с помощью Powershell / Invoke-WebRequest / getElementsByTagName и боремся с производительностью

Иногда бывает нужно отпарсить какой-нибудь сайт или большую веб-страницу. Google предлагает множество программ и целых сложных комплексов для решения этой задачи, но я хочу показать, как это достаточно просто можно делать в Powershell.

В Powershell есть специальный коммандлет Invoke-WebRequest, который собственно и разбирает HTML-страницу на тэги и содержимое. На выходе этот коммандлет выдает объект страницы с полем ParsedHtml. К этому полю можно применять методы по выборке нужных данных.

Допустим, что вам нужно выбрать все ссылки на странице. Вот как это работает.

$url = "http://site.com/page.html";
$page = Invoke-WebRequest $url -Method Get -DisableKeepAlive;
$elements_a = $page.ParsedHtml.getElementsByTagName('a') | ?{$_.getAttribute('itemprop') -eq "url"};

После того, как вы получите результат последней строки в $elements_a, вы должны будете обратиться к полям этого объекта и забрать нужные данные. В каждом конкретном случае выборка данных будет разная, т.к. сайты разные. Но можно взять универсальный пример - забрать Title страницы. Скорее всего, это будет работать на всех сайтах.

$title = $($page.ParsedHtml.getElementsByTagName('title')).innertext;

 Для сложных сайтов строим более сложные фильтры:

$address = $( $page.ParsedHtml.getElementsByTagName('p') | ?{$_.className -eq 'address'} ).innertext;

 

Производительность Invoke-WebRequest и getElementsByTagName

Функция getElementsByTagName в реализации Microsoft - достаточно тяжелая, поэтому может выполняться медленно. Если же вы к ней еще применяете конвейер и отправляете результаты метода в другую команду, скорость работы скрипта может быть катастрофически низкой. К примеру, последний пример может выполняться до 30 секунд (если страница достаточно большая).

Я не знаю, в чем причина такого поведения. Погуглив, я вышел на это обсуждение: http://stackoverflow.com/questions/14202054/why-is-this-powershell-code-invoke-webrequest-getelementsbytagname-so-incred. Из текста я понял, что getElementsByTagName дает нам некоторое количество COM-объектов, и мы отдаем эти COM-объекты в другой коммандлет. Это ужасно медленный процесс, который отнимает до 86% всего времени. 

У этой проблемы я пока нашел только одно решение: пользуйтесь 32-битным Powershell в таких случаях. Не знаю почему, но это действительно дает результат.

Метки: powershell (ru), скрипт

ПечатьE-mail

Добавить комментарий


Защитный код
Обновить