Использование регулярных выражений для анализа HTML: почему бы и нет?

Кажется, что каждый вопрос о stackoverflow, в котором обманщик использует regex для захвата некоторой информации из HTML, неизбежно имеет "ответ", который говорит, что не использовать регулярное выражение для разбора HTML.

Почему бы и нет? Я знаю, что есть цитаты-безоговорочные "реальные" парсеры HTML, такие как Beautiful Soup, и я уверен, что они мощные и полезно, но если вы просто делаете что-то простое, быстрое или грязное, то зачем пытаться использовать что-то настолько сложное, когда несколько операторов регулярных выражений будут работать нормально?

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

Ответ 1

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

Регулярные выражения могут соответствовать только обычным языкам, но HTML - это контекстно-свободный язык, а не обычный язык (как отметил @StefanPochmann, обычные языки также не содержат контекста, поэтому контекстно-свободный необязательно означает не регулярность). Единственное, что вы можете делать с регулярными выражениями на HTML, это эвристика, но это не будет работать при каждом условии. Должно быть возможно представить HTML файл, который будет неправильно сопоставлен любым регулярным выражением.

Ответ 2

Для quick'n'dirty regexp все будет хорошо. Но самое главное знать, что невозможно создать регулярное выражение, которое будет правильно анализировать HTML.

Причина в том, что regexps can not обрабатывает произвольные вложенные выражения. См. Можно ли использовать регулярные выражения для соответствия вложенным шаблонам?

Ответ 3

(Из http://htmlparsing.com/regexes)

Скажем, у вас есть файл HTML, в котором вы пытаетесь извлечь URL-адреса из < & IMG GT; теги.

<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-данных, регулярные выражения - это всего лишь рецепт будущей сердечной боли.

Ответ 4

Две быстрых причины:

  • писать регулярное выражение, которое может противостоять вредоносному вводу; путь сложнее, чем использовать готовый инструмент
  • писать регулярное выражение, которое может работать с нелепой разметкой, с которой вы неизбежно столкнетесь, сложно; путь сложнее, чем использовать готовый инструмент

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

Ответ 5

Что касается синтаксического анализа, регулярные выражения могут быть полезны на этапе "лексического анализа" (lexer), где ввод разбивается на токены. Это менее полезно на этапе "построить дерево синтаксического анализа".

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

Ответ 6

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

Ответ 7

Проблема заключается в том, что большинство пользователей, которые задают вопрос, связанный с HTML и регулярным выражением, делают это, потому что они не могут найти собственное регулярное выражение, которое работает. Тогда нужно подумать, будет ли все проще при использовании парсера DOM или SAX или чего-то подобного. Они оптимизированы и построены с целью работы с структурами документов, подобными XML.

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

Если вы просто хотите найти все URL-адреса, которые выглядят как http://.../, у вас все нормально с регулярными выражениями. Но если вы хотите найти все URL-адреса, которые находятся в a-Element, который имеет класс "mylink", вам, вероятно, лучше использовать соответствующий синтаксический анализатор.

Ответ 8

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

Ответ 9

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

Ответ 10

Это выражение извлекает атрибуты из элементов HTML. Он поддерживает:

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

(?:\<\!\-\-(?:(?!\-\-\>)\r\n?|\n|.)*?-\-\>)|(?:<(\S+)\s+(?=.*>)|(?<=[=\s])\G)(?:((?:(?!\s|=).)*)\s*?=\s*?[\"']?((?:(?<=\")(?:(?<=\\)\"|[^\"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!\"|')(?:(?!\/>|>|\s).)+))[\"']?\s*)

Проверьте это. Он лучше работает с флагами "gisx", как в демо.

Ответ 11

"Это зависит". Верно, что регулярные выражения не могут и не могут анализировать HTML с достоверной точностью по всем приведенным здесь причинам. Если, однако, последствия неправильного использования (например, не обрабатываются вложенные теги) незначительны, и если регулярные выражения очень удобны в вашей среде (например, когда вы взламываете Perl), продолжайте.

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

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

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

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

Ответ 12

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

Консенсус выше заключается в том, что в целом это плохая идея. Однако, если структура HTML известна (и вряд ли изменится), то она по-прежнему является действительным подходом.

Ответ 13

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]*? )
           )
      )
 )
 >

Ответ 14

Имейте в виду, что, хотя сам HTML не является регулярным, части страницы, на которую вы смотрите, могут быть регулярными.

Например, для тегов <form> является ошибкой вложенности; если веб-страница работает правильно, то использование регулярного выражения для захвата <form> было бы вполне разумным.

Недавно я сделал несколько скрепок, используя только Selenium и регулярные выражения. Мне это удалось, потому что нужные мне данные были помещены в <form> и помещены в простой формат таблицы (поэтому я мог даже рассчитывать на <table>, <tr> и <td> не вложенные - что на самом деле очень необычно). В некоторой степени регулярные выражения были даже почти необходимы, потому что некоторые структуры, которые мне нужны для доступа, были ограничены комментариями. (Beautiful Soup может дать вам комментарии, но было бы трудно захватить блоки <!-- BEGIN --> и <!-- END -->, используя Beautiful Soup.)

Если бы мне пришлось беспокоиться о вложенных таблицах, мой подход просто не сработал бы! Мне пришлось бы отступить на Beautiful Soup. Однако даже тогда вы можете использовать регулярное выражение, чтобы захватить требуемый фрагмент, а затем оттуда оттуда.

Ответ 15

Собственно, HTML-анализ с регулярным выражением вполне возможен в PHP. Вам просто нужно проанализировать всю строку назад, используя strrpos, чтобы найти < и повторить регулярное выражение оттуда, используя каждый раз, когда для определения вложенных тегов используются специальные атрибуты ungreedy. Не очень и очень медленно на больших вещах, но я использовал его для своего собственного редактора шаблонов для своего сайта. Я фактически не разбирал HTML, но несколько специальных тегов, которые я сделал для запросов к записям базы данных, чтобы отображать таблицы данных (мой тег <#if()> мог выделять специальные записи таким образом). Я не был готов пойти на синтаксический анализатор XML только на пару самоподготовленных тегов (с очень не-XML-данными внутри них) здесь и там.

Итак, хотя этот вопрос значительно мертв, он все равно появляется в поиске Google. Я прочитал его и подумал, что "вызов принят", и закончил исправление моего простого кода без необходимости замены всего. Решил предложить другое мнение любому, кто ищет подобную причину. Также последний ответ был отправлен 4 часа назад, так что это по-прежнему горячая тема.

Ответ 16

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

Использовать с параметрами '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 (он может работать на других языках, не пробовал, использует положительные образы, отрицательные lookbehinds и назвал обратные ссылки). Поддержка:

  • Открыть тег - <div ...>
  • Закрыть тег - </div>
  • Комментарий - <!-- ... -->
  • CDATA - <![CDATA[ ... ]]>
  • Self-Closing Tag - <div .../>
  • Дополнительные значения атрибута - <input checked>
  • Значения без кавычек/котировок - <div style='...'>
  • Одиночные/двойные кавычки - <div style="...">
  • Скрытые котировки - <a title='John\ Story'>
    (это не совсем правильный HTML, но я хороший парень)
  • Пробелы вокруг равных знаков - <a href = '...'>
  • Именованные записи для интересных бит.

Также неплохо было не запускать неверные теги, например, когда вы забыли < или >.

Если ваш аромат регулярного выражения поддерживает повторяющиеся имена, то вы золотые, но Python re не (я знаю, что regex does, но мне нужно использовать vanilla Python). Вот что вы получите:

  • content - весь контент до следующего тега. Вы можете оставить это.
  • markup - весь тег со всем в нем.
  • comment - Если это комментарий, содержимое комментария.
  • cdata - Если это <![CDATA[...]]>, содержимое CDATA.
  • close_tag - если это тег close (</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

Ответ 17

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

Ответ 18

Ты, знаешь... там много менталитета тебя НЕ МОЖЕТ делать это, и я думаю, что все по обеим сторонам забора правильные и неправильные. Вы CAN выполняете это, но для этого требуется немного больше обработки, чем просто запустить одно регулярное выражение. Возьмем this (я написал это через час) в качестве примера. Предполагается, что HTML полностью действителен, но в зависимости от того, какой язык вы используете для применения вышеупомянутого регулярного выражения, вы можете сделать некоторые исправления HTML, чтобы убедиться, что он будет успешным. Например, удаление закрывающих тегов, которые не должны быть там: </img> например. Затем добавьте закрытие одной косой черты HTML к элементам, которые их не хватает, и т.д.

Я бы использовал это в контексте написания библиотеки, которая позволила бы мне, например, выполнять поиск элементов HTML, аналогично поиску JavaScript [x].getElementsByTagName(). Я просто объединил функциональность, которую я написал в разделе DEFINE регулярного выражения, и использовал его для шага внутри дерева элементов по одному.

Итак, будет ли это окончательный 100% -ый ответ для проверки HTML? Нет. Но это начало и с немного большей работой, это можно сделать. Однако попытка сделать это внутри одного выполнения регулярного выражения непрактична и эффективна.