ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
Автоматизация в WebВведениеВ главе 19 "Программирование CGI" основное внимание уделяется ответам на запросы броузеров и генерации документов с применением CGI. В этой главе программирование для Web рассматривается с другой стороны: вместо того чтобы общаться с броузером, вы сами притворяетесь броузером, генерируете запросы и обрабатываете возвращаемые документы. Для упрощения этого процесса мы будем широко использовать модули, поскольку правильно реализовать низкоуровневые сетевые протоколы и форматы документов непросто. Поручая всю трудную работу модулям, вы концентрируетесь на самом интересном - вашей собственной программе.Упоминаемые модули находятся по следующему URL: http://www.perl.com/CPAN/modules/by'-categoiy/1'5_World_Wide_Web_HTML_ HTTP_CGI/ Здесь хранятся модули для вычисления контрольных сумм кредитных карт, взаимодействия с API Netscape или сервера АрасПе, обработки графических карт (image maps), проверки HTML и работы с MIME. Однако самые большие и важные модули этой главы входят в комплекс модулей libwww-perl, объединяемых общим термином LWP. Ниже описаны лишь некоторые модули, входящие в LWP. Модули HTTP:: и LWP:: позволяют запрашивать документы с сервера. В частности, модуль LWP::Simple обеспечивает простейший способ получения документов. Однако LWP::Simple не хватает возможности обращаться к отдельным компонентам ответов HTTP. Для работы с ними используются модули HTTP::Request, HTTP::Response и HTTP::UserAgent. Оба набора модулей демонстрируются в рецептах 20.1-20.2 и 20.10. Имя модуля назначение LWP::UserAgent Класс пользовательских агентов WWW LWP::RobotUA Разработка приложений-роботов LWP::Protocol Интерфейс для различных схем протоколов LWP::Authen::Basic Обработка ответов 401 и 407 LWP::MediaTypes Конфигурация типов MIME (text/html и т. д.) LWP::Debug Отладочный модуль LWP::Simplc Простой процедурный интерфейс для часто используемых функций LWP::UserAgent Отправка HTTP::Request и возвращение HTTP::Response HTTP::Hcaders Заголовки стилей MIME/RFC822 HTTP::Message Сообщение в стиле HTTP HTTP::Request Запрос HTTP HTTP::Response Ответ HTTP HTTP::Daemon Класс сервера HTTP HTTP::Status Коды статуса HTTP (200 OK и т. д.) HTTP::Date Модуль обработки даты для форматов дат HTTP HTTP::Ncgotiate Обсуждение содержимого HTTP URI::URL URL WWW::RobotRulcs Анализ файлов robots.txt File::Listing Анализ списков содержимого каталогов Модули HTML:: находятся в близкой связи с LWP, но не распространяются в составе этого пакета. Они предназначены для анализа HTML-кода. На них основаны рецепты 20.3-20.7, программы htmlsub и hrefsub. В рецепте 20.12 приведено регулярное выражение для декодирования полей в файлах журналов Web-сервера, а также показано, как интерпретировать эти поля. Мы используем это регулярное выражение с модулем Logfile::Apache в рецепте 20.13, чтобы продемонстрировать два подхода к обобщению данных в журналах Web-серверов. 20.1. Выборка URL из сценария PerlПроблемаТребуется обратиться из сценария по некоторому URL.РешениеВоспользуйтесь функцией get модуля LWP::Simple от СРАМ, входящего в LWP.use LWP::Simple; $content =? get($URL); КомментарийПравильный выбор библиотек заметно упрощает работу. Модули LWP идеально подходят для поставленной задачи.Функция get модуля LWP::Simple в случае ошибки возвращает undef, поэтому ошибки следует проверять так: use LWP::Simple; unless (defined ($content = get $URL)) { die "could not get $URL\n"; } Однако в этом случае вы не сможете определить причину ошибки. В этой и других нетривиальных ситуациях возможностей LWP::Simple оказывается недостаточно. В примере 20.1 приведена программа выборки документа по URL. Если попытка оказывается неудачной, программа выводит строку с кодом ошибки. В противном случае печатается название документа и количество строк в его содержимом. Мы используем четыре модуля от LWP. LWP::UserAgent Модуль создает виртуальный броузер. Объект, полученный при вызове конструктора new, используется для дальнейших запросов. Мы задаем для своего агента имя "Schmozilla/v9.14 Platinum", чтобы Web-мастер мучился от зависти при просмотре журнала. HTTP:: Request Модуль создает запрос, но не отправляет его. Мы создаем запрос GET и присваиваем фиктивный URL для запрашивающей страницы. HTTP:: Response Тип объекта, возвращаемый при фактическом выполнении запроса пользовательским объектом. Проверяется на предмет ошибок и для получения искомого содержимого. URI::Heuristic Занятный маленький модуль использует Netscape-подобные алгоритмы для расширения частичных URL. Например: Частичный URL Предположение perl http://www.pcrl.coiu www.oreilly.com http://www.orcilly.com ftp.funet.fi ftp://ftp.funet.fi /etc/passwd filc:/etc/passwd Хотя строки в левом столбце не являются правильными URL (их формат не отвечает спецификации URI), Netscape пытается угадать, каким URL они соответствуют. То же самое делают и многие другие броузеры. Исходный текст программы приведен в примере 20.1. #!/usr/bin/perl -w # titlebytes - определение названия и размера документа use LWP::UserAgent; use HTTP::Request; use HTTP: -.Response; use URI::Heuristic; my $raw_url = shift or die "usage: $0 url\n"; my $url = URI::Heuristic::uf_urlstr($raw_url); $1=1; # Немедленный вывод следующей строки printf "%s =>\n\t", $url; my $ua = LWP::UserAgent->new(); $ua->agent("Schmozilla/v9.14 Platinum"); my $req = HTTP::Request->new(GET => $url); $req->referer("http://wizard.yellowbrick.oz"); # Чтобы озадачить программы анализа журнала my $response = $ua->request($req); if ($response->is_error()) { printf " %s\n", $response->status_line; } else { my $count; my $bytes; my $content = $response->content(); $bytes = length Scontent; $count = ($content =~ tr/\n/\n/): printf "%s (%d lines, %d bytes)\n", $response->title(), $count, $bytes; } Программа выдает результаты следующего вида: % titlebytes http://www.tpj.com/ http://www.tpj.com/ => The Perl Journal (109 lines, 4530 bytes) Обратите внимание: вместо правильного английского refer rer используется вариант написания ref ere г. Ошибка была допущена разработчиками стандарта при выборе имени HTTP_REFERER. > Смотри также --------------------------------
20.2. Автоматизация подачи формыПроблемаВы хотите передать сценарию CGI значения полей формы из своей программы.РешениеЕсли значения передаются методом GET, создайте URL и закодируйте форму методом query_form:use LWP::Simple; use URI::uRL; my $url = url('http://www,perl.com/cgi-bin/cpan_mod'); $url->query_form(module => 'DB_File', readme => 1); $content = get($url); Если вы используете метод POST, создайте собственного пользовательского агента и закодируйте содержимое: use HTTP::Request::Common qw(POST); use LWP::UserAgent; $ua = LWP::UserAgent->new(); my $req = POST 'http://www.perl.com/cgi-bin/cpan_mod', [ module => "DB_File", readme => 1 ]; $content = $ua->request($req)->as_stnng: КомментарийДля простых операций хватает процедурного интерфейса модуля LWP::Simple. Для менее тривиальных ситуаций модуль LWP::UserAgent предоставляет объект виртуального броузера, работа с которым осуществляется посредством вызова методов. Строка запроса имеет следующий формат:&ПОЛЕ1=ЗНАЧЕНИЕ1 &ПОЛЕ2=ЗНАЧЕНИЕ2 &ПОЛЕЗ=ЗНАЧЕНИЕЗ В запросах GET информация кодируется в запрашиваемом URL: http://www.site.com/path/to/ script.cgi?field1=value1&field2=value2&field3=value3 Служебные символы в полях должны быть соответствующим образом преобразованы, поэтому присваивание параметру а гд строки "this isn't Для передачи данных в запросе GET можно использовать модуль LWP::Simk', однако для запросов POST не существует аналогичного интерфейса LWP::Simple. Вместо этого функция POST модуля HTTP::Request::Common создает правильно отформатированный запрос с оформлением всех служебных символов. Если запрос должен проходить через прокси-сервер, сконструируйте своего пользовательского агента и прикажите ему использовать прокси: $ua->proxy(['http', 'ftp'] => 'http://proxy.myorg.com:8081'); Это означает, что запросы HTTP и FTP для данного пользовательского агента должны маршрутизироваться через прокси на порте 8081 по адресу proxy. myorg.com. > Смотри также -------------------------------
20.3. Извлечение URLПроблемаТребуется извлечь все URL из HTML-файла.РешениеВоспользуйтесь модулем HTML::LinkExtor из LWP:use HTML::LinkExtor; $parser = HTML::LinkExtor->new(undef, $base_url); $parser->parse_file($filename); @links = $parser->links; foreach $linkarray (@links) { my @element = @$linkarray; my $elt_type = shift @element; # Тип элемента # Проверить, тот ли это элемент, который нас интересует while (@element) { # Извлечь следующий атрибут и его значение my ($attr_name, $attr_value) = splice(@element, 0, 2); # ... Сделать что-то ... } } КомментарийМодуль HTML::LinkExtor можно использовать двумя способами: либо вызвать links для получения списка всех URL в документе после его полного разбора, либо передать ссылку на функцию в первом аргументе new. Указанная функция будет вызываться для каждого URL, найденного во время разбора документа.Метод links очищает список ссылок, поэтому для каждого анализируемого документа он вызывается лишь один раз. Метод возвращает ссылку на массив элементов. Каждый элемент сам по себе представляет ссылку на массив, в начале которого находится объект HTML::Element, а далее следует список пар "имя атрибута/значение". Например, для следующего HTML-фрагмента: А HREF="http://www.perl.com/">Home page IMG SRC="images/big.gif" LOWSRC="images/big-lowres.gif"> возвращается структура данных: [ [ a, href => "http://www.perl.com/" ], [ img, src =>"images/big.gif", lowsrc => "images/big-lowres.gif" ] ] В следующем фрагменте демонстрируется пример использования $elt_type и $attr_name: if ($elt_type eq 'a' && $attr_name eq 'href') { print "ANCHOR: $attr_value\n" if $attr_value->scheme =~ /http|ftp/; } if ($elt_type eq 'img' && $attr_name eq 'src') { print "IMAGE: $attr_value\n"; } Программа из примера 20.2 получает в качестве аргументов URL (например, file:///tmp/testing.html или http://www.ora.com/) и выдает в стандартный вывод отсортированный по алфавиту список уникальных ссылок из него. Пример 20.2. xurl #!/usr/bin/perl -w # xurl - получение отсортированного списка ссылок с URL use HTML::LinkExtor; use LWP::Simple; $base_url = shift; Sparser = HTML::LinkExtor->new(undef, $base_url); $parser->parse(get($base_url))->eof; @links = $parser->links; foreach $linkarray (@links) { my Oelement = @$linkarray; my $elt_type = shift ©element; while (©element) { my ($attr_name , $attr_value) = splice(@"element, 0, 2); $seen{$attr_value}++; } } for (sort keys %seen) { print $_, "\n" } У программы xurl имеется существенный недостаток: если в get или $base_url используется перенаправление, все ссылки будут рассматриваться для исходного, а не для перенаправленного URL. Возможное решение: получите документ с помощью LWP::UserAgent и проанализируйте код ответа, чтобы узнать, произошло ли перенаправление. Определив URL после перенаправления (если он есть), конструируйте объект HTML::LinkExtor. Примерный результат выглядит так: %хиrl http://www.perl.com/CPAN ftp://ftp@ftp.perl.com/CPAN/CPAN.html http://language, perl. com/inisc/CPAN. cgi http://language, perl. coin/inisc/cpan_module http://language.perl.com/misc/getcpan http://language, perl. corn/index, html http://language.perl.com/gifs/lcb.xbm В почте и сообщениях Usenet часто встречаются вида: < URL:http://www.perl.com > Это упрощает выборку URL из сообщений: @URLs = ($message =~ / > Смотри также -------------------------------
20.4. Преобразование ASCII в HTMLПроблемаТребуется преобразовать ASCII-текст в HTML.РешениеВоспользуйтесь простым кодирующим фильтром из примера 20.3. Пример 20.3. text2html#!/usr/bin/pecl -w -p00 # text2html - простейшее html-кодирование обычного текста # -p означает, что сценарий применяется для каждой записи. # -00 означает, что запись представляет собой абзац use HTML: '.Entities; $_ = encode_entities($_, "\200-\377"); if (/"W) { # Абзацы, начинающиеся с пропусков, заключаются в PRE> s{(.*)$} {PRE>\n$1/PRE>\n}s; # Оставить отступы } else { s{"(>.*)} {$1BR>}gm; # quoted text s{ s{(http:\S+)} {A HREF="$1">$K/A>}gs; # Предполагаемые URL(nлoxo) s{\*(\S+)\*} {STRONG>$K/STRONG>}g; # *Полужирный* s{\b_(\S+)\_\b} {EM>$K/EM>}g; # Курсив. s{^} {P>\n}; # Добавить тег абзаца } КомментарийЗадача преобразования произвольного текста в формат HTML не имеет общего решения, поскольку существует много разных, конфликтующих друг с другом способов форматирования обычного текста. Чем больше вам известно о входных данных, тем лучше вы их отформатируете. Например, если вы знаете, что исходным текстом будет почтовое сообщение, можно добавить следующий блок для форматирования почтовых заголовков:BEGIN { print "TABLE>"; $_ = encode_entities(scalar о); s/\n\s+/ /g; # Строки продолжения while ( /"(\S+?:)\s*(.*)$/gm ) { # Анализ заголовков print "$K/TH>$2\n"; } print "/TABLE>HR>"; } > Смотри также -------------------------------
20.5. Преобразование HTML в ASCIIПроблемаТребуется преобразовать HTML-файл в отформатированный ASCII-текст.РешениеЕсли у вас есть внешняя программа форматирования (например, lynx), воспользуйтесь ей:$ascii = 'lynx -dump $filename'; Если вы хотите сделать все в своей программе и не беспокоитесь о том, что HTML::TreeBuilder еще не умеет обрабатывать таблицы и фреймы: use HTML::FormatText; use HTML::Parse; $html = parse_htmlfile($filename); Sformatter = HTML: :FormatText->new(leftrnargin => 0, rightmargin => 50); $ascii = $1:ormatter->format($html); КомментарийВ обоих примерах предполагается, что HTML-текст находится в файле. Если он хранится в переменной, то для применения lynx необходимо записать его в файл. При работе с HTML::FormatText воспользуйтесь модулем HTML::TreeBuilder:use HTML::TreeBuilder; use HTML::FormatText; $html = HTML::TreeBuilder->new(); $html->parse($document); $formatter = HTML::FormatText->new(leftmargin => 0, rightmargin o=> 50); $ascii = $formatter->format($html); Если вы используете Netscape, команда Save As с типом Text отлично справляется с таблицами. > Смотри также -------------------------------
20.6. Удаление тегов HTMLПроблемаТребуется удалить из строки теги HTML и оставить в ней обычный текст.РешениеСледующее решение встречается часто, но работает неверно (за исключением простейшего HTML-кода):($plain_text = $html_text) ^~ s/<[~>]*>//gs; #НЕВЕРНО Правильный, но медленный и более сложный способ связан с применением модуля LWP: use HTML::Parse; use HTML::FormatText; $plain_text = HTML::FormatText->new->format(parse_html($html_text)); КомментарийКак всегда, поставленную задачу можно решить несколькими способами. Каждое решение пытается соблюдать баланс между скоростью и универсальностью. Для простейшего HTML-кода работает даже самая элементарная командная строка:% perl -pe "s/<[">]*>//g" ФАЙЛ Однако это решение не подходит для файлов, в которых теги пересекают границы строк: IMG SRC = "foo.gif" ALT = "Flurp!"> Поэтому иногда встречается следующее решение: % perl -0777 -ре "s/<[">]*>//gs" ФАЙЛ или его сценарный эквивалент: { local $/; # Временный режим чтения всего файла $html = $html =~ s/<[">]*>//gs: } Но даже этот вариант работает лишь для самого примитивного HTML-кода, не содержащего никаких "изюминок". В частности, он пасует перед следующими примерами допустимого HTML-кода (не говоря о многих других): IMG SRC = "foo.gif" ALT = "А > В"> -> script if (a< b && a=c) /script> <# Просто данные #> Проблемы возникают и в том случае, если комментарии HTML содержат другие теги: В>Меня не видно!В> -> Единственное надежное решение - использовать алгоритмы анализа HTML-кода из LWP. Эта методика продемонстрирована во втором фрагменте, приведенном в решении. Чтобы сделать анализ более гибким, субклассируйте HTML::Parser от LWP и записывайте только найденные текстовые элементы: package MyParser; use HTML::Parser; use HTML::Entities qw(decode_entities); @ISA = qw(HTML::Parser): sub text { my($self, $text) = @_; print decode_entities($text); } package main; MyParser->new->parse_file(*F); Если вас интересуют лишь простые теги, не содержащие вложенных тегов, возможно, вам подойдет другое решение. Следующий пример извлекает название несложного HTML-документа: ($title) = ($html =~ m##is); Как говорилось выше, подход с регулярными выражениями имеет свои недостатки. В примере 20.4 показано более полное решение, в котором HTML-код обрабатывается с использованием LWP. Пример 20.4. htitle #!/usr/bin/perl # htitle - Получить название HTML-документа для URL die "usage: $0 ucl ...\n" unless @ require LWP; foreach $url (@ARGV) { $ua = LWP::UserAgent->new(); $res = $ua->request(HTTP::Request->new(GET => $url)); print "$url: " if OARGV > 1; if ($res->is_success) { print $res->title, "\n"; } else { print $res->status_line, "\n"; } } Приведем пример вывода: % htitle http://www.ora.com www.oreilly.com - Welcome to O'Reilly & Associates! % htitle http://www.perl.com/ http://www.perl.com/nullvoid http://www.perl.com/: The www.perl.com Home Page http://www.perl.com/nullvoid: 404 File Not Found [> Смотри также -------------------------------
20.7. Поиск устаревших ссылокПроблемаТребуется узнать, содержит ли документ устаревшие ссылки.РешениеВоспользуйтесь методикой, описанной в рецепте 20.3, для получения всех ссылок я проверьте их существование функцией head модуля LWP::Simple.КомментарийСледующая программа является прикладным примером методики извлечения ссылок из HTML-документа. На этот раз мы не ограничиваемся простым выводом ссылок и вызываем для нее функцию head модуля LWP::Simple. Метод HEAD получает метаданные удаленного документа и определяет его статус, не загружая самого документа. Если вызов закончился неудачно, значит, ссылка не работает, и мы выводим соответствующее сообщение.Поскольку программа использует функцию get из LWP::Simple, она должна получать URL, а не имя файла. Если вы хотите поддерживать обе возможности, воспользуйтесь модулем URI::Heuristic (см. рецепт 20.1). Пример 20.5. churl #!/usr/bin/perl -w # churl - проверка URL use HTML::LinkExtor; use LWP::Simple qw(get head); $base_url = shift or die "usage: $0 Sparser = HTML::LinkExtor->new(undef, $base_url); $parser->parse(get($base_url)); @links = $parser->links; print "$base_url: \n"; foreach $linkarray (@>links) { my @element = @$linkarray; my $elt_type = shift @element; while (@element) { my ($attr_name , $attr_value) = splice(@element, 0, 2); if ($attr_value->scheme =~ /\b(ftp|https?|file)\b/) { print " $attr_value: ", head($attr_value)? "OK" : "BAD", "\n"; } } } Для программы действуют те же ограничения, что и для программы, использующей HTML::LinkExtor, из рецепта 20.3. > Смотри также -------------------------------
|