AngleSharp для C# (.NET): полное руководство с примерами
AngleSharp — это современная, высокопроизводительная библиотека для парсинга и манипуляции HTML, CSS, XML и SVG-документами в экосистеме .NET. Она предоставляет разработчикам на C# полноценную реализацию DOM (Document Object Model), максимально приближенную к стандартам W3C. В отличие от многих других парсеров, AngleSharp не просто извлекает данные из строки — он строит полноценное DOM-дерево, с которым можно работать так же, как в браузере: находить элементы по селекторам, изменять атрибуты, добавлять или удалять узлы, сериализовать результат обратно в HTML.
Зачем нужна эта библиотека? В современной веб-разработке и автоматизации часто возникает необходимость программно анализировать HTML-страницы. Это может быть парсинг данных для аналитики, веб-скрапинг (извлечение контента с сайтов), тестирование веб-приложений, создание статических генераторов сайтов или даже написание собственного браузерного движка. AngleSharp идеально подходит для этих задач, так как он полностью написан на C#, не имеет внешних зависимостей (кроме .NET Standard), и работает в разы быстрее, чем, например, использование HtmlAgilityPack в связке с регулярными выражениями. Кроме того, библиотека поддерживает CSS-селекторы, что делает навигацию по DOM-дереву интуитивно понятной для любого веб-разработчика.
Ещё одно важное преимущество AngleSharp — это его модульность и расширяемость. Вы можете подключать дополнительные пакеты для работы с CSS, JavaScript-движками (например, Jint), или для парсинга SVG. Библиотека активно поддерживается сообществом, имеет подробную документацию и используется в production-среде многими компаниями. В этом руководстве мы подробно разберём установку, основные методы и классы, а также приведём реальный пример парсинга веб-страницы.
Установка
AngleSharp распространяется через NuGet — менеджер пакетов для .NET. Установить его можно двумя способами:
Через .NET CLI (рекомендуется)
dotnet add package AngleSharp
Через Package Manager Console в Visual Studio
Install-Package AngleSharp
Для работы с CSS-парсингом дополнительно установите пакет:
dotnet add package AngleSharp.Css
После установки добавьте using-директиву в ваш код:
using AngleSharp;
using AngleSharp.Dom;
using AngleSharp.Html.Parser;
Быстрый старт — минимальный рабочий пример
Давайте напишем простейшую программу, которая загружает HTML-строку и извлекает из неё заголовок (тег <h1>):
using AngleSharp;
using AngleSharp.Html.Parser;
// 1. Создаём парсер
var parser = new HtmlParser();
// 2. Парсим HTML-строку в DOM-документ
var html = @"<!DOCTYPE html>
<html>
<head><title>Тестовая страница</title></head>
<body>
<h1>Привет, AngleSharp!</h1>
<p>Это абзац текста.</p>
</body>
</html>";
var document = parser.ParseDocument(html);
// 3. Находим элемент h1 и выводим его текст
var h1 = document.QuerySelector("h1");
Console.WriteLine(h1?.TextContent); // Выведет: Привет, AngleSharp!
// 4. Выводим весь HTML обратно (сериализация)
Console.WriteLine(document.DocumentElement.OuterHtml);
Этот пример демонстрирует базовый цикл работы с библиотекой: парсинг → поиск элемента → извлечение данных. Обратите внимание, что метод QuerySelector принимает CSS-селектор, что очень удобно.
Основные методы, классы и интерфейсы
AngleSharp предоставляет богатый API. Ниже приведены 8 ключевых элементов, которые вам понадобятся в 90% случаев.
1. HtmlParser — основной класс для парсинга
Сигнатура: public class HtmlParser : IHtmlParser
Что делает: Преобразует строку, поток или URL в DOM-документ (IDocument). Это точка входа в библиотеку.
using AngleSharp.Html.Parser;
var parser = new HtmlParser();
// Парсинг из строки
var docFromString = parser.ParseDocument("<div>Hello</div>");
// Парсинг из потока (например, из файла)
using var stream = File.OpenRead("index.html");
var docFromStream = parser.ParseDocument(stream);
// Парсинг с указанием URL (для корректного разрешения относительных ссылок)
var docWithUrl = parser.ParseDocument("<img src='/image.png'>", new Url("http://example.com"));
2. IDocument — интерфейс DOM-документа
Сигнатура: public interface IDocument : INode, IEventTarget, ...
Что делает: Представляет собой целый HTML-документ. Содержит корневой элемент (DocumentElement), заголовок (Title), коллекции ссылок, изображений и т.д.
var document = parser.ParseDocument(html);
// Получаем заголовок страницы
string title = document.Title;
// Получаем корневой элемент <html>
var htmlElement = document.DocumentElement;
// Получаем все ссылки на странице
var links = document.Links;
// Получаем все изображения
var images = document.Images;
// Получаем все скрипты
var scripts = document.Scripts;
3. IElement — интерфейс HTML-элемента
Сигнатура: public interface IElement : INode
Что делает: Представляет один HTML-элемент (например, <div>, <a>, <p>). Позволяет получать и изменять атрибуты, классы, стили, текстовое содержимое.
var div = document.QuerySelector("div.my-class");
// Получаем атрибут
string id = div.GetAttribute("id");
// Устанавливаем атрибут
div.SetAttribute("data-value", "123");
// Работа с классами
div.ClassList.Add("new-class");
div.ClassList.Remove("old-class");
// Текстовое содержимое (без HTML-тегов)
string text = div.TextContent;
// Внутренний HTML
string innerHtml = div.InnerHtml;
// Внешний HTML (с самим элементом)
string outerHtml = div.OuterHtml;
4. QuerySelector / QuerySelectorAll — поиск по CSS-селекторам
Сигнатура: IElement QuerySelector(string selectors) и IHtmlCollection<IElement> QuerySelectorAll(string selectors)
Что делает: Находит элементы, соответствующие CSS-селектору. QuerySelector возвращает первый подходящий элемент, QuerySelectorAll — все.
// Найти первый элемент с классом "price"
var priceElement = document.QuerySelector(".price");
// Найти все элементы <li> внутри <ul> с id "menu"
var menuItems = document.QuerySelectorAll("ul#menu > li");
// Сложный селектор: все ссылки внутри div с классом "content", у которых href начинается с "https"
var externalLinks = document.QuerySelectorAll("div.content a[href^='https']");
foreach (var link in externalLinks)
{
Console.WriteLine(link.GetAttribute("href"));
}
5. GetElementById / GetElementsByClassName / GetElementsByTagName — быстрый поиск
Сигнатура: IElement GetElementById(string id), IHtmlCollection<IElement> GetElementsByClassName(string classNames), IHtmlCollection<IElement> GetElementsByTagName(string tagName)
Что делает: Устаревшие, но быстрые методы для поиска элементов. Работают быстрее QuerySelector, если нужно найти элемент по ID или тегу.
// Поиск по ID (самый быстрый)
var header = document.GetElementById("header");
// Поиск по имени класса
var allButtons = document.GetElementsByClassName("btn");
// Поиск по имени тега
var allParagraphs = document.GetElementsByTagName("p");
6. IHtmlCollection<T> — коллекция элементов
Сигнатура: public interface IHtmlCollection<out T> : IEnumerable<T>
Что делает: Представляет собой "живую" коллекцию элементов, которая автоматически обновляется при изменении DOM. Поддерживает итерацию и доступ по индексу.
var items = document.QuerySelectorAll("li");
// Доступ по индексу
var firstItem = items[0];
// Итерация
foreach (var item in items)
{
Console.WriteLine(item.TextContent);
}
// Количество элементов
int count = items.Length;
// Преобразование в обычный список
var list = items.ToList();
7. Создание и вставка новых элементов
Сигнатура: IElement CreateElement(string localName), void AppendChild(INode child), void InsertBefore(INode newElement, INode referenceElement)
Что делает: Позволяет программно создавать новые HTML-элементы и встраивать их в DOM-дерево.
// Создаём новый элемент <div>
var newDiv = document.CreateElement("div");
newDiv.ClassName = "alert";
newDiv.TextContent = "Это новое сообщение!";
// Находим родительский элемент
var body = document.QuerySelector("body");
// Добавляем в конец body
body.AppendChild(newDiv);
// Создаём ещё один элемент и вставляем перед первым
var anotherDiv = document.CreateElement("div");
anotherDiv.InnerHtml = "<strong>Важно!</strong>";
body.InsertBefore(anotherDiv, newDiv);
// Удаляем элемент
body.RemoveChild(newDiv);
8. Сериализация — преобразование DOM обратно в HTML
Сигнатура: string ToHtml() (метод расширения) или свойство OuterHtml
Что делает: Преобразует DOM-дерево (или его часть) обратно в строку HTML. Полезно для сохранения изменённого документа.
// Сериализация всего документа
string fullHtml = document.ToHtml();
// Сериализация только элемента
var div = document.QuerySelector("div.content");
string divHtml = div.OuterHtml;
// Сериализация с настройками (например, без форматирования)
var formatter = new AngleSharp.Html.HtmlFormatter(
new AngleSharp.Html.HtmlMarkupFormatter { Indent = false }
);
string minifiedHtml = div.ToHtml(formatter);
Реальный практический пример: Парсинг новостного сайта
Давайте напишем полноценный пример, который загружает страницу новостей (например, Hacker News), извлекает заголовки, ссылки и рейтинг, а затем сохраняет результат в CSV-файл. Это демонстрирует типичный сценарий веб-скрапинга.
using AngleSharp;
using AngleSharp.Dom;
using AngleSharp.Html.Parser;
using System.Text;
public class NewsItem
{
public string Title { get; set; }
public string Url { get; set; }
public string Score { get; set; }
}
public class HackerNewsParser
{
private readonly HtmlParser _parser;
private readonly HttpClient _httpClient;
public HackerNewsParser()
{
_parser = new HtmlParser();
_httpClient = new HttpClient();
// Имитируем браузер, чтобы не быть заблокированным
_httpClient.DefaultRequestHeaders.Add("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
}
public async Task<List<NewsItem>> ParseNewsAsync(string url)
{
var newsItems = new List<NewsItem>();
try
{
// 1. Загружаем HTML-код страницы
Console.WriteLine($"Загрузка страницы: {url}");
var response = await _httpClient.GetStringAsync(url);
// 2. Парсим HTML
var document = _parser.ParseDocument(response);
// 3. Находим все строки таблицы с новостями
// На Hacker News новости находятся в элементах <tr> с классом "athing"
var rows = document.QuerySelectorAll("tr.athing");
Console.WriteLine($"Найдено новостей: {rows.Length}");
foreach (var row in rows)
{
try
{
// 4. Извлекаем заголовок и ссылку
var titleElement = row.QuerySelector("td.title a");
if (titleElement == null) continue;
var title = titleElement.TextContent.Trim();
var link = titleElement.GetAttribute("href");
// 5. Находим рейтинг (он находится в следующей строке после <tr class="athing">)
var nextRow = row.NextElementSibling;
var scoreElement = nextRow?.QuerySelector("span.score");
var score = scoreElement?.TextContent ?? "0 points";
// 6. Создаём объект и добавляем в список
newsItems.Add(new NewsItem
{
Title = title,
Url = link,
Score = score
});
Console.WriteLine($" - {title} [{score}]");
}
catch (Exception ex)
{
Console.WriteLine($"Ошибка при обработке строки: {ex.Message}");
}
}
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Ошибка HTTP-запроса: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Общая ошибка: {ex.Message}");
}
return newsItems;
}
public void SaveToCsv(List<NewsItem> items, string filePath)
{
var csv = new StringBuilder();
csv.Append