О, да, вы можете Использовать регулярные выражения для разбора HTML!
Для задачи, которую вы пытаетесь, регулярные выражения отлично подходят!
* верно, что большинство людей недооценивают сложность парсинга HTML с помощью регулярных выражений и, следовательно, делают это плохо.
Но это не какой-то фундаментальный недостаток, связанный с вычислительной теорией.Эта глупость много попугаев здесь, но вы не верите им.
Таким образом, хотя это, безусловно, можно сделать (эта публикация служит доказательством существования этого неопровержимого факта), это не означает, что должно быть.
Вы должны решить для себя, подходит ли вам задача написания того, что представляет собой выделенный специализированный HTML-анализатор из регулярных выражений.Большинство людей не такие.
Но I am.10
Общие решения синтаксического анализа HTML на основе регулярных выражений
Сначала я покажу, как просто анализировать произвольный HTML с регулярными выражениями.Полная программа находится в конце этой публикации, но сердце парсера:
for (;;) {
given ($html) {
last when (pos || 0) >= length;
printf "\@%d=", (pos || 0);
print "doctype " when / \G (?&doctype) $RX_SUBS /xgc;
print "cdata " when / \G (?&cdata) $RX_SUBS /xgc;
print "xml " when / \G (?&xml) $RX_SUBS /xgc;
print "xhook " when / \G (?&xhook) $RX_SUBS /xgc;
print "script " when / \G (?&script) $RX_SUBS /xgc;
print "style " when / \G (?&style) $RX_SUBS /xgc;
print "comment " when / \G (?&comment) $RX_SUBS /xgc;
print "tag " when / \G (?&tag) $RX_SUBS /xgc;
print "untag " when / \G (?&untag) $RX_SUBS /xgc;
print "nasty " when / \G (?&nasty) $RX_SUBS /xgc;
print "text " when / \G (?&nontag) $RX_SUBS /xgc;
default {
die "UNCLASSIFIED: " .
substr($_, pos || 0, (length > 65) ? 65 : length);
}
}
}
Посмотрите, как просто что читать?
Как написано, он идентифицирует каждый фрагмент HTML и сообщает, где он нашел этот фрагмент.Вы можете легко изменить его так, чтобы он делал все, что вам нужно, с любым заданным типом фрагмента или для более конкретных типов, чем эти.
У меня нет неудачных тестов (слева :): я успешно выполнил этот кодна более чем 100 000 HTML-файлов - каждый из которых я мог быстро и легко достать.Помимо этого, я также запускаю его на файлах , специально созданных , чтобы сломать наивные парсеры.
Это , а не наивный анализатор.
О, я уверен, что он не идеален, но мне пока не удалось его сломать.Я полагаю, что даже если бы что-то и было, исправить это было бы легко из-за четкой структуры программы.Даже программы с регулярными выражениями должны иметь структуру.
Теперь, когда это не так, позвольте мне ответить на вопрос ОП.
Демонстрация решения задачи ОП с использованием регулярных выражений
Маленькая html_input_rx
программа, которую я включил ниже, производит следующий вывод, так что вы можете видеть, что синтаксический анализ HTML с регулярными выражениями прекрасно работает для того, что вы хотите сделать:
% html_input_rx Amazon.com-_Online_Shopping_for_Electronics,_Apparel,_Computers,_Books,_DVDs_\&_more.htm
input tag #1 at character 9955:
class => "searchSelect"
id => "twotabsearchtextbox"
name => "field-keywords"
size => "50"
style => "width:100%; background-color: #FFF;"
title => "Search for"
type => "text"
value => ""
input tag #2 at character 10335:
alt => "Go"
src => "http://g-ecx.images-amazon.com/images/G/01/x-locale/common/transparent-pixel._V192234675_.gif"
type => "image"
Parse Input Tags,См. Зло не вводится
Вот источник для программы, которая произвела вывод выше.
#!/usr/bin/env perl
#
# html_input_rx - pull out all <input> tags from (X)HTML src
# via simple regex processing
#
# Tom Christiansen <tchrist@perl.com>
# Sat Nov 20 10:17:31 MST 2010
#
################################################################
use 5.012;
use strict;
use autodie;
use warnings FATAL => "all";
use subs qw{
see_no_evil
parse_input_tags
input descape dequote
load_patterns
};
use open ":std",
IN => ":bytes",
OUT => ":utf8";
use Encode qw< encode decode >;
###########################################################
parse_input_tags
see_no_evil
input
###########################################################
until eof(); sub parse_input_tags {
my $_ = shift();
our($Input_Tag_Rx, $Pull_Attr_Rx);
my $count = 0;
while (/$Input_Tag_Rx/pig) {
my $input_tag = $+{TAG};
my $place = pos() - length ${^MATCH};
printf "input tag #%d at character %d:\n", ++$count, $place;
my %attr = ();
while ($input_tag =~ /$Pull_Attr_Rx/g) {
my ($name, $value) = @+{ qw< NAME VALUE > };
$value = dequote($value);
if (exists $attr{$name}) {
printf "Discarding dup attr value '%s' on %s attr\n",
$attr{$name} // "<undef>", $name;
}
$attr{$name} = $value;
}
for my $name (sort keys %attr) {
printf " %10s => ", $name;
my $value = descape $attr{$name};
my @Q; given ($value) {
@Q = qw[ " " ] when !/'/ && !/"/;
@Q = qw[ " " ] when /'/ && !/"/;
@Q = qw[ ' ' ] when !/'/ && /"/;
@Q = qw[ q( ) ] when /'/ && /"/;
default { die "NOTREACHED" }
}
say $Q[0], $value, $Q[1];
}
print "\n";
}
}
sub dequote {
my $_ = $_[0];
s{
(?<quote> ["'] )
(?<BODY>
(?s: (?! \k<quote> ) . ) *
)
\k<quote>
}{$+{BODY}}six;
return $_;
}
sub descape {
my $string = $_[0];
for my $_ ($string) {
s{
(?<! % )
% ( \p{Hex_Digit} {2} )
}{
chr hex $1;
}gsex;
s{
& \043
( [0-9]+ )
(?: ;
| (?= [^0-9] )
)
}{
chr $1;
}gsex;
s{
& \043 x
( \p{ASCII_HexDigit} + )
(?: ;
| (?= \P{ASCII_HexDigit} )
)
}{
chr hex $1;
}gsex;
}
return $string;
}
sub input {
our ($RX_SUBS, $Meta_Tag_Rx);
my $_ = do { local $/; <> };
my $encoding = "iso-8859-1"; # web default; wish we had the HTTP headers :(
while (/$Meta_Tag_Rx/gi) {
my $meta = $+{META};
next unless $meta =~ m{ $RX_SUBS
(?= http-equiv )
(?&name)
(?&equals)
(?= (?"e)? content-type )
(?&value)
}six;
next unless $meta =~ m{ $RX_SUBS
(?= content ) (?&name)
(?&equals)
(?<CONTENT> (?&value) )
}six;
next unless $+{CONTENT} =~ m{ $RX_SUBS
(?= charset ) (?&name)
(?&equals)
(?<CHARSET> (?&value) )
}six;
if (lc $encoding ne lc $+{CHARSET}) {
say "[RESETTING ENCODING $encoding => $+{CHARSET}]";
$encoding = $+{CHARSET};
}
}
return decode($encoding, $_);
}
sub see_no_evil {
my $_ = shift();
s{ <! DOCTYPE .*? > }{}sx;
s{ <! \[ CDATA \[ .*? \]\] > }{}gsx;
s{ <script> .*? </script> }{}gsix;
s{ <!-- .*? --> }{}gsx;
return $_;
}
sub load_patterns {
our $RX_SUBS = qr{ (?(DEFINE)
(?<nv_pair> (?&name) (?&equals) (?&value) )
(?<name> \b (?= \pL ) [\w\-] + (?<= \pL ) \b )
(?<equals> (?&might_white) = (?&might_white) )
(?<value> (?"ed_value) | (?&unquoted_value) )
(?<unwhite_chunk> (?: (?! > ) \S ) + )
(?<unquoted_value> [\w\-] * )
(?<might_white> \s * )
(?<quoted_value>
(?<quote> ["'] )
(?: (?! \k<quote> ) . ) *
\k<quote>
)
(?<start_tag> < (?&might_white) )
(?<end_tag>
(?&might_white)
(?: (?&html_end_tag)
| (?&xhtml_end_tag)
)
)
(?<html_end_tag> > )
(?<xhtml_end_tag> / > )
) }six;
our $Meta_Tag_Rx = qr{ $RX_SUBS
(?<META>
(?&start_tag) meta \b
(?:
(?&might_white) (?&nv_pair)
) +
(?&end_tag)
)
}six;
our $Pull_Attr_Rx = qr{ $RX_SUBS
(?<NAME> (?&name) )
(?&equals)
(?<VALUE> (?&value) )
}six;
our $Input_Tag_Rx = qr{ $RX_SUBS
(?<TAG> (?&input_tag) )
(?(DEFINE)
(?<input_tag>
(?&start_tag)
input
(?&might_white)
(?&attributes)
(?&might_white)
(?&end_tag)
)
(?<attributes>
(?:
(?&might_white)
(?&one_attribute)
) *
)
(?<one_attribute>
\b
(?&legal_attribute)
(?&might_white) = (?&might_white)
(?:
(?"ed_value)
| (?&unquoted_value)
)
)
(?<legal_attribute>
(?: (?&optional_attribute)
| (?&standard_attribute)
| (?&event_attribute)
# for LEGAL parse only, comment out next line
| (?&illegal_attribute)
)
)
(?<illegal_attribute> (?&name) )
(?<required_attribute> (?#no required attributes) )
(?<optional_attribute>
(?&permitted_attribute)
| (?&deprecated_attribute)
)
# NB: The white space in string literals
# below DOES NOT COUNT! It's just
# there for legibility.
(?<permitted_attribute>
accept
| alt
| bottom
| check box
| checked
| disabled
| file
| hidden
| image
| max length
| middle
| name
| password
| radio
| read only
| reset
| right
| size
| src
| submit
| text
| top
| type
| value
)
(?<deprecated_attribute>
align
)
(?<standard_attribute>
access key
| class
| dir
| ltr
| id
| lang
| style
| tab index
| title
| xml:lang
)
(?<event_attribute>
on blur
| on change
| on click
| on dbl click
| on focus
| on mouse down
| on mouse move
| on mouse out
| on mouse over
| on mouse up
| on key down
| on key press
| on key up
| on select
)
)
}six;
}
UNITCHECK {
load_patterns();
}
END {
close(STDOUT)
|| die "can't close stdout: $!";
}
Вот, пожалуйста!Ничего подобного!:)
Только вы можете судить, соответствует ли ваш навык регулярным выражениям какой-либо конкретной задаче анализа.У каждого уровень мастерства разный, и каждое новое задание разное.Для заданий, где у вас есть четко определенный входной набор, регулярные выражения, очевидно, являются правильным выбором, потому что их легко собрать, когда у вас есть ограниченный набор HTML для работы.Даже начинающие регулярные выражения должны справляться с этими заданиями с помощью регулярных выражений.Все остальное излишне.
Однако , как только HTML начинает становиться все менее прибитым, как только он начинает разветвляться способами, которые вы не можете предсказать, но которые совершенно законны, когда вам нужно сопоставить большес другими вещами или с более сложными зависимостями, вы в конечном итоге придете к тому, что вам придется работать усерднее, чтобы создать решение, использующее регулярные выражения, чем при использовании класса синтаксического анализа.То, где эта точка безубыточности падает снова, зависит от вашего собственного уровня комфорта с регулярными выражениями.
Так что мне делать?
Я не собираюсь говорить вам, что вы должен делать или то, что вы не можете делать.Я думаю, что это неправильно.Я просто хочу представить вам возможности, немного откройте глаза.Вы можете выбрать, что вы хотите сделать и как вы хотите это сделать.Абсолютов нет - и никто другой не знает вашей ситуации так же хорошо, как вы сами.Если что-то кажется слишком большим, ну, может быть, так и есть.Знаете, программирование должно быть забавным .Если это не так, возможно, вы делаете это неправильно.
Можно посмотреть на мою html_input_rx
программу любым допустимым образом.Одним из них является то, что вы действительно можете анализировать HTML с помощью регулярных выражений.Но другое заключается в том, что это намного, намного, намного сложнее, чем кто-либо когда-либо думал.Это может легко привести к выводу, что моя программа является свидетельством того, что вы должны не делать, потому что это действительно слишком сложно.
Я не буду с этим не согласен.Конечно, если все, что я делаю в своей программе, не имеет смысла для вас после некоторого изучения, то вы не должны пытаться использовать регулярные выражения для такого рода задач.Для конкретного HTML регулярные выражения хороши, но для общего HTML они равносильны безумию.Я все время использую классы парсинга, особенно если это HTML, который я сам не генерировал.
Регулярные выражения, оптимальные для маленьких проблем с парсингом HTML, пессимальных для больших
Даже если моя программа воспринимается как пример того, почему вы должны не использоватьрегулярные выражения для анализа общего HTML-кода - это нормально, потому что я вроде бы хотел, чтобы это было так - - это все равно должно быть откровением, чтобы больше людей ломало ужасно распространенную и неприятную, неприятную привычку писать нечитаемые, неструктурированные и не поддерживаемые шаблоны,
Шаблоны не должны быть безобразными и не должны быть жесткими.Если вы создаете уродливые шаблоны, это отражение вас, а не их.
Феноменально изысканный язык регулярных выражений
Меня попросили указать, что мое предложенное решение вашей проблемы было написанов перл.Вы удивлены?Вы не заметили?Является ли это откровение бомбой?
Я должен признаться, что нахожу этот запрос странным до крайности, , поскольку любой, кто не может понять это, глядя на самую первую строчку моей программыбезусловно, есть и другие умственные отклонения.
Это правда, что не все другие инструменты и языки программирования столь же удобны, выразительны и мощны, когда речь идет о регулярных выражениях, как Perl.Там есть большой спектр, с некоторыми, более подходящими чем другие.В целом, с языками, в которых регулярные выражения выражены как часть основного языка, а не как библиотека, легче работать.Я ничего не сделал с регулярными выражениями, которые вы не могли бы сделать, скажем, в PCRE, хотя вы бы по-другому структурировали программу, если бы использовали C.
В конце концов другие языки будут догонять то, где сейчас находится Perlс точки зрения регулярных выражений.Я говорю это потому, что когда Perl начинал, никто не имел ничего подобного регулярным выражениям Perl.Скажите что угодно, но именно здесь Perl явно победил: все копировали регулярные выражения Perl, хотя и на разных этапах своего развития.Perl впервые применил почти (не совсем все, но почти) все, на что вы сегодня полагаетесь в современных моделях, независимо от того, какой инструмент или язык вы используете.Так что в конечном итоге остальные будут догонять.
Но они будут догонять только то, где Perl был когда-то в прошлом, так же, как и сейчас.Все продвигается.В регулярных выражениях, если ничего другого, куда ведет Perl, следуют другие.Где будет Perl, когда все наконец поймут, где сейчас находится Perl?Понятия не имею, но знаю, что мы тоже переедем.Вероятно, мы будем ближе к стилю создания Perl patterns .
Если вам нравятся такие вещи, ноЯ хотел бы использовать его в Perl₅, возможно, вас заинтересует модуль замечательного Regexp :: Grammars Дамиана Конвея. Это совершенно потрясающе, и делает то, что я сделал здесь, в моей программе, таким же примитивным, как и мое, делает шаблоны, которые люди собирают вместе, без пробелов и буквенных идентификаторов. Проверьте это!
Простой HTML Chunker
Вот полный исходный код парсера, с которого я показал центральную часть в начале этой публикации.
Я не , предлагая вам использовать это в строго проверенном классе синтаксического анализа. Но я устал от людей, притворяющихся, что никто не может анализировать HTML с регулярными выражениями только потому, что они не могут. Вы можете ясно, и эта программа является доказательством этого утверждения.
Конечно, это не легко, но это возможно возможно!
И попытка сделать это - ужасная трата времени, потому что существуют хорошие классы разбора, которые вы должны использовать для этой задачи. Правильный ответ для людей, пытающихся разобрать произвольный HTML - , а не , что это невозможно. Это простой и неискренний ответ. Правильный и честный ответ заключается в том, что они не должны пытаться сделать это, потому что это слишком хлопотно, чтобы понять с нуля; они не должны ломать спину, стремясь заново изобрести колесо, которое отлично работает.
С другой стороны, HTML, который попадает в предсказуемое подмножество , очень легко анализировать с помощью регулярных выражений. Неудивительно, что люди пытаются их использовать, потому что для небольших проблем, игрушечных проблем, возможно, нет ничего проще. Вот почему так важно различать две задачи - специфическую и общую - поскольку они не обязательно требуют одинакового подхода.
Я надеюсь, что в будущем здесь будет более справедливое и честное рассмотрение вопросов о HTML и регулярных выражениях.
Вот мой HTML-лексер. Он не пытается выполнить проверочный анализ; он просто определяет лексические элементы. Вы можете думать об этом больше как о блоке HTML , чем о парсере HTML. Разбитый HTML не очень простителен, хотя в этом направлении он делает некоторые небольшие поправки.
Даже если вы никогда не разбираете полный HTML самостоятельно (а зачем вам это решать? Наслаждайтесь!
#!/usr/bin/env perl
#
# chunk_HTML - a regex-based HTML chunker
#
# Tom Christiansen <tchrist@perl.com
# Sun Nov 21 19:16:02 MST 2010
########################################
use 5.012;
use strict;
use autodie;
use warnings qw< FATAL all >;
use open qw< IN :bytes OUT :utf8 :std >;
MAIN: {
$| = 1;
lex_html(my $page = slurpy());
exit();
}
########################################################################
sub lex_html {
our $RX_SUBS; ###############
my $html = shift(); # Am I... #
for (;;) { # forgiven? :)#
given ($html) { ###############
last when (pos || 0) >= length;
printf "\@%d=", (pos || 0);
print "doctype " when / \G (?&doctype) $RX_SUBS /xgc;
print "cdata " when / \G (?&cdata) $RX_SUBS /xgc;
print "xml " when / \G (?&xml) $RX_SUBS /xgc;
print "xhook " when / \G (?&xhook) $RX_SUBS /xgc;
print "script " when / \G (?&script) $RX_SUBS /xgc;
print "style " when / \G (?&style) $RX_SUBS /xgc;
print "comment " when / \G (?&comment) $RX_SUBS /xgc;
print "tag " when / \G (?&tag) $RX_SUBS /xgc;
print "untag " when / \G (?&untag) $RX_SUBS /xgc;
print "nasty " when / \G (?&nasty) $RX_SUBS /xgc;
print "text " when / \G (?&nontag) $RX_SUBS /xgc;
default {
die "UNCLASSIFIED: " .
substr($_, pos || 0, (length > 65) ? 65 : length);
}
}
}
say ".";
}
#####################
# Return correctly decoded contents of next complete
# file slurped in from the <ARGV> stream.
#
sub slurpy {
our ($RX_SUBS, $Meta_Tag_Rx);
my $_ = do { local $/; <ARGV> }; # read all input
return unless length;
use Encode qw< decode >;
my $bom = "";
given ($_) {
$bom = "UTF-32LE" when / ^ \xFf \xFe \0 \0 /x; # LE
$bom = "UTF-32BE" when / ^ \0 \0 \xFe \xFf /x; # BE
$bom = "UTF-16LE" when / ^ \xFf \xFe /x; # le
$bom = "UTF-16BE" when / ^ \xFe \xFf /x; # be
$bom = "UTF-8" when / ^ \xEF \xBB \xBF /x; # st00pid
}
if ($bom) {
say "[BOM $bom]";
s/^...// if $bom eq "UTF-8"; # st00pid
# Must use UTF-(16|32) w/o -[BL]E to strip BOM.
$bom =~ s/-[LB]E//;
return decode($bom, $_);
# if BOM found, don't fall through to look
# for embedded encoding spec
}
# Latin1 is web default if not otherwise specified.
# No way to do this correctly if it was overridden
# in the HTTP header, since we assume stream contains
# HTML only, not also the HTTP header.
my $encoding = "iso-8859-1";
while (/ (?&xml) $RX_SUBS /pgx) {
my $xml = ${^MATCH};
next unless $xml =~ m{ $RX_SUBS
(?= encoding ) (?&name)
(?&equals)
(?"e) ?
(?<ENCODING> (?&value) )
}sx;
if (lc $encoding ne lc $+{ENCODING}) {
say "[XML ENCODING $encoding => $+{ENCODING}]";
$encoding = $+{ENCODING};
}
}
while (/$Meta_Tag_Rx/gi) {
my $meta = $+{META};
next unless $meta =~ m{ $RX_SUBS
(?= http-equiv ) (?&name)
(?&equals)
(?= (?"e)? content-type )
(?&value)
}six;
next unless $meta =~ m{ $RX_SUBS
(?= content ) (?&name)
(?&equals)
(?<CONTENT> (?&value) )
}six;
next unless $+{CONTENT} =~ m{ $RX_SUBS
(?= charset ) (?&name)
(?&equals)
(?<CHARSET> (?&value) )
}six;
if (lc $encoding ne lc $+{CHARSET}) {
say "[HTTP-EQUIV ENCODING $encoding => $+{CHARSET}]";
$encoding = $+{CHARSET};
}
}
return decode($encoding, $_);
}
########################################################################
# Make sure to this function is called
# as soon as source unit has been compiled.
UNITCHECK { load_rxsubs() }
# useful regex subroutines for HTML parsing
sub load_rxsubs {
our $RX_SUBS = qr{
(?(DEFINE)
(?<WS> \s * )
(?<any_nv_pair> (?&name) (?&equals) (?&value) )
(?<name> \b (?= \pL ) [\w:\-] + \b )
(?<equals> (?&WS) = (?&WS) )
(?<value> (?"ed_value) | (?&unquoted_value) )
(?<unwhite_chunk> (?: (?! > ) \S ) + )
(?<unquoted_value> [\w:\-] * )
(?<any_quote> ["'] )
(?<quoted_value>
(?<quote> (?&any_quote) )
(?: (?! \k<quote> ) . ) *
\k<quote>
)
(?<start_tag> < (?&WS) )
(?<html_end_tag> > )
(?<xhtml_end_tag> / > )
(?<end_tag>
(?&WS)
(?: (?&html_end_tag)
| (?&xhtml_end_tag) )
)
(?<tag>
(?&start_tag)
(?&name)
(?:
(?&WS)
(?&any_nv_pair)
) *
(?&end_tag)
)
(?<untag> </ (?&name) > )
# starts like a tag, but has screwed up quotes inside it
(?<nasty>
(?&start_tag)
(?&name)
.*?
(?&end_tag)
)
(?<nontag> [^<] + )
(?<string> (?"ed_value) )
(?<word> (?&name) )
(?<doctype>
<!DOCTYPE
# please don't feed me nonHTML
### (?&WS) HTML
[^>]* >
)
(?<cdata> <!\[CDATA\[ .*? \]\] > )
(?<script> (?= <script ) (?&tag) .*? </script> )
(?<style> (?= <style ) (?&tag) .*? </style> )
(?<comment> <!-- .*? --> )
(?<xml>
< \? xml
(?:
(?&WS)
(?&any_nv_pair)
) *
(?&WS)
\? >
)
(?<xhook> < \? .*? \? > )
)
}six;
our $Meta_Tag_Rx = qr{ $RX_SUBS
(?<META>
(?&start_tag) meta \b
(?:
(?&WS) (?&any_nv_pair)
) +
(?&end_tag)
)
}six;
}
# nobody *ever* remembers to do this!
END { close STDOUT }