Парсим сайты и веб-страницы с помощью 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 в таких случаях. Не знаю почему, но это действительно дает результат.
- Просмотров: 9328