Приглашаем посетить
Спорт (sport.niv.ru)

Автоматизация в Web

Глава 20 Автоматизация в Web

Назад
Глава 20 Автоматизация в Web
Вперед

Автоматизация в 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.

> Смотри также --------------------------------
Документация по модулю LWP::Simple с CPAN и страница руководства Iwpcook(i), прилагаемая к LWP; документация по модулям LWP::UserAgent, HTTP::Request, HTTP::Response и URI::Heuristic; рецепт 20.2.

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 and " выглядит так: http://www.site.com/path/to/ script.cgi?arg=%22this+isn%27t+%3CEASY%3E+%26+%3CFUN%3E%22 Метод query_form, вызываемый для объекта URL, оформляет служебные символы формы за вас. Кроме того, можно вызвать URI: : Escape: :uri_escape или CGI:escape_html по собственной инициативе. В запросах POST строка параметров входит в тело HTML-документа, передаваемого сценарию CGI.
Для передачи данных в запросе 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.

> Смотри также -------------------------------
Документация по модулям LWP::Simple, LWP::UserAgent, HTTP::Request::Com-mon, URI::Escape и URI::URL с CPAN; рецепт 20.1.

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/015/big.gif"
LOWSRC="/images/015/big-lowres.gif">
возвращается структура данных:
[
[ a, href => "http://www.perl.com/" ],
[ img, src =>"/images/015/big.gif",
lowsrc => "/images/015/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 celement;
while (celement) {
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 =~ //g);

> Смотри также -------------------------------
Документация по модулям LWP::Simple, HTML::LinkExtor и HTML::Entities; рецепт 20.1.

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{} {A HREF="$1">$K/A>}gs # Внутренние URL(xopoшo)
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 "<TR><<TH ALIGN='LEFT'>$K</TH><TD>$2</TD></TR>\n";
}
print "</TABLE>HR>";
}


> Смотри также -------------------------------
Документация по модулю HTML::Entities от CPAN.

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 отлично справляется с таблицами.

> Смотри также -------------------------------
Документация по модулям HTML::Parse, HTML::TreeBuilder и HTML::Format-Text; man-страница lynx{1) вашей системы; рецепт 20.6.

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#\s*(.*?)\s* #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

Смотри также: Документация по модулям HTML::TreeBuilder, HTML::Parser, HTML::Entities и LWP::UserAgent с CPAN; рецепт 20.5.

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 \n";
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.

> Смотри также -------------------------------
Документация по модулям HTML::LinkExtor, LWP::Simple, LWP::UserAgent и HTTP::Response с CPAN; рецепт 20.8.
Назад
Вперед