Pull to refresh

Comments 69

Когда мне надо было парсить html, я пользовался регулярными выражениями. А можно было проще…
UFO just landed and posted this here
как мне кажется, через это проходили если не все, то многие :-)
года с полтора назад я писал качалку музыки с известного ресурса — ссылки парсились регулярками, а когда появилась идея про вакансии на хабре, мысль с регулярками как-то даже и не пришла в голову — все приходит с опытом
Я ненормальный — когда все использовали регулярные выражения, я использовал конечные автоматы :)
Надеюсь это не патология, ибо нас таких минимум двое)
«asking regexes to parse arbitrary HTML is like asking Paris Hilton to write an operating system» © от туда (-:
Всему надо знать меру. Если у вас есть html и надо получить, к примеру, title, не ужели будете подключать сторонние библиотеки и парсить весь html? Regex в данном случае продуктивнее. А то что парень по ссылке с пеной у рта отстаивает свою правоту, так это, извините, диагноз.
Это не пена у рта, это сатира.
Если бы актеру пришлось переиграть это обращение к народу, ему бы пришлось использовать «пену», иначе получилось бы не правдоподобно)
Юмор, и сатира в частности, служит высмеиванию чего то не правильного, а, как говорилось, Regex все же уместен в некоторых случаях, поэтому как то не очень смешно.
UFO just landed and posted this here
Не загоняйте меня в цикл) Я всего лишь хочу сказать, что не стоит так насмехаться над регулярками. Они в сотнях случаях эффективнее любых парсеров, поэтому сравнивать с пэрис это кощунство!)
UFO just landed and posted this here
А вот у меня такая ситуация: В одном проекте надо получать title, favicon и description страницы:
  • через HttpWebResponse получаю первые 5000 байт
  • регуларкой получаем charset encoding
  • регуляркой получаем нужние данные + иногда indexOf
Согласен, что немного спартанские методы, но зато уже длительное время все работает шустро и стабильно. И только исxодя из этого, я и говорю, что иногда не стоит отказываться от регулярныx выражений, потому что у самого нету с ним негативного опыта. Конечно, весь документ парсить таким образом мне и в голову не приxодило.
UFO just landed and posted this here
На вxод, в массе, кириллица — проблем ни разу не было! Если в коде указан charset encoding его и используем, если нет, тогда — Ude.

С title, description и favicon ничего предполагать не надо, так как я все же вижу результаты и это все пишу не просто «выпендриться», а посоветовать, что бы люди не списывали со счетов Regex. Если вас устраивают другие варианты, то я только рад;). Меня же тот, который описал!
UFO just landed and posted this here
var link = Regex.Match(document, "(<([^>]*link[^>]+(rel[\\s]*=[\\s]*('|\"|)(shortcut|icon))[^>]+)>)", RegexOptions.IgnoreCase).Value;
      var favicon = Regex.Match(link, "(href[\\s]*=[\\s]*('|\"|)([^'\"\\s]+)('|\"|))", RegexOptions.IgnoreCase).Value;
Извините убирал лишнее и пропустил: в получении урл используем 3 найденую группу.
UFO just landed and posted this here
6% отпадают, так как Encoding.GetEncoding(encodingName) не обработает не верное имя -> заюзаем тогда Ude. За 3% ваша правда, но пока что все в пределаx нормы, если что, так перейду полностью на Ude.
Some people, when confronted with a problem, think «I know, I'll use regular expressions.» Now they have two problems

(С) Jamie Zawinski
Эта цитата обязательно появляется в любом месте, где упоминались регулярные выражения.
И самое интересное, что она не надоедает.
UFO just landed and posted this here
из минусов я выделил только то, что нет родных методов GetElement(s)ByTag / ByName / ByClass — хотя это все легко реализуется с помощью LINQ
Хм… а можно же extension написать.
UFO just landed and posted this here
Единственная проблема, автор не спешит принимать патчи, да и вообще как-то затерялся. Так что многое приходится патчить вручную.
Названия методов заимствованы из JavaScript + плюшки: GetElementbyId(), CreateAttribute(), CreateElement() и т.д.

Справедливости ради отмечу, что это не заимствование, а интерфейсы DOM. В JS они просто взяты оттуда же:)
спасибо за замечание, сейчас поправлю :-)
GetElementbyId() вещь конечно хорошая, только у нее есть косяк в том, что этот метод есть только у HtmlDocument…
А при работе уже с HtmlNode такого метода нет. И это крайне не удобно, если делаешь не поиск 1 div в огромном документа, а когда делаешь довольно большой разбор
Согласно стандарту, id должен быть уникальным в пределах документа. Так что как бы и нет смысла искать по id в пределах определенной ноды (хоть это и не совсем так). В JS, например, у нод тоже нет этого метода — всё согласно стандарту.
DOM-методы — не самая удобная вещь на свете.
Стандарты стандартами, к ним очень вольно относятся разработчики.
Я тут парсил вики на выходных, и дубляж обнаружил даже там где-то.
По этому стандарт стандартом, а парсер должен работать.
Вики? Википедию? Неуникальные id'ы? плохо:(

Все-таки искать по id внутри ноды это одно, а его неуникальность — другое. Если мы предположим, что атрибут id может быть не уникальным, то нам придется из метода, название которого говорит, что он возвращает один элемент, возвращать коллекцию.

Если вам действительно, на практике, необходимо такое поведение, то можно отнаследоваться и поступить так, как в js-библиотеке MooTools:
function (id) {
    var founded = document.getElementById(id);
    if(!founded) {
        return null;
    }
    for (var parent = founded.parentNode; parent != this; parent = parent.parentNode) {
        if (!parent) {
            return null;
        }
    }
    return founded
}
Я этот парсер использую для фильтрации того хлама, что пишут в форму написания поста/комментария пользователи. Достаточно хорошая штука, ни разу не подводила.
>возникла такая проблема — страницы скачивались очень медленно.

Такую проблему решал распараллеливанием. На некоторых сайтах 25 потоков спокойно работают. Хотя хабр, как понимаю, ограничения накладывает на кол-во запросов. Получится около 1-2секунды на стр
Ничего полезного — в смысле те кто имеют высшее получают больше или как?
в смысле, что недостаточно данных для того чтобы вести речь о какой-либо зависимости.
хотя один вывод пожалуй сделать можно — серьезные отечественные компании (например Rambler, СКБ Контур, QIWI Кошелек, Parallels) требуют наличия высшего образования.
впрочем, вы можете и сами взглянуть на отчете, который приложен в результатах топика
Зачем такие страшные конкструкции, если заявлена поддержка xpath?
сам с xpath никогда не сталкивался и узнал про него когда страшная конструкция уже была написана. поэтому, оставлю на будущее
Год назад были проблемы с поддержкой xpath — простые конструкции он обрабатывал верно, но на что-нибудь более-менее сложное возвращал пустой результат.
Как со скоростью обработки тегов?
По сравнению с регулярками медленней?
документ слишком маленький, пожалуй, на нем серьезного сравнения не провести.
в любом случае, разбирать DOM-деревья регулярными выражениями — плохой выбор (см. комментарий выше)
UFO just landed and posted this here
Так это смотря на «продакшене» ЧЕГО.
Если не большого массива информации за короткое время, а обычных сайтов, периодически обновляемой информации, то удобство окупается.
Вспоминаю парсинг html регулярками как страшный сон.
UFO just landed and posted this here
В HtmlAgilityPack есть класс HtmlWeb, с помощью которого можно сразу получить HtmlDocument, вызвав метод экземпляра Load и передав в него в качестве параметра url страницы, так что можно было обойтись возможностями лишь HtmlAgilityPack в рамках этой задачи. У вас на скриншоте этот класс, кстати, есть.
спасибо за наводку, я это пропустил
Я для парсинга субтитров с ted.com использовал эту штуку, удобно очень.
Тоже пытались использовать. Удобно бесспорно, но у нас были очень большие объемы (много страниц парсились одновременно), а он к сожалению выжирает очень много памяти, так как хранит на момент работы всю Dom структуру документа. И к сожалению пришлось вернуться к регулярным выражениям. Т.е. на больших объемах обрабатываемых параллельно, готовьтесь к большим утечкам памяти. Но, на мой взгляд, лучшее решение на небольших объемах или если нужно выдирать много различных данных, и регулярки использовать накладно в плане нагрузки процессора, так как операции поиска по dom отрабатывают быстрее (проверенно на парсинге ссылок из документа) и есть возможность жертвовать памятью.
а вам было критично знать всю структуру? или последовательного чтения достаточно?
если да, то по идее можно использовать SAX (Simple Api XML) — в .net это обеспечивает класс XmlReader.
написать обертку для HTML и вперед
Конечно лучше всего использовать xpath, а не городить такой монструозный код. В файерфоксе есть куча плагинов, облегчающих работу с xpath, т.е. достаточно кликнуть на нужный элемент, скопировать выражение и вставить в код. Только нужно знать некоторые особенности, например когда файерфокс создает DOM, после тэга table добавляется tbody, при верстке страницы его как правило опускают. Я написал для себя маленькую утилитку, позволяющую тестить xpath именно для HtmlAglityPack и статического html, получилось очень удобно.
А аналогов nokogiri для .net нет?

Ну там «tr.hot» и дальше разбор полетов…
А как библиотека справляется с неправильным html (нет закрытия тега и т.д.)?
Вполне отлично — закрывает их! :)
Я в свое время для разбора сайтов SgmlReader юзал. Всех плюшек уже не помню, но то что он «неправильный» html делал «правильным» и спокойно работал с xpath, точно было.
Забавно, что по прошествии уймы лет, критический баг Incorrect parsing of HTML4 optional end tags до сих пор не пофиксен. Это означает, что совершенно валидный документ HTML будет неверно обработан, если он пользуется опциональностью закрытия тегов. Это же курам на смех! Пять лет! Пять лет прошло!

Не понимаю, почему у этой библиотеки такая популярность, хотя сейчас есть достойные конкуренты. Я разобрал некоторые на StackOverflow:

Как распарсить HTML в .NET?

Короче, я бы советовал переходить на CsQuery или AngleSharp, а не пользоваться этим ископаемым глюкалом (некоторые ещё и XPath умудряются пользоваться, хотя даже для HAP есть Fizzler, я уж молчу про хождение по ChildNodes индексами — ещё одна загадка природы).

P. S. Пишу комментарий к старой статье, потому что она в топе гугла, и, подозреваю, многие ей доверятся. Про недостатки HAP надо знать.

А сейчас то, в 2020, т.е. еще через 5 лет, этот баг пофикшен наконец? :) Ссылка ваша теперь не валидна, поэтому не понял о каком баге идет речь.

В 2020 году нет смысла использовать что-то, что не называется AngleSharp. Автор даже сдался и нарушил "стандарты" во имя адекватного API. Это лучшая библиотека для парсинга HTML, точка.


CsQuery умер как бесполезный на фоне AngleSharp.


HtmlAgilityPack существует как легаси. Он переехал на GitHub, трекер забит багами про неправильный парсинг, в роадмапе невыполненные планы лежат годами, всем пофиг. После смерти CodePlex — внезапно! — ничего не поменялось.


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

Ну вот я попробовал AngleSharp и вот что мне не понравилось:


  1. При парсинге частичного HTML, парсер всегда оборачивает результат в
    <html><body></body></html>, хотя мне это не нужно. К счастью, это решается с помощью фрагментного парсинга.
  2. При парсинге неизвестных тегов парсер вставляет какую-то несуществующую хрень. Пример: парсим хабровский <cut/>, получаем <cut></cut>. А при таком коде <cut/><!--<spoiler1>--> test</spoiler1> парсер изгаляется как может: <cut><!--<spoiler1>--> test</spoiler1></cut> (пришлось заменить spoiler на spoiler1, потому что хабровский парсер ломается).
  3. Парсер никуда не сохраняет тип кавычек у атрибутов и позиции элементов в исходном тексте.

Это пункты делают невозможным конвертацию html файлов с кастомными тегами.


При этом все это нормально работает в HtmlAgilityPack. Так что рано ставить точку — до идеала далеко.


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

Ну мне, как конечному потребителю библиотеки, не особо важна чистота реализации.

AngleSharp парсит так, как будет парсить любой полноценный браузер. Из невалидного HTML он сгенерирует точно такой же мусор, который сгенерирует браузер. HAP в этом плане гораздо хуже, потому что мало того, что он сгенерирует мусор по-своему, так ещё и валидный код распарсит не так, как полноценный браузер.


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


По пунктам:


  1. Итого, проблема отсутствует, если использовать корректный API.


  2. Выполните код div.innerHTML = "<p><cut/></p>"; alert(div.innerHTML). Это к вопросу о том, почему так сделано. Если парсер Хабра не состоянии обработать полученный текст (там что, регулярка?), то это проблема Хабра. Если вы ориентируетесь на неправильное поведение, то вы заранее подписываетесь на проблемы, независимо от библиотеки.


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


  3. Вам зачем кавычки сохранять? Вы используете библиотеку в редакторе? Тогда при использовании HAP у вас будет гораздо больше проблем, потому что пока AngleSharp изменит сериализованный формат, HAP испортит и сериализацию, и представление: пробелы полетят, вложенность тегов изменится и т.п., причём без всяких кастомных тегов.



Итого:


  1. Если мне надо предсказуемо распарсить HTML так, как сделает нормальный браузер, то выбора нет — только AngleSharp. HAP сделает что-то своё и непредсказуемое.


  2. Если писать плагин для редактора или ещё что-нибудь такое, то обе библиотеки совершенно не подходят. Там нужен совершенно другой уровень представления.


  3. Если для работы с текстом для Хабра вам идеально подходит HAP (например, потому что на Хабре используется та же библиотека, поэтому многочисленные баги аккуратно нкладываются друг на друга), то вам просто сильно повезло, но в общем случае HAP остаётся ужасно кривым парсером, несовместимым с окружающим миром.


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

Согласен — надо было с этого начинать. Я разрабатываю конвертер MarkConv. И дело в том, что сам Markdown может включать в себя HTML блоки, например так:


<details>
<summary>title</summary>

content

</details>

Сейчас используются регулярки, но сейчас переписываю на нормальные парсеры: Markdig и HTML.


Markdig парсит этот так: html block; paragraph; html block. И эти html блоки уже можно парсить с помощью html парсера. Но вот незадача — они невалидные, т.е. в конце, например, получится закрывающий тег </details>. С таким парсингом нормально не справляются ни HAP ни AngleSharp.


Тогда я пришел к другой более сложной стратегии: обрабатывать сначала markdown блоки, затем сцеплять их в единый html и парсить уже валидный HTML. Схема работает с использованием HAP, а с AngleSharp возникли проблемы, указанные выше.


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


Если парсер Хабра не состоянии обработать полученный текст (там что, регулярка?), то это проблема Хабра.

Про Хабра сказал лишь с целью того, что через комментарий не смог нормально выразить свою мысль. А так-то да — у Хабра есть проблемы с парсингом.


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

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


Вам зачем кавычки сохранять? Вы используете библиотеку в редакторе?

Ну ок — это в принципе небольшая некритичная вещь, без которой можно обойтись.


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

Да, примерно такой кейс. Ну я бы не сказал, что совершенно — HAP более мене справляется. Писать свое я сейчас не хочу.


Если для работы с текстом для Хабра вам идеально подходит HAP (например, потому что на Хабре используется та же библиотека, поэтому многочисленные баги аккуратно нкладываются друг на друга)

И для Хабра тоже. Разве используется? Я думал Хабр на PHP написан.

Я разрабатываю конвертер MarkConv.

Я правильно понял, что это конвертация между диалектами, плюс какая-то обработка картинок и генерация содержания? Было бы неплохо обозначить в ридми суть: зачем конкретно оно надо, что конкретно оно делает по пунктам.


Тогда я пришел к другой более сложной стратегии: обрабатывать сначала markdown блоки, затем сцеплять их в единый html и парсить уже валидный HTML. Схема работает с использованием HAP, а с AngleSharp возникли проблемы, указанные выше.

Подход звучит логичным, потому что это то, что будет делать конвертер MD в HTML. С фрагментами проблем нет, как я понял. Если вам нужна поддержка тегов со специфическим поведением, я бы форкнул AngleSharp (для ката скопипастить код какого-нибудь только-самозакрывающегося тега типа img и убрать лишнее). Кавычки значения не должны иметь для ваших целей.

Я правильно понял, что это конвертация между диалектами, плюс какая-то обработка картинок и генерация содержания? Было бы неплохо обозначить в ридми суть: зачем конкретно оно надо, что конкретно оно делает по пунктам.

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


Потом захотелось конвертировать теги <details> в хабровский <!--<spoiler>--> , проверять валидность ссылок, мапить локальные картинки на habrastorage, генерировать содержание и делать другие вещи. На самом деле по маркдауну у хабра есть еще проблемы. До какой-то степени это реализовать удалось, но сейчас код выглядит уже довольно костыльно, к тому же я столкнулся с проблемами при обработки вложенных узлов. И вот недавно решил переписать проект на нормальные парсеры.


В итоге MarkConv сейчас представляет из себя не только конвертер маркдаунов (помимо habr там теперь еще поддерживается формат https://dev.to/), но и чекер.


В целом я продвигаю идею написания текста как исходного кода, с использованием системы контроля версий, пулл-риквестов, непрерывной интеграции и других привычных для программиста вещей. Думаю написать об этом статью на хабр, продвинуть MarkDown.


Если вам нужна поддержка тегов со специфическим поведением, я бы форкнул AngleSharp (для ката скопипастить код какого-нибудь только-самозакрывающегося тега типа img и убрать лишнее). Кавычки значения не должны иметь для ваших целей.

Я пожалуй пока что останусь на HAP — вроде работает нормально, к тому же такие HTML вставки намного легче полноценного кода веб-страницы. Если обнаружу, что он не справляется с парсингом валидного, задумаюсь об альтернативах. Форком сейчас не хочу заниматься.

В итоге решил написать свой лексер и парсер на ANTLR — так получилось обойтись без костылей с преобразованием markdown фрагментов в html текст — для них просто используются специальные токены MARKDOWN_FRAGMENT. Грамматика HTML небольшая и легко кастомизируемая (в частности добавил поддержку хабровского тега cut): HtmlLexer, HtmlParser.


Подробней о технических деталях парсинга HTML внутри Markdown я планирую написать статью в скором времени.

Sign up to leave a comment.

Articles