Использование регулярных выражений для разбора HTML: почему бы и нет?
Кажется, что каждый вопрос в стеке потока, когда запрашивающий использует регулярное выражение для получения некоторой информации из HTML, неизбежно будет иметь "ответ", который говорит, что не следует использовать регулярное выражение для анализа HTML.
Почему бы и нет? Я знаю, что существуют "настоящие" парсеры HTML без кавычек, такие как Beautiful Soup, и я уверен, что они мощные и полезные, но если вы просто делаете что-то простое, быстрое или грязное, то почему использовать что-то настолько сложное, когда несколько операторов регулярных выражений будут работать нормально?
Кроме того, есть ли что-то фундаментальное, чего я не понимаю в регулярных выражениях, что делает их плохим выбором для синтаксического анализа вообще?
18 ответов
С помощью регулярных выражений синтаксический анализ всего HTML невозможен, поскольку он зависит от соответствия открывающего и закрывающего тегов, что невозможно при регулярных выражениях.
Регулярные выражения могут соответствовать только обычным языкам, но HTML является языком без контекста, а не с обычным языком (как указывал @StefanPochmann, регулярные языки также являются контекстно-свободными, поэтому контекстно-свободный не обязательно означает не регулярный). Единственное, что вы можете сделать с помощью регулярных выражений в HTML, - это эвристика, но это не сработает при любых условиях. Должна быть возможность представить HTML-файл, который будет некорректно сопоставляться любым регулярным выражением.
Для быстрого и грязного регулярного выражения все будет хорошо. Но фундаментальная вещь, которую нужно знать, это то, что невозможно создать регулярное выражение, которое будет правильно анализировать HTML.
Причина в том, что регулярные выражения не могут обрабатывать произвольно вложенные выражения. См. Можно ли использовать регулярные выражения для сопоставления с вложенными шаблонами?
(С http://htmlparsing.com/regexes)
Скажем, у вас есть файл HTML, в котором вы пытаетесь извлечь URL из тегов.
<img src="http://example.com/whatever.jpg">
Итак, вы пишете регулярное выражение в Perl:
if ( $html =~ /<img src="(.+)"/ ) {
$url = $1;
}
В этом случае, $url
будет действительно содержатьhttp://example.com/whatever.jpg
, Но что происходит, когда вы начинаете получать HTML, как это:
<img src='http://example.com/whatever.jpg'>
или же
<img src=http://example.com/whatever.jpg>
или же
<img border=0 src="http://example.com/whatever.jpg">
или же
<img
src="http://example.com/whatever.jpg">
или вы начинаете получать ложные срабатывания от
<!-- // commented out
<img src="http://example.com/outdated.png">
-->
Это выглядит так просто, и это может быть просто для одного неизменного файла, но для всего, что вы собираетесь делать с произвольными данными HTML, регулярные выражения - всего лишь рецепт будущей душевной боли.
Что касается синтаксического анализа, регулярные выражения могут быть полезны на этапе "лексического анализа" (lexer), когда входные данные разбиваются на токены. Это менее полезно на стадии "построения дерева разбора".
Для синтаксического анализатора HTML я ожидал бы, что он будет принимать только правильно сформированный HTML, и для этого требуются возможности, выходящие за пределы возможностей регулярного выражения (они не могут "подсчитать" и убедиться, что заданное число открываемых элементов сбалансировано одним и тем же числом). закрывающих элементов).
Две быстрые причины:
- трудно написать регулярное выражение, способное противостоять злонамеренному вводу; гораздо сложнее, чем с помощью готового инструмента
- Трудно написать регулярное выражение, которое может работать с нелепой разметкой, с которой вы неизбежно столкнетесь; гораздо сложнее, чем с помощью готового инструмента
Относительно пригодности регулярных выражений для синтаксического анализа в целом: они не подходят. Вы когда-нибудь видели виды регулярных выражений, которые вам понадобятся для анализа большинства языков?
Проблема в том, что большинство пользователей, которые задают вопрос, связанный с HTML и регулярным выражением, делают это, потому что они не могут найти собственное регулярное выражение, которое работает. Тогда нужно подумать, будет ли все проще при использовании парсера DOM или SAX или чего-то подобного. Они оптимизированы и сконструированы для работы с XML-подобными структурами документов.
Конечно, есть проблемы, которые можно легко решить с помощью регулярных выражений. Но акцент делается на легко.
Если вы просто хотите найти все URL, которые выглядят как http://.../
ты в порядке с регулярными выражениями Но если вы хотите найти все URL-адреса в a-элементе, который имеет класс "mylink", вам, вероятно, лучше использовать соответствующий синтаксический анализатор.
Потому что есть много способов "испортить" HTML, который браузеры будут воспринимать довольно либерально, но потребуется довольно много усилий, чтобы воспроизвести либеральное поведение браузера, чтобы покрыть все случаи регулярными выражениями, поэтому ваше регулярное выражение неизбежно потерпит неудачу в некоторых особых случаев, и это может привести к серьезным пробелам в безопасности вашей системы.
Я считаю, что ответ лежит в теории вычислений. Для анализа языка с использованием регулярных выражений он должен быть по определению "обычный" ( ссылка). HTML не является обычным языком, так как он не соответствует ряду критериев для обычного языка (во многом из-за множества уровней вложенности, присущих HTML-коду). Если вас интересует теория вычислений, я бы порекомендовал эту книгу.
Регулярные выражения не предназначены для обработки структуры вложенных тегов, и в лучшем случае сложно (в худшем случае невозможно) обрабатывать все возможные крайние случаи, которые вы получаете с реальным HTML.
HTML/XML делится на разметку и контент.
Regex полезен только для анализа лексических тегов.
Я думаю, вы могли бы вывести содержание.
Это был бы хороший выбор для парсера SAX.
Теги и контент могут быть доставлены пользователю
определенная функция, где вложенность / закрытие элементов
можно отслеживать.
Что касается простого анализа тегов, это можно сделать с помощью
регулярное выражение и используется для удаления тегов из документа.
За годы испытаний я нашел секрет
способ, которым браузеры анализируют теги, как хорошо, так и плохо сформированные
Нормальные элементы анализируются с помощью этой формы:
Ядро этих тегов использует это регулярное выражение
(?:
" [\S\s]*? "
| ' [\S\s]*? '
| [^>]?
)+
Вы заметите это [^>]?
как одно из чередований.
Это будет соответствовать несбалансированным цитатам из плохо сформированных тегов.
Кроме того, это самый корень всего зла в регулярных выражениях.
То, как он используется, вызовет удар, чтобы удовлетворить его жадный, обязательный матч
количественный контейнер.
При пассивном использовании проблем не возникает.
Но если вы заставляете что-то совпадать, перемежая это с
искомая пара атрибут / значение и не обеспечивают адекватную защиту
от отслеживания, это неуправляемый кошмар.
Это общая форма для простых старых тегов.
Обратите внимание на [\w:]
представляющий имя тега?
На самом деле, юридические символы, представляющие имя тега
невероятный список символов Юникода.
<
(?:
[\w:]+
\s+
(?:
" [\S\s]*? "
| ' [\S\s]*? '
| [^>]?
)+
\s* /?
)
>
Двигаясь дальше, мы также видим, что вы просто не можете искать определенный тег
без разбора ВСЕХ тегов.
Я имею в виду, что вы могли бы, но это должно было бы использовать комбинацию
глаголы типа (*SKIP)(*FAIL), но все же все теги должны быть проанализированы.
Причина в том, что синтаксис тегов может быть скрыт внутри других тегов и т. Д.
Таким образом, для пассивного анализа всех тегов необходимо регулярное выражение, подобное приведенному ниже.
Этот конкретный соответствует также невидимому контенту.
Когда новый HTML или XML или любой другой разработают новые конструкции, просто добавьте его как
одно из чередований.
Примечание веб-страницы - я никогда не видел веб-страницу (или xhtml/xml), что это
были проблемы с. Если найдешь, дай мне знать.
Примечание по производительности - это быстро. Это самый быстрый анализатор тегов, который я видел
(там может быть быстрее, кто знает).
У меня есть несколько конкретных версий. Это также отлично, как скребок
(если вы практический тип).
Полное необработанное регулярное выражение
<(?:(?:(?:(script|style|object|embed|applet|noframes|noscript|noembed)(?:\s+(?>"[\S\s]*?"|'[\S\s]*?'|(?:(?!/>)[^>])?)+)?\s*>)[\S\s]*?</\1\s*(?=>))|(?:/?[\w:]+\s*/?)|(?:[\w:]+\s+(?:"[\S\s]*?"|'[\S\s]*?'|[^>]?)+\s*/?)|\?[\S\s]*?\?|(?:!(?:(?:DOCTYPE[\S\s]*?)|(?:\[CDATA\[[\S\s]*?\]\])|(?:--[\S\s]*?--)|(?:ATTLIST[\S\s]*?)|(?:ENTITY[\S\s]*?)|(?:ELEMENT[\S\s]*?))))>
Отформатированный вид
<
(?:
(?:
(?:
# Invisible content; end tag req'd
( # (1 start)
script
| style
| object
| embed
| applet
| noframes
| noscript
| noembed
) # (1 end)
(?:
\s+
(?>
" [\S\s]*? "
| ' [\S\s]*? '
| (?:
(?! /> )
[^>]
)?
)+
)?
\s* >
)
[\S\s]*? </ \1 \s*
(?= > )
)
| (?: /? [\w:]+ \s* /? )
| (?:
[\w:]+
\s+
(?:
" [\S\s]*? "
| ' [\S\s]*? '
| [^>]?
)+
\s* /?
)
| \? [\S\s]*? \?
| (?:
!
(?:
(?: DOCTYPE [\S\s]*? )
| (?: \[CDATA\[ [\S\s]*? \]\] )
| (?: -- [\S\s]*? -- )
| (?: ATTLIST [\S\s]*? )
| (?: ENTITY [\S\s]*? )
| (?: ELEMENT [\S\s]*? )
)
)
)
>
Это выражение извлекает атрибуты из элементов HTML. Поддерживает:
- атрибуты без кавычек / кавычек,
- одинарные / двойные кавычки,
- экранированные кавычки внутри атрибутов,
- пробелы вокруг знаков равенства,
- любое количество атрибутов,
- проверять только атрибуты внутри тегов,
- избегать комментариев и
- управлять различными кавычками в пределах значения атрибута.
(?:\<\!\-\-(?:(?!\-\-\>)\r\n?|\n|.)*?-\-\>)|(?:<(\S+)\s+(?=.*>)|(?<=[=\s])\G)(?:((?:(?!\s|=).)*)\s*?=\s*?[\"']?((?:(?<=\")(?:(?<=\\)\"|[^\"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!\"|')(?:(?!\/>|>|\s).)+))[\"']?\s*)
Проверьте это. Лучше работает с флагами "gisx", как в демо.
Определенно есть случаи, когда использование регулярного выражения для синтаксического анализа некоторой информации из HTML - правильный путь - это во многом зависит от конкретной ситуации.
Консенсус выше, что в целом это плохая идея. Тем не менее, если структура HTML известна (и вряд ли изменится), то это все еще допустимый подход.
Имейте в виду, что, хотя сам HTML не является регулярным, части просматриваемой страницы могут быть регулярными.
Например, это ошибка для <form>
теги, которые будут вложены; если веб-страница работает правильно, используйте регулярное выражение для <form>
было бы совершенно разумно.
Недавно я сделал несколько операций с использованием только Selenium и регулярных выражений. Мне это сошло с рук, потому что данные, которые я хотел, были помещены в <form>
и положить в простой формат таблицы (чтобы я мог даже рассчитывать на <table>
, <tr>
а также <td>
быть не вложенным - что на самом деле весьма необычно). В некоторой степени регулярные выражения были даже почти необходимы, потому что некоторая структура, к которой мне нужно было получить доступ, была ограничена комментариями. (Красивый суп может дать вам комментарии, но было бы трудно получить <!-- BEGIN -->
а также <!-- END -->
блоки с помощью Beautiful Soup.)
Однако, если бы мне пришлось беспокоиться о вложенных таблицах, мой подход просто не сработал бы! Мне бы пришлось вернуться на Beautiful Soup. Однако даже в этом случае иногда вы можете использовать регулярное выражение для захвата нужного вам фрагмента, а затем углубиться в детали.
"Это зависит", хотя. Это правда, что регулярные выражения не выполняют и не могут анализировать HTML с истинной точностью по всем причинам, приведенным здесь. Однако, если последствия неправильного использования (например, не обработка вложенных тегов) незначительны, и если регулярные выражения очень удобны в вашей среде (например, когда вы взламываете Perl), продолжайте.
Предположим, что вы, возможно, анализируете веб-страницы, которые ссылаются на ваш сайт - возможно, вы нашли их с помощью поиска ссылок Google - и вы хотите получить быстрый способ получить общее представление о контексте, окружающем вашу ссылку. Вы пытаетесь запустить небольшой отчет, который может предупредить вас о связывании спама, что-то вроде этого.
В этом случае неправильный анализ некоторых документов не будет иметь большого значения. Никто, кроме вас, не увидит ошибок, и если вам очень повезет, их будет достаточно, чтобы вы могли отслеживать их индивидуально.
Я думаю, я говорю, что это компромисс. Иногда реализация или использование правильного синтаксического анализатора - настолько легкого, насколько это возможно - может не стоить проблем, если точность не критична.
Просто будьте осторожны с вашими предположениями. Я могу придумать несколько способов, которыми ярлык регулярного выражения может иметь неприятные последствия, если вы пытаетесь проанализировать что-то, что будет показано, например, публично.
Я попробовал свои силы в регулярных выражениях для этого тоже. Он в основном полезен для поиска порций контента в паре со следующим HTML-тегом, и он не ищет подходящих тегов, но подбирает закрытые теги. Сверните стопку на вашем родном языке, чтобы проверить это.
Используйте с опциями 'sx'. "g", если вам повезет:
(?P<content>.*?) # Content up to next tag
(?P<markup> # Entire tag
<!\[CDATA\[(?P<cdata>.+?)]]>| # <![CDATA[ ... ]]>
<!--(?P<comment>.+?)-->| # <!-- Comment -->
</\s*(?P<close_tag>\w+)\s*>| # </tag>
<(?P<tag>\w+) # <tag ...
(?P<attributes>
(?P<attribute>\s+
# <snip>: Use this part to get the attributes out of 'attributes' group.
(?P<attribute_name>\w+)
(?:\s*=\s*
(?P<attribute_value>
[\w:/.\-]+| # Unquoted
(?=(?P<_v> # Quoted
(?P<_q>['\"]).*?(?<!\\)(?P=_q)))
(?P=_v)
))?
# </snip>
)*
)\s*
(?P<is_self_closing>/?) # Self-closing indicator
>) # End of tag
Этот предназначен для Python (он может работать для других языков, еще не пробовал, он использует положительные взгляды, отрицательные взгляды и именованные обратные ссылки). Поддержка:
- Открыть тег -
<div ...>
- Закрыть тег -
</div>
- Комментарий -
<!-- ... -->
- CDATA -
<![CDATA[ ... ]]>
- Самозакрывающийся тег -
<div .../>
- Значения необязательных атрибутов -
<input checked>
- Значения атрибутов без кавычек / кавычек -
<div style='...'>
- Одиночные / двойные кавычки -
<div style="...">
- Побег Цитаты -
<a title='John\'s Story'>
(это не совсем правильный HTML, но я хороший парень) - Пробелы вокруг знаков равенства -
<a href = '...'>
- Названные Захваты Для Интересных Бит
Это также очень хорошо, чтобы не вызывать неправильные теги, например, когда вы забыли <
или же >
,
Если ваш regex-аромат поддерживает повторные именованные захваты, значит, вы великолепны, но Python re
нет (я знаю, что регулярное выражение делает, но мне нужно использовать ванильный Python). Вот что вы получаете:
content
- Весь контент до следующего тега. Вы могли бы оставить это вне.markup
- Весь тег со всем в нем.comment
- Если это комментарий, содержание комментария.cdata
- Если это<![CDATA[...]]>
Содержание CDATA.close_tag
- Если это закрытый тег (</div>
), имя тега.tag
- Если это открытый тег (<div>
), имя тега.attributes
- Все атрибуты внутри тега. Используйте это, чтобы получить все атрибуты, если у вас нет повторяющихся групп.attribute
- Повторяется каждый атрибут.attribute_name
- Повторяется, каждый атрибут имени.attribute_value
- Повторяется каждое значение атрибута. Это включает в себя кавычки, если он был процитирован.is_self_closing
- Это/
если это самозакрывающийся тег, иначе ничего._q
а также_v
- игнорировать это; они используются внутри для обратных ссылок.
Если ваш движок регулярных выражений не поддерживает повторные именованные захваты, есть раздел, который вы можете использовать для получения каждого атрибута. Просто запустите это регулярное выражение на attributes
группа, чтобы получить каждый attribute
, attribute_name
а также attribute_value
из этого.
Демо здесь: https://regex101.com/r/mH8jSu/11
На самом деле, разбор HTML с помощью регулярных выражений вполне возможен в PHP. Вы просто должны проанализировать всю строку назад, используя strrpos
найти <
и повторяйте регулярное выражение оттуда, используя несвязные спецификаторы каждый раз, чтобы преодолеть вложенные теги. Не причудливый и ужасно медленный на большие вещи, но я использовал его для моего собственного личного редактора шаблонов для моего сайта. Я на самом деле не разбирал HTML, но сделал несколько пользовательских тегов для запроса записей в базе данных для отображения таблиц данных (мой <#if()>
тег может выделить специальные записи таким образом). Я не был готов пойти на парсер XML только на пару самостоятельно созданных тегов (с очень не XML-данными внутри них) здесь и там.
Таким образом, хотя этот вопрос значительно мертв, он все равно обнаруживается в поиске Google. Я прочитал его и подумал, что "вызов принят", и закончил исправление моего простого кода без замены всего. Решил предложить другое мнение любому, кто ищет подобную причину. Также последний ответ был опубликован 4 часа назад, так что это все еще горячая тема.
Регулярные выражения недостаточно мощны для такого языка, как HTML. Конечно, есть несколько примеров, где вы можете использовать регулярные выражения. Но в целом это не подходит для разбора.
Вы, знаете... у вас много менталитета, вы НЕ МОЖЕТЕ это сделать, и я думаю, что все по обе стороны забора правы и неправы. Вы МОЖЕТЕ сделать это, но это требует немного больше обработки, чем просто выполнение одного регулярного выражения. Возьмите это (я написал это в течение часа) в качестве примера. Предполагается, что HTML-код полностью допустим, но в зависимости от того, какой язык вы используете для применения вышеупомянутого регулярного выражения, вы можете внести некоторые исправления в HTML-код, чтобы убедиться в его успешности. Например, удаление закрывающих тегов, которых там не должно быть: </img>
например. Затем добавьте закрывающий одиночный слеш HTML к элементам, в которых они отсутствуют, и т. Д.
Я бы использовал это в контексте написания библиотеки, которая позволила бы мне выполнять поиск элементов HTML, аналогичных JavaScript [x].getElementsByTagName()
, например. Я просто разделил бы функциональность, которую я написал в разделе DEFINE регулярного выражения, и использовал бы ее для перехода внутрь дерева элементов, по одному за раз.
Итак, это будет окончательный 100% ответ для проверки HTML? Нет. Но это начало, и немного больше работы можно сделать. Однако попытка сделать это внутри одного выполнения регулярного выражения не практична и не эффективна.