В ходе работы над одним домашним проектом, столкнулся с необходимостью парсинга HTML. Поиск по гуглу выдал комментарий Athari и его микро-обзор актуальных парсеров HTML в .NET за что ему огромное спасибо.

К сожалению, никаких цифр и/или аргументов в пользу того или иного парсера найдено не было, что послужило поводом к написанию данной статьи.

Сегодня я протестирую популярные, на данный момент, библиотеки для работы с HTML, а именно: AngleSharp, CsQuery, Fizzler, HtmlAgilityPack и, конечно же, Regex-way. Сравню их по скорости работы и удобству использования.


TL;DR: Код всех бенчмарков можно найти на github. Там же лежат результаты тестирования. Самым актуальным парсером на данный момент является AngleSharp — удобный, быстрый, молодежный парсер с удобным API.

Тем, кому интересен подробный обзор — добро пожаловать под кат.

Содержание




Описание библиотек


В данном разделе будут краткие описания рассматриваемых библиотек, описание лицензий и тд.

HtmlAgilityPack


Один из самых (если не самый) известный парсер HTML в мире .NET. Про него написано немало статей как на русском, так и на английском языках, к примеру на habrahabr.

Вкратце это быстрая, относительно удобная библиотека для работы с HTML (если XPath запросы будут несложными). Репозиторий давно не обновляется.
Лицензия MS-PL.

Парсер будет удобным если задача типична и хорошо описывается XPath выражением, к примеру, чтобы получить все ссылки со страницы, нам понадобится совсем немного кода:

/// <summary>
/// Extract all anchor tags using HtmlAgilityPack
/// </summary>
public IEnumerable<string> HtmlAgilityPack()
{
    HtmlDocument htmlSnippet = new HtmlDocument();
    htmlSnippet.LoadHtml(Html);

    List<string> hrefTags = new List<string>();

    foreach (HtmlNode link in htmlSnippet.DocumentNode.SelectNodes("//a[@href]"))
    {
        HtmlAttribute att = link.Attributes["href"];
        hrefTags.Add(att.Value);
    }

    return hrefTags;
}

Однако, если вам захочется поработать с css-классами, то использование XPath доставит вам много головной боли:

/// <summary>
/// Extract all anchor tags using HtmlAgilityPack
/// </summary>
public IEnumerable<string> HtmlAgilityPack()
{
    HtmlDocument hap = new HtmlDocument();
    hap.LoadHtml(html);
    HtmlNodeCollection nodes = hap
        .DocumentNode
        .SelectNodes("//h3[contains(concat(' ', @class, ' '), ' r ')]/a");
    
    List<string> hrefTags = new List<string>();

    if (nodes != null)
    {
        foreach (HtmlNode node in nodes)
        {
            hrefTags.Add(node.GetAttributeValue("href", null));
        }
    }

    return hrefTags;
}

Из замеченных странностей — специфическое API, порой непонятное и запутывающее. Если ничего не найдено, возвращается null, а не пустая коллекция. Ну и обновление библиотеки как-то затянулось — новый код давно никто не коммитал. Баги не фиксаются ( Athari упоминал о критическом баге Incorrect parsing of HTML4 optional end tags, который приводит к некорректной обработке тегов HTML, закрывающие теги для которых опциональны.)

Fizzler


Надстройка к HtmlAgilityPack, позволяющая использовать селекторы CSS.
Код, в данном случае, будет наглядным описанием того, какую проблему решает Fizzler:

// Документ загружается как обычно
var html = new HtmlDocument();
html.LoadHtml(@"
  <html>
      <head></head>
      <body>
        <div>
          <p class='content'>Fizzler</p>
          <p>CSS Selector Engine</p></div>
      </body>
  </html>");

// Fizzler это набор методов-расширений для HtmlAgilityPack
// к примеру QuerySelectorAll у HtmlNode

var document = html.DocumentNode;

// вернется: [<p class="content">Fizzler</p>]
document.QuerySelectorAll(".content"); 

// вернется: [<p class="content">Fizzler</p>,<p>CSS Selector Engine</p>]
document.QuerySelectorAll("p");

// вернется пустая последовательность
document.QuerySelectorAll("body>p");

// вернется [<p class="content">Fizzler</p>,<p>CSS Selector Engine</p>]
document.QuerySelectorAll("body p");

// вернется [<p class="content">Fizzler</p>]
document.QuerySelectorAll("p:first-child");

По скорости работы практически не отличается от HtmlAgilityPack, но удобнее за счет работы с селекторами CSS.

С коммитами такая же проблема как и у HtmlAgilityPack — обновлений давно нет и, по-видимому, не предвидится.

Лицензия: LGPL.

CsQuery


Был одним из современных парсеров HTML для .NET. В качестве основы был взят парсер validator.nu для Java, который в свою очередь является портом парсера из движка Gecko (Firefox).

API черпал вдохновение у jQuery, для выбора элементов используется язык селекторов CSS. Названия методов скопированы практически один-в-один, то есть для программистов, знакомых с jQuery, изучение будет простым.

На данный момент разработка CsQuery находится в пассивной стадии.

Сообщение от разработчика
CsQuery is not being actively maintained. I no longer use it in my day-to-day work, and indeed don't even work in .NET much these day! Therefore it is difficult for me to spend any time addressing problems or questions. If you post issues, I may not be able to respond to them, and it's very unlikely I will be able to make bug fixes.

While the current release on NuGet (1.3.4) is stable, there are a couple known bugs (see issues) and there are many changes since the last release in the repository. However, I am not going to publish any more official releases, since I don't have time to validate the current code base and address the known issues, or support any unforseen problems that may arise from a new release.

I would welcome any community involvement in making this project active again. If you use CsQuery and are interested in being a collaborator on the project please contact me directly.


Сам автор советует использовать AngleSharp как альтернативу своему проекту.

Код для получения ссылок со страницы выглядит приятно и знакомо для всех, кто использовал jQuery:

/// <summary>
/// Extract all anchor tags using CsQuery
/// </summary>
public IEnumerable<string> CsQuery()
{
    List<string> hrefTags = new List<string>();

    CQ cq = CQ.Create(Html);
    foreach (IDomObject obj in cq.Find("a"))
    {
        hrefTags.Add(obj.GetAttribute("href"));
    }

    return hrefTags;
}

Лицензия: MIT

AngleSharp


В отличие от CsQuery, написан с нуля вручную на C#. Также включает парсеры других языков.

API построен на базе официальной спецификации по JavaScript HTML DOM. В некоторых местах есть странности, непривычные для разработчиков на .NET (например, при обращении к неверному индексу в коллекции будет возвращён null, а не выброшено исключение; есть свой отдельный класс Url; пространства имён очень гранулярные), но в целом ничего критичного.

Развивается библиотека очень быстро. Количество различных плюшек, облегчающих работу просто поражает воображение, к примеру IHtmlTableElement, IHtmlProgressElement и тд.

Код чистый, аккуратных, удобный.
К примеру, извлечение ссылок со страницы практически ничем не отличается от Fizzler:

/// <summary>
/// Extract all anchor tags using AngleSharp
/// </summary>
public IEnumerable<string> AngleSharp()
{
    List<string> hrefTags = new List<string>();

    var parser = new HtmlParser();
    var document = parser.Parse(Html);
    foreach (IElement element in document.QuerySelectorAll("a"))
    {
    hrefTags.Add(element.GetAttribute("href"));
    }

    return hrefTags;
}

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

Лицензия: MIT

Regex


Древний и не самый удачных подход для работы с HTML. Мне очень понравился комментарий Athari, поэтому я его, комментарий, здесь и продублирую:
Страшные и ужасные регулярные выражения. Применять их нежелательно, но иногда возникает необходимость, так как парсеры, которые строят DOM, заметно прожорливее, чем Regex: они потребляют больше и процессорного времени, и памяти.

Если дошло до регулярных выражений, то нужно понимать, что вы не сможете построить на них универсальное и абсолютно надёжное решение. Однако если вы хотите парсить конкретный сайт, то эта проблема может быть не так критична.

Ради всего святого, не надо превращать регулярные выражения в нечитаемое месиво. Вы не пишете код на C# в одну строчку с однобуквенными именами переменных, так и регулярные выражения не нужно портить. Движок регулярных выражений в .NET достаточно мощный, чтобы можно было писать качественный код.

Код для получения ссылок со страницы выглядит ещё более-менее понятно:
/// <summary>
/// Extract all anchor tags using Regex
/// </summary>
public IEnumerable<string> Regex()
{
    List<string> hrefTags = new List<string>();

    Regex reHref = new Regex(@"(?inx)
    <a \s [^>]*
        href \s* = \s*
            (?<q> ['""] )
                (?<url> [^""]+ )
            \k<q>
    [^>]* >");
    
    foreach (Match match in reHref.Matches(Html))
    {
        hrefTags.Add(match.Groups["url"].ToString());
    }

    return hrefTags;
}


Но если вам вдруг захочется поработать с таблицами, да ещё и в вычурном формате, то пожалуйста, сначала посмотрите сюда.

Лицензия указана на этом сайте.

Benchmark


Скорость работы парсера, как ни крути, один из важнейших атрибутов. От скорости обработки HTML зависит то, сколько у вас времени займет та или иная задача.

Для замера производительности парсеров я использовал библиотеку BenchmarkDotNet от DreamWalker, за что ему огромное спасибо.

Замеры производились на Intel Core(TM) i7-4770 CPU @ 3.40GHz, но опыт подсказывает, что относительное время будет одинаковое на любых других конфигурациях.

Пару слов о Regex — не повторяйте этого дома. Regex очень хороший инструмент в умелых руках, но работа с HTML это точно не то, где стоит его использовать. Но в качестве эксперимента я попробовал реализовать минимально рабочую версию кода. Свою задачу он выполнил успешно, но количество времени, потраченное на написание этого кода, подсказывает, что повторять это я точно не стану.

Что ж, давай-те посмотрим на бенчмарки.

Получение адресов из ссылок на странице


Данная задача, как мне кажется, является базовой для всех парсеров — чаще именно с такой постановки задачи начинается увлекательное знакомство с миром парсеров (иногда и Regex).

Код бенчмарка можно найти на github, а ниже представлена таблица с результатами:

Библиотека Среднее время Среднеквадратическое отклонение операций/сек
AngleSharp 8.7233 ms 0.4735 ms 114.94
CsQuery 12.7652 ms 0.2296 ms 78.36
Fizzler 5.9388 ms 0.1080 ms 168.44
HtmlAgilityPack 5.4742 ms 0.1205 ms 182.76
Regex 3.2897 ms 0.1240 ms 304.37


В целом, ожидаемо Regex оказался самым быстрым, но далеко не самым удобным. HtmlAgilityPack и Fizzler показали примерно одинаковое время обработки, немного опередив AngleSharp. CsQuery, к сожалению, безнадежно отстал. Вполне вероятно, что я не умею его готовить. Буду рад услышать комментарии от людей, которые работали с данной библиотекой.

Оценить удобство не представляется возможным, так как код практически идентичен. Но при прочих равных условиях, код CsQuery и AngleSharp мне понравился больше.

Получение данных из таблицы


С данной задачей я столкнулся на практике. Причем таблица, с которой мне предстаяло поработать, не была простой.

Заметка о жизни в Беларуси
Захотелось мне получать актуальную информацию о обменном курсе валют в славном городе Минске. Каких-либо сервисов, для получения информации о курсах в банках, найдено не было, но случайно наткнулся на http://select.by/kurs/. Там информация обновляется часто и есть то, что мне нужно. Но в очень неудобном формате.
Ребят, если будете это читать — сделайте нормальный сервис, ну или хотя бы HTML поправьте.


Я предпринял попытку максимально запрятать всё то, что не относится именно к обработке HTML, но ввиду специфики задачи, не всё получилось.

Код у всех библиотек примерно одинаков, отличие только в API и том, какие возвращаются результаты. Однако стоит упомянуть о двух вещах: во-первых, у AngleSharp есть специализированные интерфейсы, что облегчило решение задачи. Во-вторых, Regex для данной задачи не подходит вообще никак.

Давай-те посмотрим на результаты:

Библиотека Среднее время Среднеквадратическое отклонение операций/сек
AngleSharp 27.4181 ms 1.1380 ms 36.53
CsQuery 42.2388 ms 0.7857 ms 23.68
Fizzler 21.7716 ms 0.6842 ms 45.97
HtmlAgilityPack 20.6314 ms 0.3786 ms 48.49
Regex 42.2942 ms 0.1382 ms 23.64


Как и в предыдущем примере HtmlAgilityPack и Fizzler показали примерно одинаковое и очень хорошее время. AngleSharp отстаёт от них, но, возможно, я сделал всё не самым оптимальным образом. К моему удивлению, CsQuery и Regex показали одинаково плохое время обработки. Если с CsQuery всё понятно — он просто медленный, то с Regex не всё так однозначно — скорее всего задачу можно решить более оптимальным способом.

Выводы


Выводы, наверное, каждый сделал для себя сам. От себя добавлю, что оптимальным выбором сейчас будет AngleSharp, так как он активно разрабатывается, обладает интуитивным API и показывает хорошо время обработки. Имеет ли смысл перебегать на AngleSharp с HtmlAgilityPack? Скорее всего нет — ставим Fizzler и радуемся очень быстрой и удобной библиотеке.

Всем спасибо за внимание.
Весь код можно найти в репозитории на github. Любые дополнения и/или изменения только приветствуются.
Какой HTML парсер используете вы?

Проголосовало 239 человек. Воздержалось 144 человека.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Комментарии (34)


  1. HellBrick
    23.12.2015 20:41

    Буквально на прошлой неделе наткнулся на очень милую граблю: HtmlAgilityPack при попытке распарсить одну невалидную html-ину улетел в stack overflow, чем и положил весь процесс =\ Никто не в курсе, как с обработкой кривых данных из реального веба у конкурентов (в основном интересует AngleSharp, как наиболее живой и вкусный на вид)?


    1. Athari
      23.12.2015 20:52
      +1

      Насколько мне известно, HAP — единственная библиотека, которая не дружит с кривым HTML. Что AS, что CQ должны работать нормально. Фактами доказать не могу, но глюки HAP задокументированы, а про обе нормальные библиотеки ничего подобного не слышал. :)


    1. isxaker
      23.12.2015 22:11
      +1

      Вот еще один интересный баг фича в HtmlAgilityPack — элемент form не содержит дочерних элементов(если нужно распарсить параметры form для формирования последующего запроса, приходится использовать div вместо form в xPath подробнее)


  1. masterL
    23.12.2015 21:03

    Глаз зацепился:

    Однако, если вам захочется поработать с css-классами, то использование XPath доставит вам много головной боли

    //h3[contains(concat(' ', @class, ' '), ' r ')]/a
    

    А что вы делаете этим запросом?


    1. Athari
      23.12.2015 21:17
      +2

      Это эквивалент CSS-запроса «h3.r>a», который корректно обрабатывает атрибуты class вида «r q», «q r», «rq» и другие. Можно было бы написать просто «h3[@class='r']/a», но тогда обработка атрибута была бы не как в CSS.


  1. Athari
    23.12.2015 21:13
    +14

    Славно накопипастили. :) Ну ладно, я не жадный. Впрочем, если подходить формально, то текст на SO лицензирован под CC BY-SA, то есть не помешает указать ссылку на источник (вопрос на SO) и лицензию (CC BY-SA 3.0). Куски кода — под Public Domain, в соответствии с указанием в моём профиле на SO (я юридически не имею права этого делать, но оставим придури законов за рамками).

    Что касается производительности, то у CsQuery и HtmlAgilityPack изначально были оптимизации в разные стороны, в результате CQ быстрее обрабатывал сложные запросы за счёт построения всяких индексов, а HAP быстрее искал по всем документы простыми запросами за счёт, собственно, отсутствия индексов. За AngleSharp не отвечаю. Я с автором пообщался, он упёртый как баран.

    Что меня напрягает во всех трёх библиотеках — это что все три паршиво следуют Framework Design Guidelines, да и просто хорошим практикам из самого .NET: HAP возвращает null вместо пустых коллекций; CQ имитирует краткие записи а-ля jQuery, нарушая все мыслимые стили именования; AS бездумно копирует интерфейсы из стандартов, наступая на грабли XmlDocument… В результате получается набор «молотков PHP»: вроде, все три работают, всеми можно пользоваться, но у всех трёх какие-то неоправданные странности. Впрочем, это взгляд перфекциониста; полагаю, большинству на такие нюансы наплевать. :)


  1. isxaker
    23.12.2015 21:52
    +1

    Много раз приходилось парсить html в .net, все время юзал HtmlAgilityPack. За исключением мелких граблей, все работает очень прилично.


  1. BloodUnit
    23.12.2015 23:05
    +2

    Каждый раз, когда тянет использовать Regex для парсинга HTML, читаю этот ответ, успокаиваюсь и использую как минимум HtmlAgilityPack, чего и всем желаю.


    1. Alexufo
      24.12.2015 01:15

      меня всегда интересовало, на чем основано убеждение этого ответа. Ну вот нужно выдрать там какой нить ответ из html и в чем ужас использовать для этого самый эффективный способ?


      1. Athari
        24.12.2015 01:20

        Ужас в цене поддержки кода. Если сайт часто меняется, то программисты удавятся ковырять регулярки каждый раз. Если код поддерживать не надо, если код сайта стабильный, если программисты дешевле железа и так далее, то регулярки рулят, конечно.


        1. Mixim333
          24.12.2015 03:05

          Ни могли бы объяснить, что подразумевается под: «Если сайт часто меняется, то программисты удавятся ковырять регулярки каждый раз», разве HtmlAgilityPack не сломается, если архитектура странички сайта будет переделана?

          Сам разбираю непубличный HTML-портал с помощью регулярок, архитектура работы которого следующая: «устройство — Linux-серверх — Grep — NETWORK — мое приложение» — к первым 4 звеньям я никакого отношения не имею и периодически эти звенья дают сбой (необходимо все жестко проверять — дополнительное действие для регулярки — IsMatch), под «устройством» подразумеваются логи с некоторой железки. Кстати, еще по поводу скорости Regex: у него есть замечательный RegexOptions.Compiled, применение которого позволило мне сократить работу регулярки примерно на 20-40%


          1. Athari
            24.12.2015 03:25
            +2

            Вопрос в устойчивости парсера к изменениям в коде HTML и в количестве необходимого кода и его читаемости.

            1. Если не делать регулярки непомерно сложными, то они будут ломаться из-за добавления атрибута в каком-нибудь элементе или изменения порядка классов в атрибуте. CSS-запросы более устойчивы к подобным изменениям.

            2. При написании регулярок очень легко скатиться к написанию write-only кода, когда регулярка пишется в одну строчку, а баги фиксятся костылями («воткну-ка я здесь look-behind, вроде, начинает работать»). После нескольких итераций код будет легче написать заново, чем исправить.

            3. При работе с DOM и CSS-запросами кода заметно меньше: работа с атрибутами, каскадами и прочим доступна из коробки, а не требует написания с нуля (или копипасты) ради каждой новой странички.

            4. Порог вхождения в CSS-запросы ниже, не нужно знать премудростей регулярок, чтобы сделать простые вещи.

            По производительности регулярки рвут полноценные парсеры с построением DOM как тузик грелку, конечно, но далеко не всегда парсинг — самое узкое место. Да и если узкое, не всегда выгоднее оптимизировать код (превращением его в нечитаемое месиво) вместо закидывания железом.

            И я говорю как раз про использование селекторов CSS, а не XPath. XPath хоть и лучше подходит для HTML, чем регулярные выражения, но не может сравниться с селекторами, весь смысл существования которых в выборке элементов из HTML.


      1. BloodUnit
        24.12.2015 02:11
        +2

        Если надо вытащить текст из пары нодов, то да, возможно, наверное, не знаю.

        Вначале вы пишете регулярки типа ...class=...[^>]*>(?<text>...)<... .

        Но вдруг, надо работать с атрибутами, порядок которых неизвестен, и начинаются пляски с опциональными группами, и вы начинаете писать регулярки типа ...(?<attr1>...)?(?<attr2>...)? либо выполняете несколько matches на каждый атрибут.

        Потом вы хотите работать с коллекциями, допустим с <ul> или чего еще хуже с таблицами, и вы начинаете понимать что что-то пошло не так, но продолжаете использовать регулярки, чтобы достать значения из td вы вначале захватываете таблицу, потом коллекцию tr, и проходите по коллекции td, регулярки вида ...[^>]*>(?<text>...)<... растут как грибы на каждый элемент/идентификатор/класс/аттрибут.

        И вдруг, вам необходимо ходить по DOMу, на этом месте будет боль и холодный пот, т.к. везде уже тонны регулярок. Вы с тоской удаляете регулярки, на которые затрачено много времени и используете xpath.

        Это мой печальный опыт, если у вас по-другому, расскажите.


        1. Alexufo
          24.12.2015 02:20

          при таком подходе явно xpath проще :-) Просто парсинг обычно самодостаточен. Вот html — дай результат. И там все уже ясно.


      1. withkittens
        24.12.2015 02:20

        в чем ужас использовать для этого самый эффективный способ?
        Как раз ровно об этом следующий ответ на странице.


        1. Alexufo
          24.12.2015 02:22

          Да, верно.Просто эта цифра «4427» на SO имеет авторитет Бога.
          А у «Don't listen to these guys.» всего 760 :-)


          1. BloodUnit
            24.12.2015 02:42
            +3

            Вы только первое предложение из ответа с 760 голосами прочитали?)

            Don't listen to these guys. You actually can parse context-free grammars with regex if you break the task into smaller pieces. You can generate the correct pattern with a script that does each of these in order:

            Solve the Halting Problem.
            Square a circle (simulate the «ruler and compass» method for this).
            Work out the Traveling Salesman Problem in O(log n). It needs to be fast or the generator will hang.
            The pattern will be pretty big, so make sure you have an algorithm that losslessly compresses random data.
            Almost there — just divide the whole thing by zero. Easy-peasy.


  1. efremovaleksey
    24.12.2015 02:27

    Недавно возникла задача написать утилиту, уведомляющую об обновлениях на определенном сайте, где результаты поиска подгружаются динамически.

    Использовал PhantonJS + Selenium Webdriver. В плане использования связка оказалась очень удобной, чего не скажешь о производительности.


  1. Rudolfo
    24.12.2015 03:40

    Я работаю с 2009 года с библиотекой Chilkat .NET — перевожу HTML в XHTML, а дальше работаю с ним как с XDocument'ом.


    1. Athari
      24.12.2015 03:41

      Положим, XPath (и тем более ручная навигация) — тоже не сахар.


  1. petuhov_k
    24.12.2015 05:49

    Нужен вариант «использую свой велосипед».


    1. Athari
      24.12.2015 07:50

      Прямо-таки мучает вопрос, какой можно изобрести велосипед для обработки HTML…


      1. petuhov_k
        24.12.2015 08:25

        Четыре библиотеки из статьи изобрели же зачем-то. А мне нужен был простой парсер для построения DOM. Без блэкджека и селекторов. Пробовал HtmlAgilityPack — как показал html5test.com, он не совсем корректен. О других не знал, решил написать сам — делов то на пару часов.


        1. Athari
          24.12.2015 09:59
          +1

          И… ваш парсер, написанный за два часа, превзошёл по качеству HAP?.. По-моему, вам стоит написать об этом статью. Я б почитал.


          1. petuhov_k
            25.12.2015 05:46

            Я не решал задачу «превзойти по качеству X», мне нужен был html парсер для браузера. Хотелось найти более лёгкую замену паре phantomjs+selenium для автоматизированного тестирования. Как-нибудь обязательно напишу об этом статью или несколько.


  1. AlekseenkoAV
    24.12.2015 07:47

    Спасибо за хороший обзор :)


  1. l0cal
    24.12.2015 09:55

    Ещё можно подключить jsoup через ikvm. Выходит 700 кб библиотека и 50 мб зависимостей, но работает нормально. Я использовал как замену htmlagiltypack когда нужно было быстро сделать парсер., так как CSS селекторы в jsoup работают надежней чем xpath в htmlagilitypack.


    1. Athari
      24.12.2015 09:57

      А как же Fizzler?


      1. l0cal
        24.12.2015 10:30

        На тот момент я о нем ничего не знал и паралелльно узнал о ikvm и имел опыт использования jsoup. Если надергать CSS селекторы из отладчика хрома, то можно создать парсер за 15 минут. Конечно он ломается когда меняют структуру страницы, но это легко и быстро исправить.


  1. and_rew
    24.12.2015 10:18
    +1

    Вдруг кому пригодится — используем Goose Parser, очень довольны. Работает с фантомом, позволяет декларативно описать действия пользователя и хранить их в обычном JSON. Расширяемый, если надо добавить своих фишек.


  1. danslapman
    24.12.2015 11:22
    +1

    Использую HtmlParser и HtmlTypeProvider из FSharp.Data, когда нужно сделать быстро и удобно


  1. redmanmale
    24.12.2015 14:09
    +1

    Я вижу, что автор против того, чтобы парсить HTML регулярками, но не могу не напомнить о знаменитом ответе на стеке.


  1. luxferre
    24.12.2015 14:10

    использую SGMLReader и XPath в таких случаях github.com/MindTouch/SGMLReader


  1. Ch0bits
    24.12.2015 15:04

    Большое спасибо за обзор. Раньше всегда использовал HAP, который славится своими багами. Но приходилось как-то жить с ним. Но благодаря обзору открыл для себя AngleSharp — это просто сказочная библиотека, бриллиант. Спасибо!