Приглашаем посетить
Пушкин (pushkin-lit.ru)

20.8. Поиск свежих ссылок

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

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

20.8. Поиск свежих ссылок

Проблема

Имеется список URL. Вы хотите узнать, какие из них изменялись позже других.

Решение

Программа из примера 20.6 читает URL из стандартного ввода, упорядочивает их по времени последней модификации и выводит в стандартный вывод с префиксами времени. Пример 20.6. surl
#!/usr/bin/pecl -w
# surl - сортировка URL по времени последней модификации
use LWP::UserAgent;
use HTTP::Request;
use URI::URL qw(url);
my($url, %Date):
my $ua = LWP::UserAgent->new(),
while ( Surl = url(scalar <>) ) { my($req, $ans);
next unless $url->scheme =~ /"(file|https?)$/, Sans =
$ua->request(HTTP::Request->new("HEAD", $url));
if ($ans->is_success) {
$Date{$url} = $ans->last_modified || 0; # unknown } else {
print STDERR "$url: Error [", $ans->code, "] ", $ans->message, "!\n";
}
}
foreach Surl ( sort { $Date{$b} <=> $Date{$a} } keys %Date ) {
printf "%-25s %s\n", $Date{$url} ? (scalar localtime $Date{$url}) : "",
$url;
}

Комментарий

Сценарий surl больше похож на традиционную программу-фильтр. Он построчно читает URL из стандартного ввода (па самом деле данные читаются из , что по умолчанию совпадает с STDIN при пустом массиве @ARGV). Время последней модификации каждого URL извлекается с помощью запроса HEAD. Время сохраняется в хэше, где ключами являются URL. Затем простейшая сортировка хэша по значению упорядочивает URL по времени. При выводе внутренний формат времени преобразуется в формат localtime. В следующем примере программа xurl из предыдущего рецепта извлекает список URL, после чего выходные данные этой программы передаются на вход surl.
%xurl http://www.perl.com/ | surl | head
Моп Арг 20 06:16:02 1998 http://electriclichen.com/linux/sroni.html
Fri Apr 17 13:38:51 1998 http://www.oreilly.com/
Fri Mar 13 12:16:47 1998 http://www2.binevolve.com/
Sun Mar 8 21:01:27 1998 http://www.perl.org/
Tue Nov 18 13:41:32 1997 http://www.perl.com/universal/header.map
Wed Oct 1 12:55:13 1997 http://www.songline.com/
Sun Aug 17 21:43:51 1997 http://www.perl.com/graphics/perlhome.header.jpg
Sun Aug 17 21:43:47 1997 http://www.perl.com/graphics/perl.id.313c.gif
Sun Aug 17 21:43:46 1997 http://www.perl.com/graphics/ora.logo.gif
Sun Aug 17 21:43:44 1997 http://www.perl.com/graphics/header-nav.gif
Маленькие программы, которые выполняют свою узкую задачу и могут объединяться в более мощные конструкции, - верный признак хорошего программирования. Более того, можно было бы заставить xurl работать с файлами и организовать фактическую выборку содержимого URL в Web другой программой, которая бы передавала свои результаты xurl, churl или surl. Вероятно, эту программу следовало бы назвать gurl, но программа с таким именем уже существует: в комплекс модулей LWP входит программа Iwp-request с синонимами HEAD, GET и POST для выполнения этих операций в сценариях командного интерпретатора.

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

20.9. Создание шаблонов HTML

Проблема

Вы хотите сохранить параметризованный шаблон во внешнем файле, прочитать его в сценарий CGI и подставить собственные переменные вместо заполнителей, находящихся в тексте. Это позволяет отделить программу от статических частей документа.

Решение

Если вы ограничиваетесь заменой ссылок на переменные, используйте функцию
template:
sub template {
my ($filename, $fillings) = @_;
my $text;
local $/; # Режим поглощающего ввода (undef)
local *F; # Создать локальный манипулятор
open(F, "< $filename") [| return;
$text = ; # Прочитать весь файл
close(F); # Игнорировать код возврата
# Заменить конструкции %%...%% значениями из хэша
%$#ilings $text =~ s{ %%(.*?)%%}
{ exists( $fillings->{$1} ) ? $fillings->{$1}
}gsex;
return $text;
}

В этом случае используемые данные выглядят так:
Report for %%username%% BODY> H1>Report for %%username%% /H1> %%username%% logged in %%count%% times, for a total of %%total%% minutes.
Для расширения полноценных выражений используйте модуль Text::Template с CPAN, если вы можете гарантировать защиту данных от постороннего вмешательства. Файл данных для Text::Template выглядит так:
!- fancy.template for Text::Template -> HTMLXHEAD>TITLE>Report for {$user}/TITLEx/HEAD> BODY>H1>Report for {$user}/H1>
{ lcfirst($user) } logged in {$count} times, for a total of
{ int($seconds / 60) } minutes.

Комментарий

Параметризованный ввод в сценариях CGI хорош по многим причинам. Отделение программы от данных дает возможность другим людям (например, дизайнерам) изменять код HTML, не трогая программы. Еще лучше то, что две программы могут работать с одним шаблоном, поэтому стилевые изменения шаблона немедленно отразятся на обеих программах.
Предположим, вы сохранили в файле первый шаблон из решения. Ваша программа CGI содержит определение функции template (см. выше) и соответствующим образом задает значения переменных $whats_his_name, $login_count и $minute_used. Шаблон заполняется просто:
%fields = (
username => $whats_his_name,
count => $login_count,
total => $minute_used, );
print templateC'/home/httpd/templates/simple.template", \%fields);

Файл шаблона содержит ключевые слова, окруженные двойными символами % (%%КЛЮЧЕВОЕ-СЛОВО%%). Ключевые слова ищутся в хэше %$fillings, ссылка на который передается template в качестве второго аргумента. В примере 20.7 приведен более близкий к реальности пример, использующий базу данных SQL. Пример 20.7. userrepi
#!/usr/bin/perl -w
# userrepi - вывод данных о продолжительности работы пользователей
# с применением базы данных SOL
use DBI;
use CGI qw(:standard);
# Функция template() определена в решении (см. выше)
$user = param("username") or die "No username";
$dbh = DBI->connect("dbi:mysql:connections:mysql.domain.com:3306",
"connections", "seekritpassword") or die "Couldn't connect\n";
$sth = $dbh->prepare(""END_OF_SELECT") or die "Couldn't prepare SQL";
SELECT COUNT(duration),SUM(duration)
FROM logins WHERE username='$user'
ENO_OF_SELECT
if (@row = $sth->fetchrow()) {
($count, $seconds) = @row;
} else {
($count, $seconds) = (0,0);
}
$sth->finish();
$dbh->disconnect;
print header();
print templateC'report.tpl", {
'username' => $user,
'count' => $count,
'total' => $total });

Если вам потребуется более изощренное и гибкое решение, рассмотрите второй шаблон решения, основанный на модуле Text::Template с CPAN. Содержимое пар фигурных скобок, обнаруженных в файле шаблона, вычисляется как код Perl. Как правило, расширение сводится к простой подстановке переменных:
You owe: {$total} но в фигурных скобках также могут находиться полноценные выражения: The average was {$count ? ($total/$count) : 0}. Возможное применение этого шаблона продемонстрировано в примере 20.8.
Пример 20.8. userrep2
#!/usr/bin/perl -w
# userrep2 - вывод данных о продолжительности работы пользователей
# с применением базы данных SQL
use Text::Template;
use DBI;
use CGI qw(:standard);
$tmpl = "/home/httpd/templates/fancy.template";
$template = Text::Template->new(-type => "file", -source => $tmpl);
$user = param("username") or die "No username";
$dbh = DBI->connect("dbi:mysql:connections:mysql.domain.com:3306",
"connections", "secret passwd") or die "Couldn't db connect\n";
$sth = $dbh->prepare(""END_OF_SELECT") or die "Couldn't prepare SQL";
SELECT COUNT(duration),SUM(duration)
FROM logins WHERE username='$user' END_OF_SELECT
$sth->execute() or die "Couldn't execute SQL";
if (Orow = $sth->fetchrow()) {
($count, $total) = @irow;
} else {
$count = $total = 0;
}
$sth->finish();
$dbh->disconnect;
print header();
print $template->fill_in();

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

> Смотри также -------------------------------
Документация по модулю Text::Template с CPAN; рецепты 8.16; 14.10.

20.10. Зеркальное копирование Web-страниц

Проблема

Вы хотите поддерживать локальную копию Web-страницы.

Решение

Воспользуйтесь функцией mirror модуля LWP::Simple:
use LWP::Simple;
mirror($URL, $local_filename);

Комментарий

Несмотря на тесную связь с функцией get, описанной в рецепте 20.1, функция mirror не выполняет безусловной загрузки файла. В создаваемый ей запрос GET включается заголовок If-Modified-Since, чтобы сервер передавал лишь недавно обновленные файлы. Функция mirror копирует только одну страницу, а не целое дерево. Для копирования набора страниц следует использовать ее в сочетании с рецептом 20.3. Хороший вариант зеркального копирования целого удаленного дерева приведен в программе w3mir, также находящейся на CPAN. Будьте осторожны! Можно (и даже просто) написать программу, которая сходит с ума и начинает перекачивать все Web-страницы подряд. Это не только дурной тон, но и бесконечный труд, поскольку некоторые страницы генерируются динамически. Кроме того, у вас могут возникнуть неприятности с теми, кто не желает, чтобы их страницы загружались en masse.

> Смотри также -------------------------------
Документация по модулю LWP::Simple с CPAN; спецификация HTTP но адресу http://www.w3.or/puh/WWW/Protocols/HTTP/.

20.11. Создание робота

Проблема

Требуется написать сценарий, который самостоятельно работает в Web (то есть робота). При этом желательно уважать правила работы удаленных узлов.

Решение

Вместо модуля LWP::UserAgent используйте в роботе модуль LWP::RobotUA:
use LWP::RobotUA;
$ua = LWP::RobotUA->new('websnuffler/0.1', 'me@wherever.com'):

Комментарий

Чтобы жадные роботы не перегружали серверы, на узлах рекомендуется создавать файл с правилами доступа robots.txt. Если ваш сценарий получает лишь один документ, ничего страшного, но при получении множества документов с одного сервера вы легко перекроете пропускную способность узла.
Создавая собственные сценарии для работы в Web, важно помнить о правилах хорошего тона. Во-первых, не следует слишком часто запрашивать документы с одного сервера. Во-вторых, соблюдайте правила, описанные в файле robots.txt. Самый простой выход заключается в создании агентов с применением модуля LWP::RobotUA вместо LWP::UserAgent. Этот агент автоматически "снижает обороты" при многократных обращениях к одному серверу. Кроме того, он просматривает файл robots.txt каждого узла и проверяет, не пытаетесь ли вы принять файл, размер которого превышает максимально допустимый. В этом случае возвращается ответ вида:
403 (Forbidden) Forbidden by robots.txt
Следующий пример файла robots.txt получен программой GET, входящей в комплекс модулей LWP:
% GET http://www.webtechniques.com/robots.txt User-agent: *
Disallow: /stats
Disallow: /db
Disallow: /logs
Disallow: /store
Disallow: /forms
Disallow: /gifs
Disallow: /wais-src Disallow: /scripts Disallow: /config

Более интересный и содержательный пример находится по адресу http:// www.cnn.com/robots.txt. Этот файл настолько велик, что его даже держат под контролем RCS! % GET http://www.cnn.com/robots.txt | head
# robots, scram
# $1 d : robots.txt,v 1.2 1998/03/10 18:27:01 mreed Exp $
User-agent: *
Disallow: /
|
User-agent: Mozilla/3.01 (hotwired-test/0.1) Disallow: /cgi-bin Disallow: /TRANSCRIPTS Disallow: /development


Смотри также: Документация по модулю LWP::RobotUA(3) с CPAN; описание правил хорошего тона для роботов по адресу http://info.webcrawler.com/mak/projects/robots/ robot.html.

20.12. Анализ файла журнала Web-сервера

Проблема

Вы хотите извлечь из файла журнала Web-сервера лишь интересующую вас информацию.

Решение

Разберите содержимое файла журнала следующим образом:
while () {
my ($client, $identuser, $authuser, $date, $time, $tz, $method,
$url, $protocol, $status, $bytes) = /"(\S+) (\S+) (\S+)
\[([-:]+):(\d+:\d+:\d+) ([-\]]+) "(\S+) (.*?) (\S+)"
(\S+) (\S+)$/, # . . .
}

Комментарий

Приведенное выше регулярное выражение разбирает записи формата Common Log Format - неформального стандарта, которого придерживается большинство Web-серверов. Поля имеют следующий смысл:
client IP-адрес или имя домена для броузера.
identuser Результаты команды IDENT (RFC 1413), если она использовалась.
authuser Имя пользователя при аутентификации по схеме "имя/пароль".
date Дата поступления запроса (0 I/Mar/I 997).
time Время поступления запроса (12:55:36).
tz
Часовой пояс (-0700).
method Метод запроса: GET, POST, PUT.
uri Запрашиваемый URL (/-user/index.html).
protocol HTTP/1.0 или HTTP/I.I.
status Возвращаемый статус (200 - все в порядке, 500 - ошибка сервера).
bytes
Количество возвращаемых байт (может быть равно "-" для ошибок, перенаправлений и операций, не сопровождаемых пересылкой документа). В другие форматы также включаются данные о внешней ссылке и агенте. Ценой минимальных изменений можно заставить этот шаблон работать с другим форматом журнала. Обратите внимание: пробелы в URL не оформляются служебными символами. Это означает, что для извлечения URL нельзя использовать \8* - . * заставит регулярное выражение совпасть с целой строкой, а затем возвращаться до тех пор, пока не будет найдено соответствие для остатка шаблона. Мы используем . *? и фиксируем шаблон в конце строки с помощью $, чтобы механизм поиска не устанавливал совпадения и последовательно добавлял символы до тех пор, пока не будет найдено совпадение для всего шаблона.

> Смотри также
Спецификация CLF по адресу http://www.w3.org/Daemon/User/Config/ Logging.html.

20.13. Обработка серверных журналов

Проблема

Требуется обобщить данные в серверном журнале, но у вас нет специальной программы с возможностью настройки параметров.

Решение

Анализируйте журнал с помощью регулярных выражений или воспользуйтесь модулями Logfile с CPAN.

Комментарий

В примере 20.9 приведен образец генератора отчетов для журнала Apache. Пример 20.9. sumwww
#!/usr/bin/perl -w
# sumwww - обобщение данных об операциях Web-сервера
$lastdate = "";
daily_logs();
summary();
exit;
# Читать файлы CLF и запоминать обращения с хоста и на URL
sub daily_logs { while (<>) {
($type, $what) = /"(GET|POST)\s+(\S+?) \S+7 or next;
($host, undef, undef, $datetime) = split;
($bytes) = /\s(\d+)\s*$/ .or next;
($date) = ($datetime =- /\[([~:]*)/);
$posts += ($type eq POST);
$home++ if m, / ,;
if ($date ne $lastdate) {
if ($lastdate) { write_report() } else { $lastdate = $date } } $count++;
$hosts{$host}++;
$what{$what}++;
$bytesum += $bytes;
} write_report() if $count;
}
# Ускорить копирование за-счет создания синонимов
# глобальных переменных вида *typeglob
sub summary {
$lastdate = "Grand Total";
*count = *sumcount;
*bytesum = *bytesumsum
*hosts = *allhosts;
*posts = *allposts;
*what = *allwhat;
*home = *allhome;
write:
}
# Вывести сведения по хостам и URL с применением специального формата
sub wrlte_report { write;
# add to summary data $lastdate = $date;
$sumcount += $count:
$bytesumsum += $bytesum;
$allposts += $posts:
$allhome += $home;
# Сбросить данные за день $posts = $count = $bytesum = $home = 0;
@allwhat{keys %what} = keys %what;
@allhosts{keys %hosts} = keys %hosts;
%hosts = %what = ();
}
format STDOUT_TOP =
@|||||||||| @|||||| @||||||| @||||||| @|||||| @|||||| @|||||||||||||
"Date","Hosts", "Accesses"'Unidocs", "POST", "Home", "Bytes'
format STDOUT =
@>>>>>>>>>> @>>>>>> @>>>>>>> @>>>>>>> @>>>>>> @>>>>>> @>>>>>>>>>>>>>
$lastdate, scalar(keys %hosts),
$count, scalar(keys %what),
$posts, $home, $bytesum

Bytes
16058246 61879643 64613798 52437374 55623059 250612120
Пример вывода выглядит I.IK:
Date Hosts Accesses Unidocs POST Home
-lQ/May/1998 353 6447 3074 352 51
20/May/1998 1938 23868 4288 972 350
21/May/1998 1775 27872 6596 1064 376
22/May/1998 1680 21402 4467 735 285
23/May/1998 1128 21260 4944 592 186
Grand Total 6050 100849 10090 3715 1248
Модуль Logfile::Apache c CPAN (см. пример 20.10) позволяет написать аналогичную, но менее специализированную программу. Этот модуль распространяется вместе с другими модулями Logfile в единой поставке Logtile (на момент написания книги - Logfile-O.im.tar.gz).
Пример 20.10. aprept
#!/usr/bin/perl -w
# aprept - отчет по журналам Apache
use Logfile::Apache;
$1 = Logfile::Apache->new(
File => oo-", # STDIN Group => [ Domain, File ]);
$l->report(Group => Domain, Sort => Records);
$l->report(Group => File, List => [Bytes,Records]);

Конструктор new читает файл журнала и строит индексы. В параметре File передается имя файла, а в параметре Group - индексируемые поля. Возможные значения - Date (дата), Hour (время получения запроса), File (запрашиваемый файл), U or (имя пользователя, извлеченное из запроса), Host (имя хоста, запросившего документ) и Domain (Host, преобразованный в строку типа "France", "Germany" и т. д.).
Вывод отчета в STDOUT осуществляется методом report. В параметре Group передается используемый индекс, а также дополнительно - способ сортировки (Records - по количеству обращений, Bytes - по количеству переданных байт) и способ дальнейшей группировки данных (по количеству байт или количеству обращений). Приведем примеры вывода:
Domain
US Commercial 222 38.47Х US Educational 115 19.93X
Network
Unresolved Australia
Canada
Mexico

93 16.12X
54 9.36X
48 8.32X
20 3.47Х
8 1.39!<
United Kingdom 6 1,04!
File Bytes Records
13008 О.Ш 6 1.04Х
11870 0.81t 2 0.35Х
39431 2.70?! 48 6.32%
143793 9.83Х 21 3,645(
54447 3.72Х 3 0.52!".

/cgi-bin/MxScreen
/cgi-bin/pickcards
/deckmaster
/deckmaster/admin

> Смотри также - Документация по модулю Logfile::Apache с СРАN;perlform(1).

20.14. Программа: htmlsub

Следующая программа выполняет подстановку в HTML-файле так, что изменения происходят только в обычном текст. Предположим, у вас имеется файл index.html следующего содержания:
Hi!
H1>Welcome to Scooby World!
I have A HREF="pictures.html">pictures/A> of the crazy dog
himself. Here's one!


IMG SRC="scooby.Jpg" ALT="Good doggy!" P>
BLINK>He's my hero! /BLINK I would like to meet him some day,
and get my picture taken with him.


P.S. I am deathly ill. A HREF="shergold.html">Please send
cards.
/BODYX/HTML>
Программа htmlsub заменяет каждый экземпляр слова "picture" в тексте документа на "photo". Новый документ выводится в STDOUT:
% htmlsub picture photo scooby.html
Hi!
H1>Welcome to Scooby World!
I have A HREF="pictures.html">photos of the crazy dog
himself. Here's one!


IMG SRC="scooby.jpg" ALT="Good doggy! "xP>
BLINK>He's my hero! I would like to meet him some day,
and get my photo taken with him.


P.S. I am deathly ill. A HREF="shergold.html">Please send
cards.
/BODYX/HTML>
Исходный текст программы приведен в примере 20.11. Пример 20.11. htmlsub
#!/usr/bin/perl -w
# htmlsub - замена обычного текста в HTML-файле
# Автор - Джайсл Аас
sub usage { die "Usage: $0 ...\n" }
my $from = shift or usage;
my $to = shift or usage;o usage unless @ARGV;
# Субклассировать HTML::Filter для выполнения подстановок.
package MyFilter;
require HTML::Filter;
@ISA=qw(HTML::Filter);
use HTML::Entities qw(decode_entities encode_entities);
sub text
{
my $selt = snirr, my $text = decode_entities($_[0]);
$text =~ s/\Q$from/$to/go; ft Самая важная строка
$self->SUPER::text(encode_entities($text));
}
# Now use the class.
package main;
foreach (OARGV) {
MyFilter->new->parse_file($_);
}

20.15. Программа: hrefsub

Программа hrefsub выполняет подстановки в HTML-файлах так, что изменения относятся только к тексту в полях HREF тегов А HREF=" ...". Например, если в предыдущем примере scooby.html файл shergold.html был переименован в cards.html, достаточно сказать: % hrefsub shergold.html cards.html scooby.html
HTMLXHEAD TITLE>Hi! /TITLEx/HEAD>BODY>
H1>Welcome to Scooby World!
I have A HREF="pictures.html">pictures of the crazy dog
himself, Here's one!


IMG SRC="scooby.jpg" ALT="Good doggy!">


BLINK>He's my hero! /BLINK> I would like to meet him some day,
and get my picture taken with him.


P.S. I am deathly ill. a href="cards.html">Please send
cards. /BODY /HTML>

В странице руководства HTML::Filter есть раздел BUGS, в котором сказано: "Комментарии в объявлениях удаляются, а затем вставляются в виде отдельных комментариев после объявления. Если включить strict_comment(), то комментарии с внутренними "-\| -" делятся на несколько комментариев". Данная версия hrefsub при выполнении подстановки всегда преобразует <а> и имена атрибутов в теге в нижний регистр. Если строка $foo содержит несколько слов, то текст, передаваемый MyFilter->text, может быть разбит так, что эти слова разделятся и подстановка не сработает. Вероятно, в HTML::Parser следует предусмотреть новый параметр, чтобы текст возвращался лишь после чтения всего сегмента. Кроме того, кое-кто не любит, когда 8-битные символы кодировки Latin-1 замещаются уродливыми эквивалентами, поэтому hrefsub справляется и с этой проблемой.


Назад