AngleSharp для C# (.NET): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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                    

Рекомендаций