Как работает анализ HTML, если он не использует регулярное выражение?

96

Я каждый день вижу вопросы, в которых спрашивают, как разобрать или извлечь что-то из некоторой HTML-строки, и первым ответом / комментарием всегда будет «Не используйте RegEx для синтаксического анализа HTML, чтобы не почувствовать гнев!» (последняя часть иногда опускается).

Меня это сбивает с толку, я всегда думал, что в целом лучший способ разобрать любую сложную строку - использовать регулярное выражение. Так как же работает парсер HTML? Разве для анализа не используются регулярные выражения.

Одним из конкретных аргументов в пользу использования регулярного выражения является то, что не всегда есть альтернатива синтаксического анализа (например, JavaScript, где DOMDocument не является универсально доступным вариантом). Например, jQuery отлично справляется с использованием регулярного выражения для преобразования строки HTML в узлы DOM.

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

Энди Э
источник
Помечено, чтобы добавить синтаксический анализ и html-синтаксический анализ - @Andy E, надеюсь, с вами все в порядке - я подумал, что это будет полезно.
JXG 08
@JXG: Меня это устраивает, спасибо :-)
Энди Э

Ответы:

65

Обычно с помощью токенизатора. В проекте спецификации HTML5 есть обширный алгоритм для обработки «реального HTML».

Квентин
источник
1
Хорошая находка ... в цитате: «Для обработки таких случаев у парсеров есть уровень вложенности скриптов, который должен быть изначально установлен на ноль, и флаг паузы парсера, который изначально должен быть установлен на ложь». - Другими словами, вы должны повторить это самостоятельно и иметь много настраиваемой логики: P
Тимоти Хоури
1
Голосовать за. Лучше сделать упор на алгоритмическую сложность, чем на какую-то технологию.
Арнис Лапса,
1
Самостоятельная итерация с большим количеством настраиваемой логики - не лучшая идея. По возможности используйте библиотеку, поддерживающую стандартный алгоритм. например search.cpan.org/~tobyink/HTML-HTML5-Parser-0.03/lib/HTML/HTML5/… / code.google.com/p/html5lib
Квентин
8
Основная проблема с анализаторами HTML заключается в том, что при обнаружении ошибки вы не можете выплюнуть «Ошибка анализа» и оставить все как есть. Вы входите в режим причуд и пытаетесь как можно лучше разглядеть возникший беспорядок, в том числе несовпадающие теги, чересстрочный стиль [{]} и всевозможные странности, пытаясь сделать результат как можно лучше, и неизбежное неудача наименее болезненна ... это не то, что вы можете сделать с регулярными выражениями.
SF.
7
@Timothy K: 'Примечание: из-за того, что этот алгоритм заставляет элементы менять родителей, он был назван «алгоритмом агентства по усыновлению» (в отличие от других возможных алгоритмов для работы с неверно вложенным контентом, который включал в себя «алгоритм инцеста», «алгоритм секретного дела» и «алгоритм Гейзенберга») ».
JXG 08
133

Так как же работает парсер HTML? Разве он не использует регулярные выражения для анализа?

Ну нет.

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

Самый простой тип языка и вычислений (для этих целей) - это обычный язык. Их можно сгенерировать с помощью регулярных выражений и распознать с помощью конечных автоматов. По сути, это означает, что «синтаксический анализ» строк на этих языках использует состояние, но не вспомогательную память. HTML определенно не является обычным языком. Если задуматься, список тегов может быть вложен сколь угодно глубоко. Например, таблицы могут содержать таблицы, и каждая таблица может содержать множество вложенных тегов. С помощью регулярных выражений вы можете выбрать пару тегов, но, конечно, не что-либо произвольно вложенное.

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

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

Хорошо, этого достаточно для HTML? К сожалению нет. Может быть, для супер-пупер-проверенного XML, на самом деле, в котором все теги всегда идеально выстраиваются в линию. В реальном HTML вы можете легко найти такие фрагменты, как <b><i>wow!</b></i>. Очевидно, что это не гнездо, поэтому для правильного анализа стек просто недостаточно мощный.

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

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

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

JXG
источник
22

Регулярные выражения - это лишь одна из форм синтаксического анализатора. Честно говоря, анализатор HTML будет значительно сложнее, чем может быть выражен в регулярных выражениях, с использованием рекурсивного спуска , предсказания и нескольких других методов для правильной интерпретации текста. Если вы действительно хотите вникнуть в это, вы можете попробовать lex & yacc и аналогичные инструменты.

Запрет на использование регулярных выражений для синтаксического анализа HTML, вероятно, следует записать более правильно как: «Не используйте наивные регулярные выражения для синтаксического анализа HTML ...» (чтобы вы не почувствовали гнев) «... и относитесь к результатам с осторожностью». Для некоторых конкретных целей регулярное выражение вполне может быть вполне подходящим, но вам нужно быть очень осторожным, чтобы знать об ограничениях вашего регулярного выражения, и быть осторожным, насколько это соответствует источнику текста, который вы анализируете (например, если он пользовательский ввод, будьте очень осторожны).

TJ Crowder
источник
+1, хороший ответ. Я должен признать, что раньше я использовал регулярные выражения, даже когда не контролировал HTML, но не в каких-либо публично выпущенных приложениях. Я тоже «почувствовал гнев», потому что это было наивно. Но это было давно :-)
Andy E
6

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

Сванте
источник
2

Если вы хотите получить 100% -ное решение: вам нужно написать свой собственный код, который выполняет итерацию по символам HTML, и вам нужно иметь огромное количество логики, чтобы определить, следует ли вам останавливать текущий узел и запускать следующий.

Причина в том, что это правильный HTML:

<ul>
<li>One
<li>Two
<li>Three
</ul>

Но так это:

<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>

Если вас устраивает «90% -ное решение»: тогда можно использовать XML-анализатор для загрузки документа. Или с помощью Regex (хотя xml проще, если вы тогда являетесь мастером содержимого).

Тимоти Хоури
источник
4
Парсер XML больше похож на 1% -ное решение. Количество документов HTML, которые представляют собой правильно сформированный XML, невелико.
Квентин
4
Да, они понимают ... не воспринимайте «символ за символом» буквально, поскольку вы можете попытаться транслировать вещи. Но я хочу сказать, что вам нужно написать свой собственный парсер. Программисты нового возраста не привыкли писать такой код ... мы привыкли к "HtmlDocumentUtility.Load" и тому подобному :)
Тимоти Хоури
4
@Andy E: Регулярные выражения не волшебны, они также работают посимвольно, как и любой другой вид синтаксического анализа, или, черт возьми, любая другая строковая функция.
Bart van Heukelom 08
1
Кстати: Ваш первый пример - это не просто «полу-действительный HTML». Это действительно правильный HTML 4.01 Strict. Вы можете использовать, например, валидатор W3C, чтобы проверить это. Закрывающий тег официально не обязателен для <li> (см. Спецификацию HTML 4).
sleske 08
2
@Bart: хороший момент, иногда мой мозг забывает всю логику и думает, что все работает по волшебству.
Andy E