Начало: «PowerShell: парсер HTML, устаревшие методы».
Получение библиотек «HTML Agility Pack» и «AngleSharp»
Обе эти библиотеки (наборы классов) можно бесплатно получить в хранилище пакетов «www.nuget.org» в интернете:
С помощью «PackageManagement» (бывший «OneGet»)
Сначала я попытался использовать менеджер пакетов «PackageManagement» (ранее его называли «OneGet»), встроенный в программу-оболочку «Windows PowerShell», на примере библиотеки «HTML Agility Pack». Список командлетов, необходимых для работы с этим менеджером пакетов, можно просмотреть в документации на сайте компании «Microsoft».
Мне это удалось с помощью следующей команды:
> Install-Package -Name "HtmlAgilityPack" -Source "nuget.org" `
-Scope CurrentUser -SkipDependencies
Но работа с этим менеджером пакетов, на мой взгляд, — это удовольствие ниже среднего. Может потребоваться ряд дополнительных настроек и обновление некоторых модулей программы-оболочки «Windows PowerShell». Должен быть установлен модуль провайдера «NuGet», следует указать правильный URL-адрес источника пакетов и так далее. Кроме этого, как видно из команды выше, мне пришлось использовать параметр -SkipDependencies
, чтобы отказаться от получения зависимостей (пакетов, от которых зависит библиотека «HTML Agility Pack»). Без этого параметра ничего не получалось загрузить.
Параметр -Scope CurrentUser
, конечно, не обязателен. Я его добавил, потому что не хотел повышать права до администраторских. По умолчанию пакеты устанавливаются в папку Program Files
(нужны права администратора), а при указанном параметре пакеты устанавливаются в папку текущего пользователя (достаточно прав текущего пользователя).
Параметр -Source "nuget.org"
имеет в виду источник пакетов по URL-адресу https://api.nuget.org/v3/index.json
.
Идея, лежащая в основе этого менеджера пакетов, интересная (идея объединить ряд менеджеров пакетов под одной крышей), но реализована она плохо. К тому же, на сайте этого менеджера пакетов сказано, что в данный момент он не находится в разработке. С момента прекращения работы над этой программой прошло уже года три (с 2019 года). Там же сказано, что будут выпускаться только «поддерживающие» версии.
С помощью утилиты «nuget.exe»
Вот этот способ мне понравился гораздо больше. Быстро, четко, без проблем. Эту утилиту можно скачать с сайта хранилища пакетов «www.nuget.org» в разделе «Downloads». Ее текущая версия 6.2.1 (2022 год). Размер — 6,75 Мб.
Установку двух указанных выше библиотек я выполнил в текущую папку (папку проекта) с помощью следующей команды из программы-оболочки «Windows PowerShell»:
> .\nuget install htmlagilitypack
> .\nuget install anglesharp
Состав библиотек
Текущая версия библиотеки «Html Agility Pack» — 1.11.43 (от 2 июня 2022 года). Сама библиотека представляет собой файл «HtmlAgilityPack.dll». К ней есть описание входящих в нее классов, методов этих классов, свойств этих классов в файле «HtmlAgilityPack.xml». Последний можно просматривать в любом текстовом редакторе.
При загрузке из хранилища пакетов у меня на компьютере было создано такое дерево файлов и папок:
HtmlAgilityPack.1.11.43
│ .signature.p7s
│ HtmlAgilityPack.1.11.43.nupkg
└───lib
├───Net35
├───Net40
├───Net40-client
├───Net45
├───NetCore45
├───netstandard1.3
├───netstandard1.6
├───netstandard2.0
├───portable-net45+netcore45+wp8+MonoAndroid+MonoTouch
├───portable-net45+netcore45+wpa81+wp8+MonoAndroid+MonoTouch
└───uap10.0
Каждая из подпапок в папке lib
содержит свою версию библиотеки «Html Agility Pack». В принципе, я могу использовать в своем скрипте на языке PowerShell любую из этих версий. Это просто разные варианты одной и той же библиотеки, предназначенные для работы в разных реализациях платформы «.NET». Я протестировал все эти одиннадцать вариантов на простом тестовом примере и у меня каждый из этих вариантов работает без проблем (далее в статье будет практический пример). Размер библиотеки (файла «HtmlAgilityPack.dll») варьируется в пределах 130-165 Кб, что, на мой взгляд, — мизерный размер.
Представление о вариантах библиотеки, содержащихся в загружаемом пакете, можно получить на странице пакета во вкладке «Frameworks». На этой вкладке показаны вообще все возможные на текущий момент варианты, которые теоретически может реализовывать библиотека для платформы «.NET», а фактически реализованные варианты отмечены синим цветом.
Почему-то никаких зависимостей (пакетов, от которых зависит загружаемый пакет) для библиотеки «Html Agility Pack» не загрузилось, хотя на странице пакета во вкладке «Dependencies» показано, что для некоторых версий библиотеки зависимости есть.
Текущая версия библиотеки «AngleSharp» — 0.17.1 (от 2 июня 2022 года). Библиотека — это файл «AngleSharp.dll». К ней есть описание в файле «AngleSharp.xml».
При загрузке из хранилища пакетов у меня на компьютере было создано такое дерево файлов и папок:
.\
├───AngleSharp.0.17.1
│ │ .signature.p7s
│ │ AngleSharp.0.17.1.nupkg
│ │ logo.png
│ └───lib
│ ├───net461
│ ├───net472
│ └───netstandard2.0
├───System.Buffers.4.5.1
├───System.Memory.4.5.4
├───System.Numerics.Vectors.4.5.0
├───System.Runtime.CompilerServices.Unsafe.6.0.0
└───System.Text.Encoding.CodePages.6.0.0
Пять папок на одном уровне с папкой AngleSharp.0.17.1
— это пакеты-зависимости, от которых зависит библиотека «AngleSharp». Утилита «nuget.exe» загрузила их одновременно с загрузкой пакета «AngleSharp». Каждая из трех подпапок в папке lib
содержит версию библиотеки «AngleSharp» для отдельной реализации платформы «.NET». Впрочем, кажется, все эти три версии библиотеки (файл «AngleSharp.dll») идентичны, так как имеют один и тот же (с точностью до байта) размер в 862 Кб.
При работе с библиотекой «AngleSharp» не обязательно понадобятся все пять пакетов-зависимостей. Для моего скрипта пока что понадобилась только одна зависимость — System.Text.Encoding.CodePages
.
Пример кода, использующий библиотеки
Я решил для обеих библиотек использовать вариант библиотеки из папки «netstandard2.0», так как только для этой спецификации в обеих тестируемых библиотеках одновременно есть варианты.
Напомню, я пишу код примера очень простого HTML-парсера в скрипте для программы-оболочки «Windows PowerShell» версии 5.1 в операционной системе «Windows 10». Код на языке HTML для анализа получаю из файла в кодировке UTF-8 (без метки BOM) и помещаю в переменную $html
типа System.String
. Эти операции я оставляю за рамками статьи и демонстрировать их тут не буду, чтобы не увеличивать размер статьи.
Пример 1. Использование библиотеки «Html Agility Pack»:
Add-Type -Path "HtmlAgilityPack.1.11.43\lib\netstandard2.0\HtmlAgilityPack.dll"
$dom = New-Object -TypeName "HtmlAgilityPack.HtmlDocument"
$dom.LoadHtml($html)
""
foreach ($node in $dom.DocumentNode.DescendantNodes()) {
if (("#text" -eq $node.Name) -and ("" -eq $node.OuterHTML.Trim())) {
# Пустые узлы (пробелы, символы новой строки и т.п.) не выводим
} else {
$content = ""; if ("#" -eq $node.Name[0]) {
$content = ", '" + $node.OuterHTML.Trim() + "'"
}
$node.Name + $content
}
}
""
Пример 2. Использование библиотеки «AngleSharp»:
Add-Type -Path ("System.Text.Encoding.CodePages.6.0.0\lib\" +
"netstandard2.0\System.Text.Encoding.CodePages.dll")
Add-Type -Path "AngleSharp.0.17.1\lib\netstandard2.0\AngleSharp.dll"
$parser = New-Object -TypeName "AngleSharp.Html.Parser.HtmlParser"
$dom = $parser.ParseDocument($html)
""
foreach ($elem in $dom.All) {
$elem.tagName
}
""
В случае с библиотекой «AngleSharp», как видно в коде выше, понадобилось предварительно загрузить в сессию работы программы-оболочки «Windows PowerShell» одну из полученных вместе с этой библиотекой ее зависимостей — «System.Text.Encoding.CodePages.6.0.0». Без этого эта библиотека у меня не работала, скрипт выдавал ошибку.
Сам тестовый код несколько различается, но на данном этапе мне это неважно. Сейчас мне важно проверить, как подключаются библиотеки и можно ли их использовать для парсера HTML в принципе.
Результаты работы тестовых скриптов
Содержание тестового файла с кодом на языке HTML (в кодировке UTF-8 без метки BOM):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Название страницы</title>
</head>
<body>
<!-- содержимое страницы -->
<p>Текст.</p>
</body>
</html>
Результат для примера 1. Использование библиотеки «Html Agility Pack»:
<!DOCTYPE html>
html
head
meta
title
Название страницы
body
<!-- содержимое страницы -->
p
Текст.
Результат для примера 2. Использование библиотеки «AngleSharp»:
HTML
HEAD
META
TITLE
BODY
P
Выводы
Обе библиотеки можно использовать для создания парсера HTML. Я пока не готов сказать, какая из них удобнее с точки зрения функциональности. Нужно написать работающий прототип нужной мне программы, тогда станет понятнее. Я пока что решил использовать библиотеку «Html Agility Pack», так как она поменьше в размерах и не имеет зависимостей.
Комментарии (6)
wzd2002
14.08.2022 11:42$httpListener = New-Object
System.Net.HttpListener
$httpListener.Prefixes.Add("http://localhost:8080/")
$context = $httpListener.GetContext()$context.Response.StatusCode = 200
$context.Response.ContentType = 'text/HTML'
$WebContent = Get-Content -Path "C:\test.html" -Encoding UTF8
$EncodingWebContent = [Text.Encoding]::UTF8.GetBytes($WebContent)$context.Response.OutputStream.Write($EncodingWebContent , 0,
$EncodingWebContent.Length)$context.Response.Close()
$Response = Invoke-WebRequest -URI http://localhost:8080
$httpListener.Close()
ilyachalov Автор
14.08.2022 12:20Спасибо за ваш ответ. Может, дадите какие-нибудь пояснения к этому коду? Я был бы благодарен. Как я понял, вы таким образом предлагаете альтернативу использованию указанных в статье библиотек. У меня нет опыта работы с результатом, возвращаемым командлетом
Invoke-WebRequest
. Я заглянул в документацию и там сказано, чтоBeginning with PowerShell 6.0.0
Invoke-WebRequest
supports basic parsing only.Что значит «basic parsing only»? Это какие-то серьезные ограничения? Мне это важно знать. Этот командлет возвращает объект класса
BasicHtmlWebResponseObject
. В документации этого класса сказано, чтоResponse object for html content without DOM parsing
Что значит «without DOM parsing»? Накладывает ли это какие-то ограничения на работу с DOM-деревом? Я собираюсь далее анализировать объект с DOM-деревом. Насколько легко можно будет сделать обход узлов объекта, полученного от командлета
Invoke-WebRequest
? Есть ли там серьезные ограничения?
wzd2002
14.08.2022 12:39+1В коде мы просто запускаем веб сервер с локально расположенной страницей. И затем уже забираем результат через Invoke-WebRequest
К сожалению на остальную часть вопроса Я вам внятно ответить не смогу. Использовал его только для несложных админских задач.
Я скорее отвечал на ваш комментарий по поводу анализа вэбреквестом именно локального файла
ilyachalov Автор
14.08.2022 20:33Спасибо за ответ. То, что задействуется локальный веб-сервер, я понял. В данный момент для меня главное — не то, как получить объект с HTML-деревом (с этим я более-менее разобрался), а то, как с ним работать дальше в смысле анализа этого HTML-дерева и отдельных HTML-элементов, их атрибутов. В частности, имен классов CSS. Если
Invoke-WebRequest
недостаточно гибок в этом плане, то придется этот метод отбросить. Но все-равно спасибо за ваше разъяснение.
nick-for-habr
Invoke-WebRequest
?ilyachalov Автор
Мне нужно проанализировать локальный файл на языке HTML. `Invoke-WebRequest` — он же, насколько я знаю, загружает файл из указанного URL?